diff --git a/pkg/cargo/kilnfile_validate.go b/pkg/cargo/kilnfile_validate.go index 552ab9c5a..76f716962 100644 --- a/pkg/cargo/kilnfile_validate.go +++ b/pkg/cargo/kilnfile_validate.go @@ -57,3 +57,42 @@ func checkComponentVersionsAndConstraint(spec ComponentSpec, lock ComponentLock, return nil } + +func checkStemcell(spec Kilnfile, lock KilnfileLock) []error { + v, err := semver.NewVersion(lock.Stemcell.Version) + if err != nil { + return []error{fmt.Errorf("invalid lock version %q in Kilnfile.lock: %w", + lock.Stemcell.Version, err)} + } + + if spec.Stemcell.Version != "" { + c, err := semver.NewConstraint(spec.Stemcell.Version) + if err != nil { + return []error{fmt.Errorf("invalid version constraint %q in Kilnfile: %w", + lock.Stemcell.Version, err)} + } + + matches, errs := c.Validate(v) + if !matches { + return []error{fmt.Errorf("stemcell version %s in Kilnfile.lock does not match constraint %q: %v", + lock.Stemcell.Version, spec.Stemcell.Version, errs)} + } + } + + var result []error + for index, componentLock := range lock.Releases { + if componentLock.StemcellOS == "" { + continue + } + if componentLock.StemcellOS != lock.Stemcell.OS { + result = append(result, fmt.Errorf("spec %s (index %d in Kilnfile) has stemcell os that does not match the stemcell lock os", + componentLock.Name, index)) + } + if componentLock.StemcellVersion != lock.Stemcell.Version { + result = append(result, fmt.Errorf("spec %s (index %d in Kilnfile) has stemcell version that does not match the stemcell lock (expected %s but got %s)", + componentLock.Name, index, lock.Stemcell.Version, componentLock.StemcellVersion)) + } + } + + return result +} diff --git a/pkg/cargo/kilnfile_validate_test.go b/pkg/cargo/kilnfile_validate_test.go index 648532738..d7a4a4748 100644 --- a/pkg/cargo/kilnfile_validate_test.go +++ b/pkg/cargo/kilnfile_validate_test.go @@ -142,3 +142,134 @@ func TestValidate_checkComponentVersionsAndConstraint(t *testing.T) { )) }) } + +func Test_checkStemcell_valid_kilfiles(t *testing.T) { + t.Parallel() + please := Ω.NewWithT(t) + results := checkStemcell(Kilnfile{ + Releases: []ComponentSpec{ + {Name: "banana", Version: "1.2.3"}, + {Name: "lemon", Version: "2.2.2"}, + }, + Stemcell: Stemcell{ + OS: "fruit", + Version: "500.*", + }, + }, KilnfileLock{ + Releases: []ComponentLock{ + {Name: "banana", Version: "1.2.3", StemcellOS: "fruit", StemcellVersion: "500.4"}, + {Name: "lemon", Version: "2.2.2"}, + }, + Stemcell: Stemcell{ + OS: "fruit", + Version: "500.4", + }, + }) + please.Expect(results).To(Ω.HaveLen(0)) +} + +func Test_checkStemcell_wrong_version(t *testing.T) { + t.Parallel() + please := Ω.NewWithT(t) + results := checkStemcell(Kilnfile{ + Releases: []ComponentSpec{ + {Name: "banana", Version: "1.2.3"}, + {Name: "lemon", Version: "2.2.2"}, + }, + Stemcell: Stemcell{ + OS: "fruit", + Version: "500.*", + }, + }, KilnfileLock{ + Releases: []ComponentLock{ + {Name: "banana", Version: "1.2.3", StemcellOS: "fruit", StemcellVersion: "400"}, + {Name: "lemon", Version: "2.2.2"}, + }, + Stemcell: Stemcell{ + OS: "fruit", + Version: "500.4", + }, + }) + please.Expect(results).To(Ω.HaveLen(1)) + please.Expect(results[0]).To(Ω.MatchError(Ω.ContainSubstring("has stemcell version that does not match the stemcell lock"))) +} + +func Test_checkStemcell_wrong_os_name(t *testing.T) { + t.Parallel() + please := Ω.NewWithT(t) + results := checkStemcell(Kilnfile{ + Releases: []ComponentSpec{ + {Name: "banana", Version: "1.2.3"}, + {Name: "lemon", Version: "2.2.2"}, + }, + Stemcell: Stemcell{ + OS: "fruit", + Version: "500.*", + }, + }, KilnfileLock{ + Releases: []ComponentLock{ + {Name: "banana", Version: "1.2.3", StemcellOS: "soap", StemcellVersion: "500.4"}, + {Name: "lemon", Version: "2.2.2"}, + }, + Stemcell: Stemcell{ + OS: "fruit", + Version: "500.4", + }, + }) + please.Expect(results).To(Ω.HaveLen(1)) + please.Expect(results[0]).To(Ω.MatchError(Ω.ContainSubstring("stemcell os that does not match the stemcell lock os"))) +} + +func Test_checkStemcell_invalid_version_lock(t *testing.T) { + t.Parallel() + please := Ω.NewWithT(t) + results := checkStemcell(Kilnfile{ + Stemcell: Stemcell{ + OS: "fruit", + Version: "500.0", + }, + }, KilnfileLock{ + Stemcell: Stemcell{ + OS: "fruit", + Version: "FAIL", + }, + }) + please.Expect(results).To(Ω.HaveLen(1)) + please.Expect(results[0]).To(Ω.MatchError(Ω.ContainSubstring("invalid lock version"))) +} + +func Test_checkStemcell_invalid_version_constraint(t *testing.T) { + t.Parallel() + please := Ω.NewWithT(t) + results := checkStemcell(Kilnfile{ + Stemcell: Stemcell{ + OS: "fruit", + Version: "FAIL", + }, + }, KilnfileLock{ + Stemcell: Stemcell{ + OS: "fruit", + Version: "2.0.0", + }, + }) + please.Expect(results).To(Ω.HaveLen(1)) + please.Expect(results[0]).To(Ω.MatchError(Ω.ContainSubstring("invalid version constraint"))) +} + +func Test_checkStemcell_lock_version_does_not_match_constraint(t *testing.T) { + t.Parallel() + please := Ω.NewWithT(t) + results := checkStemcell(Kilnfile{ + Stemcell: Stemcell{ + OS: "fruit", + Version: "400.*", + }, + }, KilnfileLock{ + Stemcell: Stemcell{ + OS: "fruit", + Version: "111.222", + }, + }) + please.Expect(results).To(Ω.HaveLen(1)) + please.Expect(results[0]).To(Ω.MatchError(Ω.ContainSubstring("does not match constraint"))) +}