From feda926f5bfff2808896081cc946b2bb346fba68 Mon Sep 17 00:00:00 2001 From: Christopher Hunter Date: Wed, 18 May 2022 11:37:58 -0700 Subject: [PATCH 1/5] fix (cach-compiled-release): false failure when BOSH release does not have packages we should be able to cache compiled releases that do not contain any packages. for example: bosh-dns-aliases-release Co-authored-by: Mark Stokan --- internal/commands/cache_compiled_releases.go | 34 +++++++++++++++++-- .../commands/cache_compiled_releases_test.go | 34 +++++++++++++++---- pkg/cargo/kilnfile.go | 4 +++ 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/internal/commands/cache_compiled_releases.go b/internal/commands/cache_compiled_releases.go index bc38710b2..32aadc617 100644 --- a/internal/commands/cache_compiled_releases.go +++ b/internal/commands/cache_compiled_releases.go @@ -188,8 +188,10 @@ func (cmd CacheCompiledReleases) Execute(args []string) error { StemcellVersion: stagedStemcellVersion, } - if hasRelease, err := bosh.HasRelease(rel.Name, rel.Version, requirement.OSVersionSlug()); err != nil { - return fmt.Errorf("failed to find release %s: %w", requirement.ReleaseSlug(), err) + if hasRelease, err := hasRequiredCompiledPackages(bosh, rel.ReleaseSlug(), requirement.OSVersionSlug()); err != nil { + if !errors.Is(err, errNoPackages) { + return fmt.Errorf("failed to find release %s: %w", requirement.ReleaseSlug(), err) + } } else if !hasRelease { return fmt.Errorf("%[1]s compiled with %[2]s is not found on bosh director (it might have been uploaded as a compiled release and the director can't recompile it for the compilation target %[2]s)", requirement.ReleaseSlug(), requirement.OSVersionSlug()) } @@ -216,6 +218,34 @@ func (cmd CacheCompiledReleases) Execute(args []string) error { return nil } +var errNoPackages = errors.New("release has no packages") + +func hasRequiredCompiledPackages(d boshdir.Director, releaseSlug boshdir.ReleaseSlug, stemcell boshdir.OSVersionSlug) (bool, error) { + release, err := d.FindRelease(releaseSlug) + if err != nil { + return false, err + } + + pkgs, err := release.Packages() + if err != nil { + return false, err + } + + if len(pkgs) == 0 { + return true, errNoPackages + } + + for _, pkg := range pkgs { + for _, compiledPkg := range pkg.CompiledPackages { + if compiledPkg.Stemcell == stemcell { + return true, nil + } + } + } + + return false, nil +} + func (cmd CacheCompiledReleases) fetchProductDeploymentData() (_ OpsManagerReleaseCacheSource, deploymentName, stemcellOS, stemcellVersion string, _ error) { omAPI, err := cmd.OpsManager(cmd.Options.ClientConfiguration) if err != nil { diff --git a/internal/commands/cache_compiled_releases_test.go b/internal/commands/cache_compiled_releases_test.go index 432c48518..11e7f9db0 100644 --- a/internal/commands/cache_compiled_releases_test.go +++ b/internal/commands/cache_compiled_releases_test.go @@ -182,7 +182,18 @@ func TestCacheCompiledReleases_Execute_when_one_release_is_cached_another_is_alr _, _ = writer.Write(releaseInBlobstore) return nil }) - bosh.HasReleaseReturns(true, nil) + bosh.FindReleaseStub = func(slug director.ReleaseSlug) (director.Release, error) { + switch slug.Name() { + default: + panic(fmt.Errorf("FindReleaseStub input not handled: %#v", slug)) + case "lemon": + return &boshdirFakes.FakeRelease{ + PackagesStub: func() ([]director.Package, error) { + return []director.Package{{CompiledPackages: []director.CompiledPackage{{Stemcell: director.NewOSVersionSlug("alpine", "9.0.0")}}}}, nil + }, + }, nil + } + } releaseStorage := new(fakes.ReleaseStorage) releaseStorage.GetMatchedReleaseCalls(fakeCacheData) @@ -305,7 +316,18 @@ func TestCacheCompiledReleases_Execute_when_a_release_is_not_compiled_with_the_c bosh := new(boshdirFakes.FakeDirector) bosh.FindDeploymentReturns(deployment, nil) - bosh.HasReleaseReturns(false, nil) // <- this is the important thing + bosh.FindReleaseStub = func(slug director.ReleaseSlug) (director.Release, error) { + switch slug.Name() { + default: + panic(fmt.Errorf("FindReleaseStub input not handled: %#v", slug)) + case "banana": + return &boshdirFakes.FakeRelease{ + PackagesStub: func() ([]director.Package, error) { + return make([]director.Package, 1), nil + }, + }, nil + } + } deployment.ExportReleaseReturns(director.ExportReleaseResult{}, nil) bosh.DownloadResourceUncheckedCalls(func(_ string, writer io.Writer) error { @@ -347,11 +369,9 @@ func TestCacheCompiledReleases_Execute_when_a_release_is_not_compiled_with_the_c please.Expect(err).To(Ω.MatchError(Ω.ContainSubstring("not found on bosh director"))) { - requestedReleaseName, requestedReleaseVersion, requestedStemcellSlug := bosh.HasReleaseArgsForCall(0) - please.Expect(requestedReleaseName).To(Ω.Equal("banana")) - please.Expect(requestedReleaseVersion).To(Ω.Equal("2.0.0")) - please.Expect(requestedStemcellSlug.Version()).To(Ω.Equal("8.0.0")) - please.Expect(requestedStemcellSlug.OS()).To(Ω.Equal("alpine")) + requestedReleaseSlug := bosh.FindReleaseArgsForCall(0) + please.Expect(requestedReleaseSlug.Name()).To(Ω.Equal("banana")) + please.Expect(requestedReleaseSlug.Version()).To(Ω.Equal("2.0.0")) } please.Expect(output.String()).To(Ω.ContainSubstring("1 release needs to be exported and cached")) diff --git a/pkg/cargo/kilnfile.go b/pkg/cargo/kilnfile.go index d818e24c9..5fd4ac85a 100644 --- a/pkg/cargo/kilnfile.go +++ b/pkg/cargo/kilnfile.go @@ -128,6 +128,10 @@ type ComponentLock struct { RemotePath string `yaml:"remote_path"` } +func (lock ComponentLock) ReleaseSlug() boshdir.ReleaseSlug { + return boshdir.NewReleaseSlug(lock.Name, lock.Version) +} + func (lock ComponentLock) String() string { var b strings.Builder b.WriteString(lock.Name) From aaaa804ea93ff844561e0488e3a4bf6c3ff80536 Mon Sep 17 00:00:00 2001 From: Christopher Hunter Date: Wed, 18 May 2022 11:40:57 -0700 Subject: [PATCH 2/5] docs: describe hasRequiredCompiledPackages Co-authored-by: Mark Stokan --- internal/commands/cache_compiled_releases.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/commands/cache_compiled_releases.go b/internal/commands/cache_compiled_releases.go index 32aadc617..8523243e5 100644 --- a/internal/commands/cache_compiled_releases.go +++ b/internal/commands/cache_compiled_releases.go @@ -220,6 +220,9 @@ func (cmd CacheCompiledReleases) Execute(args []string) error { var errNoPackages = errors.New("release has no packages") +// hasRequiredCompiledPackages implementation is copied from the boshdir.DirectorImpl HasRelease method. It adds the check +// for the length of the packages slice to allow for BOSH releases that do not have any packages. One example of a BOSH +// release without packages is https://github.com/cloudfoundry/bosh-dns-aliases-release. func hasRequiredCompiledPackages(d boshdir.Director, releaseSlug boshdir.ReleaseSlug, stemcell boshdir.OSVersionSlug) (bool, error) { release, err := d.FindRelease(releaseSlug) if err != nil { From 86ecee76b8795d08f799a5ca8a391a0ba6577644 Mon Sep 17 00:00:00 2001 From: Christopher Hunter Date: Wed, 18 May 2022 12:00:24 -0700 Subject: [PATCH 3/5] logs (cach-compiled-releases): add log line for when no packages exist --- internal/commands/cache_compiled_releases.go | 1 + internal/commands/cache_compiled_releases_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/internal/commands/cache_compiled_releases.go b/internal/commands/cache_compiled_releases.go index 8523243e5..a86c112a9 100644 --- a/internal/commands/cache_compiled_releases.go +++ b/internal/commands/cache_compiled_releases.go @@ -192,6 +192,7 @@ func (cmd CacheCompiledReleases) Execute(args []string) error { if !errors.Is(err, errNoPackages) { return fmt.Errorf("failed to find release %s: %w", requirement.ReleaseSlug(), err) } + cmd.Logger.Printf("%s does not have any packages\n", rel) } else if !hasRelease { return fmt.Errorf("%[1]s compiled with %[2]s is not found on bosh director (it might have been uploaded as a compiled release and the director can't recompile it for the compilation target %[2]s)", requirement.ReleaseSlug(), requirement.OSVersionSlug()) } diff --git a/internal/commands/cache_compiled_releases_test.go b/internal/commands/cache_compiled_releases_test.go index 11e7f9db0..62c222c14 100644 --- a/internal/commands/cache_compiled_releases_test.go +++ b/internal/commands/cache_compiled_releases_test.go @@ -377,6 +377,7 @@ func TestCacheCompiledReleases_Execute_when_a_release_is_not_compiled_with_the_c please.Expect(output.String()).To(Ω.ContainSubstring("1 release needs to be exported and cached")) please.Expect(output.String()).To(Ω.ContainSubstring("banana 2.0.0 compiled with alpine 8.0.0 not found in cache")) please.Expect(output.String()).To(Ω.ContainSubstring("exporting from bosh deployment cf-some-id")) + please.Expect(output.String()).To(Ω.ContainSubstring("banana 2.0.0 does not have any packages")) please.Expect(output.String()).NotTo(Ω.ContainSubstring("exporting lemon")) please.Expect(output.String()).NotTo(Ω.ContainSubstring("DON'T FORGET TO MAKE A COMMIT AND PR")) From 1363efc2499cf8cfa5b0b1ba012c10e1a3088646 Mon Sep 17 00:00:00 2001 From: Christopher Hunter Date: Wed, 18 May 2022 13:11:00 -0700 Subject: [PATCH 4/5] refactor: use bosh datastructures for name/version tuples --- internal/commands/cache_compiled_releases.go | 56 +++++++++++-------- .../commands/cache_compiled_releases_test.go | 16 +++--- pkg/cargo/kilnfile.go | 4 ++ 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/internal/commands/cache_compiled_releases.go b/internal/commands/cache_compiled_releases.go index a86c112a9..07d0b0ab8 100644 --- a/internal/commands/cache_compiled_releases.go +++ b/internal/commands/cache_compiled_releases.go @@ -130,7 +130,12 @@ func (cmd CacheCompiledReleases) Execute(args []string) error { if !component.IsErrNotFound(err) { return fmt.Errorf("failed check for matched release: %w", err) } - releasesToExport = append(releasesToExport, rel) + releasesToExport = append(releasesToExport, component.Lock{ + Name: rel.Name, + Version: rel.Version, + StemcellOS: lock.Stemcell.OS, + StemcellVersion: lock.Stemcell.Version, + }) continue } @@ -138,7 +143,7 @@ func (cmd CacheCompiledReleases) Execute(args []string) error { sum, err := cmd.downloadAndComputeSHA(releaseStore, remote) if err != nil { - cmd.Logger.Printf("unable to get hash sum for %s/%s", remote.Name, remote.Version) + cmd.Logger.Printf("unable to get hash sum for %s", remote.ReleaseSlug()) continue } remote.SHA1 = sum @@ -160,7 +165,7 @@ func (cmd CacheCompiledReleases) Execute(args []string) error { } for _, rel := range releasesToExport { - cmd.Logger.Printf("\t%s %s compiled with %s %s not found in cache\n", rel.Name, rel.Version, lock.Stemcell.OS, lock.Stemcell.Version) + cmd.Logger.Printf("\t%s compiled with %s not found in cache\n", rel.ReleaseSlug(), rel.StemcellSlug()) } bosh, err := cmd.Director(cmd.Options.ClientConfiguration, omAPI) @@ -181,25 +186,23 @@ func (cmd CacheCompiledReleases) Execute(args []string) error { } for _, rel := range releasesToExport { - requirement := component.Spec{ - Name: rel.Name, - Version: rel.Version, - StemcellOS: stagedStemcellOS, - StemcellVersion: stagedStemcellVersion, - } + releaseSlug := rel.ReleaseSlug() + stemcellSlug := boshdir.NewOSVersionSlug(stagedStemcellOS, stagedStemcellVersion) - if hasRelease, err := hasRequiredCompiledPackages(bosh, rel.ReleaseSlug(), requirement.OSVersionSlug()); err != nil { + hasRelease, err := hasRequiredCompiledPackages(bosh, rel.ReleaseSlug(), stemcellSlug) + if err != nil { if !errors.Is(err, errNoPackages) { - return fmt.Errorf("failed to find release %s: %w", requirement.ReleaseSlug(), err) + return fmt.Errorf("failed to find release %s: %w", rel.ReleaseSlug(), err) } cmd.Logger.Printf("%s does not have any packages\n", rel) - } else if !hasRelease { - return fmt.Errorf("%[1]s compiled with %[2]s is not found on bosh director (it might have been uploaded as a compiled release and the director can't recompile it for the compilation target %[2]s)", requirement.ReleaseSlug(), requirement.OSVersionSlug()) + } + if !hasRelease { + return fmt.Errorf("%[1]s compiled with %[2]s is not found on bosh director (it might have been uploaded as a compiled release and the director can't recompile it for the compilation target %[2]s)", releaseSlug, stemcellSlug) } - newRemote, err := cmd.cacheRelease(bosh, releaseStore, deployment, requirement) + newRemote, err := cmd.cacheRelease(bosh, releaseStore, deployment, releaseSlug, stemcellSlug) if err != nil { - cmd.Logger.Printf("\tfailed to cache release %s for %s: %s\n", requirement.ReleaseSlug(), requirement.OSVersionSlug(), err) + cmd.Logger.Printf("\tfailed to cache release %s for %s: %s\n", releaseSlug, stemcellSlug, err) continue } @@ -286,21 +289,26 @@ func (cmd CacheCompiledReleases) fetchProductDeploymentData() (_ OpsManagerRelea return omAPI, manifest.Name, stagedStemcell.OS, stagedStemcell.Version, nil } -func (cmd CacheCompiledReleases) cacheRelease(bosh boshdir.Director, rc ReleaseStorage, deployment boshdir.Deployment, req component.Spec) (component.Lock, error) { - cmd.Logger.Printf("\texporting %s\n", req.ReleaseSlug()) - result, err := deployment.ExportRelease(req.ReleaseSlug(), req.OSVersionSlug(), nil) +func (cmd CacheCompiledReleases) cacheRelease(bosh boshdir.Director, rc ReleaseStorage, deployment boshdir.Deployment, releaseSlug boshdir.ReleaseSlug, stemcellSlug boshdir.OSVersionSlug) (component.Lock, error) { + cmd.Logger.Printf("\texporting %s\n", releaseSlug) + result, err := deployment.ExportRelease(releaseSlug, stemcellSlug, nil) if err != nil { return component.Lock{}, err } - cmd.Logger.Printf("\tdownloading %s\n", req.ReleaseSlug()) - releaseFilePath, _, sha1sum, err := cmd.saveReleaseLocally(bosh, cmd.Options.ReleasesDir, req, result) + cmd.Logger.Printf("\tdownloading %s\n", releaseSlug) + releaseFilePath, _, sha1sum, err := cmd.saveReleaseLocally(bosh, cmd.Options.ReleasesDir, releaseSlug, stemcellSlug, result) if err != nil { return component.Lock{}, err } - cmd.Logger.Printf("\tuploading %s %s\n", req.Name, req.Version) - remoteRelease, err := cmd.uploadLocalRelease(req, releaseFilePath, rc) + cmd.Logger.Printf("\tuploading %s\n", releaseSlug) + remoteRelease, err := cmd.uploadLocalRelease(cargo.ComponentSpec{ + Name: releaseSlug.Name(), + Version: releaseSlug.Version(), + StemcellOS: stemcellSlug.OS(), + StemcellVersion: stemcellSlug.Version(), + }, releaseFilePath, rc) if err != nil { return component.Lock{}, err } @@ -342,8 +350,8 @@ func (cmd *CacheCompiledReleases) uploadLocalRelease(spec component.Spec, fp str return uploader.UploadRelease(spec, f) } -func (cmd *CacheCompiledReleases) saveReleaseLocally(director boshdir.Director, relDir string, req component.Spec, res boshdir.ExportReleaseResult) (string, string, string, error) { - fileName := fmt.Sprintf("%s-%s-%s-%s.tgz", req.Name, req.Version, req.StemcellOS, req.StemcellVersion) +func (cmd *CacheCompiledReleases) saveReleaseLocally(director boshdir.Director, relDir string, releaseSlug boshdir.ReleaseSlug, stemcellSlug boshdir.OSVersionSlug, res boshdir.ExportReleaseResult) (string, string, string, error) { + fileName := fmt.Sprintf("%s-%s-%s-%s.tgz", releaseSlug.Name(), releaseSlug.Version(), stemcellSlug.OS(), stemcellSlug.Version()) filePath := filepath.Join(relDir, fileName) f, err := cmd.FS.Create(filePath) diff --git a/internal/commands/cache_compiled_releases_test.go b/internal/commands/cache_compiled_releases_test.go index 62c222c14..5c9c51814 100644 --- a/internal/commands/cache_compiled_releases_test.go +++ b/internal/commands/cache_compiled_releases_test.go @@ -243,7 +243,7 @@ func TestCacheCompiledReleases_Execute_when_one_release_is_cached_another_is_alr please.Expect(requestedID).To(Ω.Equal("some-blob-id")) please.Expect(output.String()).To(Ω.ContainSubstring("1 release needs to be exported and cached")) - please.Expect(output.String()).To(Ω.ContainSubstring("lemon 3.0.0 compiled with alpine 9.0.0 not found in cache")) + please.Expect(output.String()).To(Ω.ContainSubstring("lemon/3.0.0 compiled with alpine/9.0.0 not found in cache")) please.Expect(output.String()).To(Ω.ContainSubstring("exporting from bosh deployment cf-some-id")) please.Expect(output.String()).To(Ω.ContainSubstring("exporting lemon")) please.Expect(output.String()).To(Ω.ContainSubstring("downloading lemon")) @@ -310,8 +310,6 @@ func TestCacheCompiledReleases_Execute_when_a_release_is_not_compiled_with_the_c opsManager := new(fakes.OpsManagerReleaseCacheSource) opsManager.GetStagedProductManifestReturns(`{"name": "cf-some-id", "stemcells": [{"os": "alpine", "version": "8.0.0"}]}`, nil) - releaseInBlobstore := []byte(`lemon-release-buffer`) - deployment := new(boshdirFakes.FakeDeployment) bosh := new(boshdirFakes.FakeDirector) bosh.FindDeploymentReturns(deployment, nil) @@ -330,10 +328,6 @@ func TestCacheCompiledReleases_Execute_when_a_release_is_not_compiled_with_the_c } deployment.ExportReleaseReturns(director.ExportReleaseResult{}, nil) - bosh.DownloadResourceUncheckedCalls(func(_ string, writer io.Writer) error { - _, _ = writer.Write(releaseInBlobstore) - return nil - }) releaseStorage := new(fakes.ReleaseStorage) releaseStorage.GetMatchedReleaseCalls(fakeCacheData) @@ -368,6 +362,10 @@ func TestCacheCompiledReleases_Execute_when_a_release_is_not_compiled_with_the_c please.Expect(err).To(Ω.MatchError(Ω.ContainSubstring("not found on bosh director"))) + please.Expect(bosh.DownloadResourceUncheckedCallCount()).To(Ω.Equal(0)) + please.Expect(bosh.HasReleaseCallCount()).To(Ω.Equal(0)) + please.Expect(bosh.FindReleaseCallCount()).To(Ω.Equal(1)) + { requestedReleaseSlug := bosh.FindReleaseArgsForCall(0) please.Expect(requestedReleaseSlug.Name()).To(Ω.Equal("banana")) @@ -375,9 +373,9 @@ func TestCacheCompiledReleases_Execute_when_a_release_is_not_compiled_with_the_c } please.Expect(output.String()).To(Ω.ContainSubstring("1 release needs to be exported and cached")) - please.Expect(output.String()).To(Ω.ContainSubstring("banana 2.0.0 compiled with alpine 8.0.0 not found in cache")) + please.Expect(output.String()).To(Ω.ContainSubstring("banana/2.0.0 compiled with alpine/8.0.0 not found in cache")) please.Expect(output.String()).To(Ω.ContainSubstring("exporting from bosh deployment cf-some-id")) - please.Expect(output.String()).To(Ω.ContainSubstring("banana 2.0.0 does not have any packages")) + please.Expect(output.String()).To(Ω.ContainSubstring("banana/2.0.0 does not have any packages")) please.Expect(output.String()).NotTo(Ω.ContainSubstring("exporting lemon")) please.Expect(output.String()).NotTo(Ω.ContainSubstring("DON'T FORGET TO MAKE A COMMIT AND PR")) diff --git a/pkg/cargo/kilnfile.go b/pkg/cargo/kilnfile.go index 5fd4ac85a..f0d95ce09 100644 --- a/pkg/cargo/kilnfile.go +++ b/pkg/cargo/kilnfile.go @@ -132,6 +132,10 @@ func (lock ComponentLock) ReleaseSlug() boshdir.ReleaseSlug { return boshdir.NewReleaseSlug(lock.Name, lock.Version) } +func (lock ComponentLock) StemcellSlug() boshdir.OSVersionSlug { + return boshdir.NewOSVersionSlug(lock.StemcellOS, lock.StemcellVersion) +} + func (lock ComponentLock) String() string { var b strings.Builder b.WriteString(lock.Name) From 02de8b855c7bc680c419250f013f583793c2d609 Mon Sep 17 00:00:00 2001 From: Christopher Hunter Date: Wed, 18 May 2022 13:51:58 -0700 Subject: [PATCH 5/5] fix (cach-compiled-release): missing test for release without packages --- .../commands/cache_compiled_releases_test.go | 147 ++++++++++++++++-- 1 file changed, 138 insertions(+), 9 deletions(-) diff --git a/internal/commands/cache_compiled_releases_test.go b/internal/commands/cache_compiled_releases_test.go index 5c9c51814..73fa6d9e5 100644 --- a/internal/commands/cache_compiled_releases_test.go +++ b/internal/commands/cache_compiled_releases_test.go @@ -311,9 +311,9 @@ func TestCacheCompiledReleases_Execute_when_a_release_is_not_compiled_with_the_c opsManager.GetStagedProductManifestReturns(`{"name": "cf-some-id", "stemcells": [{"os": "alpine", "version": "8.0.0"}]}`, nil) deployment := new(boshdirFakes.FakeDeployment) + deployment.ExportReleaseReturns(director.ExportReleaseResult{}, nil) bosh := new(boshdirFakes.FakeDirector) bosh.FindDeploymentReturns(deployment, nil) - bosh.FindReleaseStub = func(slug director.ReleaseSlug) (director.Release, error) { switch slug.Name() { default: @@ -327,13 +327,8 @@ func TestCacheCompiledReleases_Execute_when_a_release_is_not_compiled_with_the_c } } - deployment.ExportReleaseReturns(director.ExportReleaseResult{}, nil) - releaseStorage := new(fakes.ReleaseStorage) releaseStorage.GetMatchedReleaseCalls(fakeCacheData) - releaseStorage.UploadReleaseCalls(func(_ component.Spec, reader io.Reader) (component.Lock, error) { - return component.Lock{}, nil - }) var output bytes.Buffer logger := log.New(&output, "", 0) @@ -375,7 +370,6 @@ func TestCacheCompiledReleases_Execute_when_a_release_is_not_compiled_with_the_c please.Expect(output.String()).To(Ω.ContainSubstring("1 release needs to be exported and cached")) please.Expect(output.String()).To(Ω.ContainSubstring("banana/2.0.0 compiled with alpine/8.0.0 not found in cache")) please.Expect(output.String()).To(Ω.ContainSubstring("exporting from bosh deployment cf-some-id")) - please.Expect(output.String()).To(Ω.ContainSubstring("banana/2.0.0 does not have any packages")) please.Expect(output.String()).NotTo(Ω.ContainSubstring("exporting lemon")) please.Expect(output.String()).NotTo(Ω.ContainSubstring("DON'T FORGET TO MAKE A COMMIT AND PR")) @@ -392,8 +386,143 @@ func TestCacheCompiledReleases_Execute_when_a_release_is_not_compiled_with_the_c }), "it should not override the in-correct element in the Kilnfile.lock") } -// this test ensures make it so that we don't have to iterate over all the releases -// before failing due to a stemcell mismatch +// this test covers +// - when a release does not contain packages +func TestCacheCompiledReleases_Execute_when_a_release_has_no_packages(t *testing.T) { + please := Ω.NewWithT(t) + + // setup + + fs := memfs.New() + + please.Expect(fsWriteYAML(fs, "Kilnfile", cargo.Kilnfile{ + ReleaseSources: []cargo.ReleaseSourceConfig{ + { + ID: "cached-compiled-releases", + Publishable: true, + PathTemplate: "{{.Release}}-{{.Version}}.tgz", + }, + { + ID: "new-releases", + Publishable: false, + PathTemplate: "{{.Release}}-{{.Version}}.tgz", + }, + }, + Releases: []cargo.ComponentSpec{ + { + Name: "banana", + }, + }, + })).NotTo(Ω.HaveOccurred()) + please.Expect(fsWriteYAML(fs, "Kilnfile.lock", cargo.KilnfileLock{ + Releases: []cargo.ComponentLock{ + { + Name: "banana", + Version: "2.0.0", + + RemoteSource: "cached-compiled-releases", + RemotePath: "banana-2.0.0-alpine-5.5.5", + + SHA1: "fake-checksum", + }, + }, + Stemcell: cargo.Stemcell{ + OS: "alpine", + Version: "8.0.0", + }, + })).NotTo(Ω.HaveOccurred()) + + opsManager := new(fakes.OpsManagerReleaseCacheSource) + opsManager.GetStagedProductManifestReturns(`{"name": "cf-some-id", "stemcells": [{"os": "alpine", "version": "8.0.0"}]}`, nil) + + deployment := new(boshdirFakes.FakeDeployment) + deployment.ExportReleaseReturns(director.ExportReleaseResult{SHA1: "sha256:7dd4f2f077e449b47215359e8020c0b6c81e184d2c614486246cb8f70cac7a70"}, nil) + bosh := new(boshdirFakes.FakeDirector) + bosh.DownloadResourceUncheckedCalls(func(_ string, writer io.Writer) error { + _, _ = writer.Write([]byte("greetings")) + return nil + }) + bosh.FindDeploymentReturns(deployment, nil) + bosh.FindReleaseStub = func(slug director.ReleaseSlug) (director.Release, error) { + switch slug.Name() { + default: + panic(fmt.Errorf("FindReleaseStub input not handled: %#v", slug)) + case "banana": + return &boshdirFakes.FakeRelease{ + PackagesStub: func() ([]director.Package, error) { + return make([]director.Package, 0), nil + }, + }, nil + } + } + + releaseStorage := new(fakes.ReleaseStorage) + releaseStorage.GetMatchedReleaseCalls(fakeCacheData) + releaseStorage.UploadReleaseStub = func(spec cargo.ComponentSpec, reader io.Reader) (cargo.ComponentLock, error) { + l := spec.Lock() + l.RemotePath = "BANANA.tgz" + l.RemoteSource = "BASKET" + return l, nil + } + + var output bytes.Buffer + logger := log.New(&output, "", 0) + + cmd := commands.CacheCompiledReleases{ + FS: fs, + Logger: logger, + ReleaseSourceAndCache: func(kilnfile cargo.Kilnfile, targetID string) (commands.ReleaseStorage, error) { + return releaseStorage, nil + }, + OpsManager: func(configuration om.ClientConfiguration) (commands.OpsManagerReleaseCacheSource, error) { + return opsManager, nil + }, + Director: func(configuration om.ClientConfiguration, provider om.GetBoshEnvironmentAndSecurityRootCACertificateProvider) (director.Director, error) { + return bosh, nil + }, + } + + // run + + err := cmd.Execute([]string{ + "--upload-target-id", "cached-compiled-releases", + }) + + // check + + please.Expect(bosh.DownloadResourceUncheckedCallCount()).To(Ω.Equal(1)) + please.Expect(bosh.HasReleaseCallCount()).To(Ω.Equal(0)) + please.Expect(bosh.FindReleaseCallCount()).To(Ω.Equal(1)) + + { + requestedReleaseSlug := bosh.FindReleaseArgsForCall(0) + please.Expect(requestedReleaseSlug.Name()).To(Ω.Equal("banana")) + please.Expect(requestedReleaseSlug.Version()).To(Ω.Equal("2.0.0")) + } + + please.Expect(output.String()).To(Ω.ContainSubstring("1 release needs to be exported and cached")) + please.Expect(output.String()).To(Ω.ContainSubstring("banana/2.0.0 compiled with alpine/8.0.0 not found in cache")) + please.Expect(output.String()).To(Ω.ContainSubstring("exporting from bosh deployment cf-some-id")) + please.Expect(output.String()).To(Ω.ContainSubstring("oes not have any packages")) + please.Expect(output.String()).To(Ω.ContainSubstring("exporting banana")) + + var updatedKilnfile cargo.KilnfileLock + please.Expect(fsReadYAML(fs, "Kilnfile.lock", &updatedKilnfile)).NotTo(Ω.HaveOccurred()) + please.Expect(updatedKilnfile.Releases).To(Ω.ContainElement(component.Lock{ + Name: "banana", + Version: "2.0.0", + + RemoteSource: "BASKET", + RemotePath: "BANANA.tgz", + + SHA1: "fake-checksum", + }), "it should not override the in-correct element in the Kilnfile.lock") + + please.Expect(err).NotTo(Ω.HaveOccurred()) + + please.Expect(output.String()).To(Ω.ContainSubstring("DON'T FORGET TO MAKE A COMMIT AND PR")) +} + func TestCacheCompiledReleases_Execute_staged_and_lock_stemcells_are_not_the_same(t *testing.T) { please := Ω.NewWithT(t)