diff --git a/acceptance/exporter_test.go b/acceptance/exporter_test.go index ba57caac8..1d4234eb8 100644 --- a/acceptance/exporter_test.go +++ b/acceptance/exporter_test.go @@ -314,8 +314,11 @@ func testExporterFunc(platformAPI string) func(t *testing.T, when spec.G, it spe ) h.AssertStringContains(t, output, "Saving "+exportedImageName) + // To detect whether the export of cacheImage and exportedImage is successful h.Run(t, exec.Command("docker", "pull", exportedImageName)) assertImageOSAndArchAndCreatedAt(t, exportedImageName, exportTest, imgutil.NormalizedDateTime) + + h.Run(t, exec.Command("docker", "pull", cacheImageName)) }) it("is created with empty layer", func() { diff --git a/cmd/lifecycle/exporter.go b/cmd/lifecycle/exporter.go index f45d2be8c..847be1b46 100644 --- a/cmd/lifecycle/exporter.go +++ b/cmd/lifecycle/exporter.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "strconv" + "sync" "time" "github.com/BurntSushi/toml" @@ -205,31 +206,47 @@ func (e *exportCmd) export(group buildpack.Group, cacheStore lifecycle.Cache, an return err } - report, err := exporter.Export(lifecycle.ExportOptions{ - AdditionalNames: e.AdditionalTags, - AppDir: e.AppDir, - DefaultProcessType: e.DefaultProcessType, - ExtendedDir: e.ExtendedDir, - LauncherConfig: launcherConfig(e.LauncherPath, e.LauncherSBOMDir), - LayersDir: e.LayersDir, - OrigMetadata: analyzedMD.LayersMetadata, - Project: projectMD, - RunImageRef: runImageID, - RunImageForExport: runImageForExport, - WorkingImage: appImage, - }) - if err != nil { - return cmd.FailErrCode(err, e.CodeFor(platform.ExportError), "export") + var exportWaitGroup sync.WaitGroup + var report files.Report + var appErr error + appErr = nil + + exportWaitGroup.Add(1) + go func() { + defer exportWaitGroup.Done() + report, appErr = exporter.Export(lifecycle.ExportOptions{ + AdditionalNames: e.AdditionalTags, + AppDir: e.AppDir, + DefaultProcessType: e.DefaultProcessType, + ExtendedDir: e.ExtendedDir, + LauncherConfig: launcherConfig(e.LauncherPath, e.LauncherSBOMDir), + LayersDir: e.LayersDir, + OrigMetadata: analyzedMD.LayersMetadata, + Project: projectMD, + RunImageRef: runImageID, + RunImageForExport: runImageForExport, + WorkingImage: appImage, + }) + }() + + exportWaitGroup.Add(1) + go func() { + defer exportWaitGroup.Done() + if cacheStore != nil { + if cacheErr := exporter.Cache(e.LayersDir, cacheStore); cacheErr != nil { + cmd.DefaultLogger.Warnf("Failed to export cache: %v\n", cacheErr) + } + } + }() + + exportWaitGroup.Wait() + if appErr != nil { + return cmd.FailErrCode(appErr, e.CodeFor(platform.ExportError), "export") } if err = encoding.WriteTOML(e.ReportPath, &report); err != nil { return cmd.FailErrCode(err, e.CodeFor(platform.ExportError), "write export report") } - if cacheStore != nil { - if cacheErr := exporter.Cache(e.LayersDir, cacheStore); cacheErr != nil { - cmd.DefaultLogger.Warnf("Failed to export cache: %v\n", cacheErr) - } - } return nil } diff --git a/layers/factory.go b/layers/factory.go index d73922518..4d97162e0 100644 --- a/layers/factory.go +++ b/layers/factory.go @@ -4,6 +4,7 @@ import ( "os" "path/filepath" "strings" + "sync" v1 "github.com/google/go-containerregistry/pkg/v1" @@ -27,7 +28,8 @@ type Factory struct { UID, GID int // UID and GID are used to normalize layer entries Logger log.Logger - tarHashes map[string]string // tarHases Stores hashes of layer tarballs for reuse between the export and cache steps. + // tarHashes stores hashes of layer tarballs for reuse between the export and cache steps. + tarHashes sync.Map } type Layer struct { @@ -39,15 +41,13 @@ type Layer struct { func (f *Factory) writeLayer(id, createdBy string, addEntries func(tw *archive.NormalizingTarWriter) error) (layer Layer, err error) { tarPath := filepath.Join(f.ArtifactsDir, escape(id)+".tar") - if f.tarHashes == nil { - f.tarHashes = make(map[string]string) - } - if sha, ok := f.tarHashes[tarPath]; ok { - f.Logger.Debugf("Reusing tarball for layer %q with SHA: %s\n", id, sha) + if sha, ok := f.tarHashes.Load(tarPath); ok { + shaString := sha.(string) + f.Logger.Debugf("Reusing tarball for layer %q with SHA: %s\n", id, shaString) return Layer{ ID: id, TarPath: tarPath, - Digest: sha, + Digest: shaString, History: v1.History{CreatedBy: createdBy}, }, nil } @@ -69,7 +69,7 @@ func (f *Factory) writeLayer(id, createdBy string, addEntries func(tw *archive.N return Layer{}, err } digest := lw.Digest() - f.tarHashes[tarPath] = digest + f.tarHashes.Store(tarPath, digest) return Layer{ ID: id, Digest: digest,