From 7253994beee4733fa7afef27995a28b4bd57248c Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Fri, 22 Sep 2023 16:45:41 -0400 Subject: [PATCH] Units pass Signed-off-by: Natalie Arellano --- cmd/lifecycle/analyzer.go | 4 +- cmd/lifecycle/creator.go | 8 +- cmd/lifecycle/detector.go | 25 ++---- cmd/lifecycle/extender.go | 12 +-- lifecycle/analyzer.go | 102 +++------------------ lifecycle/analyzer_test.go | 128 +++++++++++++++++++-------- lifecycle/connected_factory.go | 87 ++++++++++++++++++ lifecycle/detector.go | 68 +++----------- lifecycle/detector_test.go | 46 ++++++---- lifecycle/extender.go | 89 +++++-------------- lifecycle/extender_test.go | 37 ++++---- lifecycle/generator.go | 78 ++++------------ lifecycle/generator_test.go | 40 +++++---- lifecycle/handlers.go | 10 +++ lifecycle/hermetic_factory.go | 88 ++++++++++++++++++ lifecycle/testmock/config_handler.go | 15 ++++ 16 files changed, 431 insertions(+), 406 deletions(-) create mode 100644 lifecycle/connected_factory.go create mode 100644 lifecycle/hermetic_factory.go diff --git a/cmd/lifecycle/analyzer.go b/cmd/lifecycle/analyzer.go index 3269c8f70..d1138d1aa 100644 --- a/cmd/lifecycle/analyzer.go +++ b/cmd/lifecycle/analyzer.go @@ -96,7 +96,7 @@ func (a *analyzeCmd) Privileges() error { // Exec executes the command. func (a *analyzeCmd) Exec() error { - factory := lifecycle.NewAnalyzerFactory( + factory := lifecycle.NewConnectedFactory( a.PlatformAPI, &cmd.BuildpackAPIVerifier{}, NewCacheHandler(a.keychain), @@ -104,7 +104,7 @@ func (a *analyzeCmd) Exec() error { image.NewHandler(a.docker, a.keychain, a.LayoutDir, a.UseLayout, a.InsecureRegistries), image.NewRegistryHandler(a.keychain, a.InsecureRegistries), ) - analyzer, err := factory.NewAnalyzer(a.AdditionalTags, a.CacheImageRef, a.LaunchCacheDir, a.LayersDir, a.OutputImageRef, a.PreviousImageRef, a.RunImageRef, a.SkipLayers, cmd.DefaultLogger) + analyzer, err := factory.NewAnalyzer(*a.LifecycleInputs, cmd.DefaultLogger) if err != nil { return unwrapErrorFailWithMessage(err, "initialize analyzer") } diff --git a/cmd/lifecycle/creator.go b/cmd/lifecycle/creator.go index e4f9cf060..fadf67a88 100644 --- a/cmd/lifecycle/creator.go +++ b/cmd/lifecycle/creator.go @@ -123,7 +123,7 @@ func (c *createCmd) Exec() error { plan files.Plan ) cmd.DefaultLogger.Phase("ANALYZING") - analyzerFactory := lifecycle.NewAnalyzerFactory( + analyzerFactory := lifecycle.NewConnectedFactory( c.PlatformAPI, &cmd.BuildpackAPIVerifier{}, NewCacheHandler(c.keychain), @@ -131,7 +131,7 @@ func (c *createCmd) Exec() error { image.NewHandler(c.docker, c.keychain, c.LayoutDir, c.UseLayout, c.InsecureRegistries), image.NewRegistryHandler(c.keychain, c.InsecureRegistries), ) - analyzer, err := analyzerFactory.NewAnalyzer(c.AdditionalTags, c.CacheImageRef, c.LaunchCacheDir, c.LayersDir, c.OutputImageRef, c.PreviousImageRef, c.RunImageRef, c.SkipLayers, cmd.DefaultLogger) + analyzer, err := analyzerFactory.NewAnalyzer(*c.LifecycleInputs, cmd.DefaultLogger) if err != nil { return unwrapErrorFailWithMessage(err, "initialize analyzer") } @@ -142,13 +142,13 @@ func (c *createCmd) Exec() error { // Detect cmd.DefaultLogger.Phase("DETECTING") - detectorFactory := lifecycle.NewDetectorFactory( + detectorFactory := lifecycle.NewHermeticFactory( c.PlatformAPI, &cmd.BuildpackAPIVerifier{}, lifecycle.NewConfigHandler(), dirStore, ) - detector, err := detectorFactory.NewDetector(analyzedMD, c.AppDir, c.BuildConfigDir, c.OrderPath, c.PlatformDir, cmd.DefaultLogger) + detector, err := detectorFactory.NewDetector(*c.LifecycleInputs, cmd.DefaultLogger) if err != nil { return unwrapErrorFailWithMessage(err, "initialize detector") } diff --git a/cmd/lifecycle/detector.go b/cmd/lifecycle/detector.go index 76b0b68f6..d2aba450d 100644 --- a/cmd/lifecycle/detector.go +++ b/cmd/lifecycle/detector.go @@ -60,22 +60,14 @@ func (d *detectCmd) Privileges() error { func (d *detectCmd) Exec() error { dirStore := platform.NewDirStore(d.BuildpacksDir, d.ExtensionsDir) - detectorFactory := lifecycle.NewDetectorFactory( + detectorFactory := lifecycle.NewHermeticFactory( d.PlatformAPI, &cmd.BuildpackAPIVerifier{}, lifecycle.NewConfigHandler(), dirStore, ) - amd, err := files.ReadAnalyzed(d.AnalyzedPath, cmd.DefaultLogger) - if err != nil { - return unwrapErrorFailWithMessage(err, "reading analyzed.toml") - } detector, err := detectorFactory.NewDetector( - amd, - d.AppDir, - d.BuildConfigDir, - d.OrderPath, - d.PlatformDir, + *d.LifecycleInputs, cmd.DefaultLogger, ) if err != nil { @@ -91,22 +83,15 @@ func (d *detectCmd) Exec() error { return err // pass through error } if group.HasExtensions() { - generatorFactory := lifecycle.NewGeneratorFactory( + generatorFactory := lifecycle.NewHermeticFactory( + d.PlatformAPI, &cmd.BuildpackAPIVerifier{}, lifecycle.Config, dirStore, ) var generator *lifecycle.Generator generator, err = generatorFactory.NewGenerator( - d.AnalyzedPath, - d.AppDir, - d.BuildConfigDir, - group.GroupExtensions, - d.GeneratedDir, - plan, - d.PlatformAPI, - d.PlatformDir, - d.RunPath, + *d.LifecycleInputs, cmd.Stdout, cmd.Stderr, cmd.DefaultLogger, ) diff --git a/cmd/lifecycle/extender.go b/cmd/lifecycle/extender.go index 7dbc82297..f56d23f8d 100644 --- a/cmd/lifecycle/extender.go +++ b/cmd/lifecycle/extender.go @@ -52,22 +52,14 @@ func (e *extendCmd) Privileges() error { } func (e *extendCmd) Exec() error { - extenderFactory := lifecycle.NewExtenderFactory(&cmd.BuildpackAPIVerifier{}, lifecycle.NewConfigHandler()) + extenderFactory := lifecycle.NewHermeticFactory(e.PlatformAPI, &cmd.BuildpackAPIVerifier{}, lifecycle.NewConfigHandler(), nil) // TODO: fix applier, err := kaniko.NewDockerfileApplier() if err != nil { return err } extender, err := extenderFactory.NewExtender( - e.AnalyzedPath, - e.AppDir, - e.ExtendedDir, - e.GeneratedDir, - e.GroupPath, - e.LayersDir, - e.PlatformDir, - e.KanikoCacheTTL, + *e.LifecycleInputs, applier, - e.ExtendKind, cmd.DefaultLogger, ) if err != nil { diff --git a/lifecycle/analyzer.go b/lifecycle/analyzer.go index 1321c85b4..c3317af38 100644 --- a/lifecycle/analyzer.go +++ b/lifecycle/analyzer.go @@ -1,3 +1,4 @@ +// Package lifecycle TODO package lifecycle import ( @@ -5,7 +6,6 @@ import ( "github.com/pkg/errors" "github.com/buildpacks/lifecycle/api" - "github.com/buildpacks/lifecycle/cache" "github.com/buildpacks/lifecycle/image" "github.com/buildpacks/lifecycle/internal/fsutil" "github.com/buildpacks/lifecycle/internal/layer" @@ -14,33 +14,7 @@ import ( "github.com/buildpacks/lifecycle/platform/files" ) -type AnalyzerFactory struct { - platformAPI *api.Version - apiVerifier BuildpackAPIVerifier - cacheHandler CacheHandler - configHandler ConfigHandler - imageHandler image.Handler - registryHandler image.RegistryHandler -} - -func NewAnalyzerFactory( - platformAPI *api.Version, - apiVerifier BuildpackAPIVerifier, - cacheHandler CacheHandler, - configHandler ConfigHandler, - imageHandler image.Handler, - registryHandler image.RegistryHandler, -) *AnalyzerFactory { - return &AnalyzerFactory{ - platformAPI: platformAPI, - apiVerifier: apiVerifier, - cacheHandler: cacheHandler, - configHandler: configHandler, - imageHandler: imageHandler, - registryHandler: registryHandler, - } -} - +// Analyzer TODO type Analyzer struct { PreviousImage imgutil.Image RunImage imgutil.Image @@ -50,90 +24,34 @@ type Analyzer struct { } // NewAnalyzer configures a new Analyzer according to the provided Platform API version. -func (f *AnalyzerFactory) NewAnalyzer(additionalTags []string, cacheImageRef string, launchCacheDir string, layersDir string, outputImageRef string, previousImageRef string, runImageRef string, skipLayers bool, logger log.Logger) (*Analyzer, error) { +func (f *ConnectedFactory) NewAnalyzer(inputs platform.LifecycleInputs, logger log.Logger) (*Analyzer, error) { analyzer := &Analyzer{ Logger: logger, SBOMRestorer: &layer.NopSBOMRestorer{}, PlatformAPI: f.platformAPI, } - if err := f.ensureRegistryAccess(additionalTags, cacheImageRef, outputImageRef, runImageRef, previousImageRef); err != nil { + if err := f.ensureRegistryAccess(inputs); err != nil { return nil, err } - if f.platformAPI.AtLeast("0.8") && !skipLayers { - analyzer.SBOMRestorer = &layer.DefaultSBOMRestorer{ // FIXME: eventually layer.NewSBOMRestorer should always return the default one, and then we can use the constructor - LayersDir: layersDir, + if f.platformAPI.AtLeast("0.8") && !inputs.SkipLayers { + analyzer.SBOMRestorer = &layer.DefaultSBOMRestorer{ + LayersDir: inputs.LayersDir, Logger: logger, } } - if err := f.setPrevious(analyzer, previousImageRef, launchCacheDir); err != nil { + var err error + if analyzer.PreviousImage, err = f.getPreviousImage(inputs.PreviousImageRef, inputs.LaunchCacheDir); err != nil { return nil, err } - if err := f.setRun(analyzer, runImageRef); err != nil { + if analyzer.RunImage, err = f.getRunImage(inputs.RunImageRef); err != nil { return nil, err } return analyzer, nil } -func (f *AnalyzerFactory) ensureRegistryAccess( - additionalTags []string, - cacheImageRef string, - outputImageRef string, - runImageRef string, - previousImageRef string, -) error { - var readImages, writeImages []string - writeImages = append(writeImages, cacheImageRef) - if f.imageHandler.Kind() == image.RemoteKind { - readImages = append(readImages, previousImageRef, runImageRef) - writeImages = append(writeImages, outputImageRef) - writeImages = append(writeImages, additionalTags...) - } - - if err := f.registryHandler.EnsureReadAccess(readImages...); err != nil { - return errors.Wrap(err, "validating registry read access") - } - if err := f.registryHandler.EnsureWriteAccess(writeImages...); err != nil { - return errors.Wrap(err, "validating registry write access") - } - return nil -} - -func (f *AnalyzerFactory) setPrevious(analyzer *Analyzer, imageRef string, launchCacheDir string) error { - if imageRef == "" { - return nil - } - var err error - analyzer.PreviousImage, err = f.imageHandler.InitImage(imageRef) - if err != nil { - return errors.Wrap(err, "getting previous image") - } - if launchCacheDir == "" || f.imageHandler.Kind() != image.LocalKind { - return nil - } - - volumeCache, err := cache.NewVolumeCache(launchCacheDir) - if err != nil { - return errors.Wrap(err, "creating launch cache") - } - analyzer.PreviousImage = cache.NewCachingImage(analyzer.PreviousImage, volumeCache) - return nil -} - -func (f *AnalyzerFactory) setRun(analyzer *Analyzer, imageRef string) error { - if imageRef == "" { - return nil - } - var err error - analyzer.RunImage, err = f.imageHandler.InitImage(imageRef) - if err != nil { - return errors.Wrap(err, "getting run image") - } - return nil -} - // Analyze fetches the layers metadata from the previous image and writes analyzed.toml. func (a *Analyzer) Analyze() (files.Analyzed, error) { defer log.NewMeasurement("Analyzer", a.Logger)() diff --git a/lifecycle/analyzer_test.go b/lifecycle/analyzer_test.go index e151f35d8..91d199502 100644 --- a/lifecycle/analyzer_test.go +++ b/lifecycle/analyzer_test.go @@ -29,15 +29,15 @@ import ( func TestAnalyzer(t *testing.T) { spec.Run(t, "unit-new-analyzer/", testAnalyzerFactory, spec.Parallel(), spec.Report(report.Terminal{})) - for _, api := range api.Platform.Supported { - spec.Run(t, "unit-analyzer/"+api.String(), testAnalyzer(api.String()), spec.Parallel(), spec.Report(report.Terminal{})) + for _, platformAPI := range api.Platform.Supported { + spec.Run(t, "unit-analyzer/"+platformAPI.String(), testAnalyzer(platformAPI.String()), spec.Parallel(), spec.Report(report.Terminal{})) } } func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) { when("#NewAnalyzer", func() { var ( - analyzerFactory *lifecycle.AnalyzerFactory + analyzerFactory *lifecycle.ConnectedFactory fakeAPIVerifier *testmock.MockBuildpackAPIVerifier fakeCacheHandler *testmock.MockCacheHandler fakeConfigHandler *testmock.MockConfigHandler @@ -63,12 +63,12 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) { it.After(func() { mockController.Finish() - os.RemoveAll(tempDir) + _ = os.RemoveAll(tempDir) }) when("platform api >= 0.8", func() { it.Before(func() { - analyzerFactory = lifecycle.NewAnalyzerFactory( + analyzerFactory = lifecycle.NewConnectedFactory( api.Platform.Latest(), fakeAPIVerifier, fakeCacheHandler, @@ -98,7 +98,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) { t.Log("processes run image") fakeImageHandler.EXPECT().InitImage("some-run-image-ref").Return(runImage, nil) - analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", "some-launch-cache-dir", "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger) + analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{ + AdditionalTags: []string{"some-additional-tag"}, + CacheImageRef: "some-cache-image-ref", + LaunchCacheDir: "some-launch-cache-dir", + LayersDir: "some-layers-dir", + OutputImageRef: "some-output-image-ref", + PreviousImageRef: "some-previous-image-ref", + RunImageRef: "some-run-image-ref", + SkipLayers: false, + }, logger) h.AssertNil(t, err) h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name()) h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name()) @@ -131,7 +140,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) { t.Log("processes run image") fakeImageHandler.EXPECT().InitImage("some-run-image-ref").Return(runImage, nil) - analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", "some-launch-cache-dir", "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger) + analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{ + AdditionalTags: []string{"some-additional-tag"}, + CacheImageRef: "some-cache-image-ref", + LaunchCacheDir: "some-launch-cache-dir", + LayersDir: "some-layers-dir", + OutputImageRef: "some-output-image-ref", + PreviousImageRef: "some-previous-image-ref", + RunImageRef: "some-run-image-ref", + SkipLayers: false, + }, logger) h.AssertNil(t, err) h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name()) h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name()) @@ -165,7 +183,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) { launchCacheDir := filepath.Join(tempDir, "some-launch-cache-dir") h.AssertNil(t, os.MkdirAll(launchCacheDir, 0777)) - analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", launchCacheDir, "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger) + analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{ + AdditionalTags: []string{"some-additional-tag"}, + CacheImageRef: "some-cache-image-ref", + LaunchCacheDir: launchCacheDir, + LayersDir: "some-layers-dir", + OutputImageRef: "some-output-image-ref", + PreviousImageRef: "some-previous-image-ref", + RunImageRef: "some-run-image-ref", + SkipLayers: false, + }, logger) h.AssertNil(t, err) h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name()) h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name()) @@ -186,7 +213,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) { fakeImageHandler.EXPECT().InitImage(gomock.Any()) fakeImageHandler.EXPECT().InitImage(gomock.Any()) - analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", "some-launch-cache-dir", "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", true, logger) + analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{ + AdditionalTags: []string{"some-additional-tag"}, + CacheImageRef: "some-cache-image-ref", + LaunchCacheDir: "some-launch-cache-dir", + LayersDir: "some-layers-dir", + OutputImageRef: "some-output-image-ref", + PreviousImageRef: "some-previous-image-ref", + RunImageRef: "some-run-image-ref", + SkipLayers: true, + }, logger) h.AssertNil(t, err) _, ok := analyzer.SBOMRestorer.(*layer.NopSBOMRestorer) @@ -197,7 +233,7 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) { when("platform api = 0.7", func() { it.Before(func() { - analyzerFactory = lifecycle.NewAnalyzerFactory( + analyzerFactory = lifecycle.NewConnectedFactory( api.MustParse("0.7"), fakeAPIVerifier, fakeCacheHandler, @@ -222,7 +258,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) { t.Log("processes run image") fakeImageHandler.EXPECT().InitImage("some-run-image-ref").Return(runImage, nil) - analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", "some-launch-cache-dir", "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger) + analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{ + AdditionalTags: []string{"some-additional-tag"}, + CacheImageRef: "some-cache-image-ref", + LaunchCacheDir: "some-launch-cache-dir", + LayersDir: "some-layers-dir", + OutputImageRef: "some-output-image-ref", + PreviousImageRef: "some-previous-image-ref", + RunImageRef: "some-run-image-ref", + SkipLayers: true, + }, logger) h.AssertNil(t, err) h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name()) h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name()) @@ -253,7 +298,16 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) { launchCacheDir := filepath.Join(tempDir, "some-launch-cache-dir") h.AssertNil(t, os.MkdirAll(launchCacheDir, 0777)) - analyzer, err := analyzerFactory.NewAnalyzer([]string{"some-additional-tag"}, "some-cache-image-ref", launchCacheDir, "some-layers-dir", "some-output-image-ref", "some-previous-image-ref", "some-run-image-ref", false, logger) + analyzer, err := analyzerFactory.NewAnalyzer(platform.LifecycleInputs{ + AdditionalTags: []string{"some-additional-tag"}, + CacheImageRef: "some-cache-image-ref", + LaunchCacheDir: launchCacheDir, + LayersDir: "some-layers-dir", + OutputImageRef: "some-output-image-ref", + PreviousImageRef: "some-previous-image-ref", + RunImageRef: "some-run-image-ref", + SkipLayers: true, + }, logger) h.AssertNil(t, err) h.AssertEq(t, analyzer.PreviousImage.Name(), previousImage.Name()) h.AssertEq(t, analyzer.RunImage.Name(), runImage.Name()) @@ -266,14 +320,14 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) { func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) { return func(t *testing.T, when spec.G, it spec.S) { var ( - cacheDir string - layersDir string - tmpDir string - analyzer *lifecycle.Analyzer - image *fakes.Image - mockCtrl *gomock.Controller - sbomRestorer *testmock.MockSBOMRestorer - testCache lifecycle.Cache + cacheDir string + layersDir string + tmpDir string + analyzer *lifecycle.Analyzer + previousImage *fakes.Image + mockCtrl *gomock.Controller + sbomRestorer *testmock.MockSBOMRestorer + testCache lifecycle.Cache ) it.Before(func() { @@ -291,7 +345,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) testCache, err = cache.NewVolumeCache(cacheDir) h.AssertNil(t, err) - image = fakes.NewImage("image-repo-name", "", local.IDIdentifier{ + previousImage = fakes.NewImage("image-repo-name", "", local.IDIdentifier{ ImageID: "s0m3D1g3sT", }) @@ -303,7 +357,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) h.AssertNil(t, err) analyzer = &lifecycle.Analyzer{ - PreviousImage: image, + PreviousImage: previousImage, Logger: &discardLogger, SBOMRestorer: sbomRestorer, PlatformAPI: api.MustParse(platformAPI), @@ -319,7 +373,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) h.AssertNil(t, os.RemoveAll(tmpDir)) h.AssertNil(t, os.RemoveAll(layersDir)) h.AssertNil(t, os.RemoveAll(cacheDir)) - h.AssertNil(t, image.Cleanup()) + h.AssertNil(t, previousImage.Cleanup()) mockCtrl.Finish() }) @@ -338,7 +392,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) when("previous image exists", func() { it.Before(func() { metadata := h.MustReadFile(t, filepath.Join("testdata", "analyzer", "app_metadata.json")) - h.AssertNil(t, image.SetLabel("io.buildpacks.lifecycle.metadata", string(metadata))) + h.AssertNil(t, previousImage.SetLabel("io.buildpacks.lifecycle.metadata", string(metadata))) h.AssertNil(t, json.Unmarshal(metadata, &expectedAppMetadata)) }) @@ -369,7 +423,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) when("previous image not found", func() { it.Before(func() { - h.AssertNil(t, image.Delete()) + h.AssertNil(t, previousImage.Delete()) }) it("returns a nil image in the analyzed metadata", func() { @@ -383,7 +437,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) when("previous image does not have metadata label", func() { it.Before(func() { - h.AssertNil(t, image.SetLabel("io.buildpacks.lifecycle.metadata", "")) + h.AssertNil(t, previousImage.SetLabel("io.buildpacks.lifecycle.metadata", "")) }) it("returns empty analyzed metadata", func() { @@ -395,7 +449,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) when("previous image has incompatible metadata", func() { it.Before(func() { - h.AssertNil(t, image.SetLabel("io.buildpacks.lifecycle.metadata", `{["bad", "metadata"]}`)) + h.AssertNil(t, previousImage.SetLabel("io.buildpacks.lifecycle.metadata", `{["bad", "metadata"]}`)) }) it("returns empty analyzed metadata", func() { @@ -408,12 +462,12 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) when("previous image has an SBOM layer digest in the analyzed metadata", func() { it.Before(func() { metadata := fmt.Sprintf(`{"sbom": {"sha":"%s"}}`, "some-digest") - h.AssertNil(t, image.SetLabel("io.buildpacks.lifecycle.metadata", metadata)) + h.AssertNil(t, previousImage.SetLabel("io.buildpacks.lifecycle.metadata", metadata)) h.AssertNil(t, json.Unmarshal([]byte(metadata), &expectedAppMetadata)) }) it("calls the SBOM restorer with the SBOM layer digest", func() { - sbomRestorer.EXPECT().RestoreFromPrevious(image, "some-digest") + sbomRestorer.EXPECT().RestoreFromPrevious(previousImage, "some-digest") _, err := analyzer.Analyze() h.AssertNil(t, err) }) @@ -421,7 +475,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) when("run image is provided", func() { it.Before(func() { - analyzer.RunImage = image + analyzer.RunImage = previousImage }) it("returns the run image digest in the analyzed metadata", func() { @@ -431,13 +485,13 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) h.AssertEq(t, md.RunImage.Reference, "s0m3D1g3sT") }) it("populates target metadata from the run image", func() { - h.AssertNil(t, image.SetLabel("io.buildpacks.base.id", "id software")) - h.AssertNil(t, image.SetOS("windows")) - h.AssertNil(t, image.SetOSVersion("95")) - h.AssertNil(t, image.SetArchitecture("Pentium")) - h.AssertNil(t, image.SetVariant("MMX")) - h.AssertNil(t, image.SetLabel("io.buildpacks.distro.name", "moobuntu")) - h.AssertNil(t, image.SetLabel("io.buildpacks.distro.version", "Helpful Holstein")) + h.AssertNil(t, previousImage.SetLabel("io.buildpacks.base.id", "id software")) + h.AssertNil(t, previousImage.SetOS("windows")) + h.AssertNil(t, previousImage.SetOSVersion("95")) + h.AssertNil(t, previousImage.SetArchitecture("Pentium")) + h.AssertNil(t, previousImage.SetVariant("MMX")) + h.AssertNil(t, previousImage.SetLabel("io.buildpacks.distro.name", "moobuntu")) + h.AssertNil(t, previousImage.SetLabel("io.buildpacks.distro.version", "Helpful Holstein")) md, err := analyzer.Analyze() h.AssertNil(t, err) diff --git a/lifecycle/connected_factory.go b/lifecycle/connected_factory.go new file mode 100644 index 000000000..e003f3542 --- /dev/null +++ b/lifecycle/connected_factory.go @@ -0,0 +1,87 @@ +package lifecycle + +import ( + "fmt" + + "github.com/buildpacks/imgutil" + + "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/cache" + "github.com/buildpacks/lifecycle/image" + "github.com/buildpacks/lifecycle/platform" +) + +// ConnectedFactory TODO +type ConnectedFactory struct { + platformAPI *api.Version + apiVerifier BuildpackAPIVerifier + cacheHandler CacheHandler + configHandler ConfigHandler + imageHandler image.Handler + registryHandler image.RegistryHandler +} + +// NewConnectedFactory TODO +func NewConnectedFactory( + platformAPI *api.Version, + apiVerifier BuildpackAPIVerifier, + cacheHandler CacheHandler, + configHandler ConfigHandler, + imageHandler image.Handler, + registryHandler image.RegistryHandler, +) *ConnectedFactory { + return &ConnectedFactory{ + platformAPI: platformAPI, + apiVerifier: apiVerifier, + cacheHandler: cacheHandler, + configHandler: configHandler, + imageHandler: imageHandler, + registryHandler: registryHandler, + } +} + +func (f *ConnectedFactory) ensureRegistryAccess(inputs platform.LifecycleInputs) error { + var readImages, writeImages []string + writeImages = append(writeImages, inputs.CacheImageRef) + if f.imageHandler.Kind() == image.RemoteKind { + readImages = append(readImages, inputs.PreviousImageRef, inputs.RunImageRef) + writeImages = append(writeImages, inputs.OutputImageRef) + writeImages = append(writeImages, inputs.AdditionalTags...) + } + if err := f.registryHandler.EnsureReadAccess(readImages...); err != nil { + return fmt.Errorf("validating registry read access: %w", err) + } + if err := f.registryHandler.EnsureWriteAccess(writeImages...); err != nil { + return fmt.Errorf("validating registry write access: %w", err) + } + return nil +} + +func (f *ConnectedFactory) getPreviousImage(imageRef string, launchCacheDir string) (imgutil.Image, error) { + if imageRef == "" { + return nil, nil + } + previousImage, err := f.imageHandler.InitImage(imageRef) + if err != nil { + return nil, fmt.Errorf("getting previous image: %w", err) + } + if launchCacheDir == "" || f.imageHandler.Kind() != image.LocalKind { + return previousImage, nil + } + volumeCache, err := cache.NewVolumeCache(launchCacheDir) + if err != nil { + return nil, fmt.Errorf("creating launch cache: %w", err) + } + return cache.NewCachingImage(previousImage, volumeCache), nil +} + +func (f *ConnectedFactory) getRunImage(imageRef string) (imgutil.Image, error) { + if imageRef == "" { + return nil, nil + } + runImage, err := f.imageHandler.InitImage(imageRef) + if err != nil { + return nil, fmt.Errorf("getting run image: %w", err) + } + return runImage, nil +} diff --git a/lifecycle/detector.go b/lifecycle/detector.go index 77257ecef..afcc35c5b 100644 --- a/lifecycle/detector.go +++ b/lifecycle/detector.go @@ -35,27 +35,6 @@ type DetectResolver interface { Resolve(done []buildpack.GroupElement, detectRuns *sync.Map) ([]buildpack.GroupElement, []files.BuildPlanEntry, error) } -type DetectorFactory struct { - platformAPI *api.Version - apiVerifier BuildpackAPIVerifier - configHandler ConfigHandler - dirStore DirStore -} - -func NewDetectorFactory( - platformAPI *api.Version, - apiVerifier BuildpackAPIVerifier, - configHandler ConfigHandler, - dirStore DirStore, -) *DetectorFactory { - return &DetectorFactory{ - platformAPI: platformAPI, - apiVerifier: apiVerifier, - configHandler: configHandler, - dirStore: dirStore, - } -} - type Detector struct { AppDir string BuildConfigDir string @@ -76,55 +55,30 @@ type Detector struct { memHandler *memory.Handler } -func (f *DetectorFactory) NewDetector(analyzedMD files.Analyzed, appDir, buildConfigDir, orderPath, platformDir string, logger log.LoggerHandlerWithLevel) (*Detector, error) { +// NewDetector TODO +func (f *HermeticFactory) NewDetector(inputs platform.LifecycleInputs, logger log.LoggerHandlerWithLevel) (*Detector, error) { memHandler := memory.New() detector := &Detector{ - AnalyzeMD: analyzedMD, - AppDir: appDir, - BuildConfigDir: buildConfigDir, + AppDir: inputs.AppDir, + BuildConfigDir: inputs.BuildConfigDir, DirStore: f.dirStore, Executor: &buildpack.DefaultDetectExecutor{}, Logger: logger, - PlatformDir: platformDir, + PlatformDir: inputs.PlatformDir, Resolver: NewDefaultDetectResolver(&apexlog.Logger{Handler: memHandler}), Runs: &sync.Map{}, memHandler: memHandler, PlatformAPI: f.platformAPI, } - if err := f.setOrder(detector, orderPath, logger); err != nil { + var err error + if detector.AnalyzeMD, err = f.configHandler.ReadAnalyzed(inputs.AnalyzedPath, logger); err != nil { + // TODO: fix creator return nil, err } - return detector, nil -} - -func (f *DetectorFactory) setOrder(detector *Detector, path string, logger log.Logger) error { - orderBp, orderExt, err := f.configHandler.ReadOrder(path) - if err != nil { - return errors.Wrap(err, "reading order") - } - if len(orderExt) > 0 { - detector.HasExtensions = true - } - if err = f.verifyAPIs(orderBp, orderExt, logger); err != nil { - return err - } - detector.Order = PrependExtensions(orderBp, orderExt) - return nil -} - -func (f *DetectorFactory) verifyAPIs(orderBp buildpack.Order, orderExt buildpack.Order, logger log.Logger) error { - for _, group := range append(orderBp, orderExt...) { - for _, groupEl := range group.Group { - module, err := f.dirStore.Lookup(groupEl.Kind(), groupEl.ID, groupEl.Version) - if err != nil { - return err - } - if err = f.apiVerifier.VerifyBuildpackAPI(groupEl.Kind(), groupEl.String(), module.API(), logger); err != nil { - return err - } - } + if detector.Order, detector.HasExtensions, err = f.getOrder(inputs.OrderPath, logger); err != nil { + return nil, err } - return nil + return detector, nil } func (d *Detector) Detect() (buildpack.Group, files.Plan, error) { diff --git a/lifecycle/detector_test.go b/lifecycle/detector_test.go index 26647054f..6f9dac1aa 100644 --- a/lifecycle/detector_test.go +++ b/lifecycle/detector_test.go @@ -20,6 +20,7 @@ import ( "github.com/buildpacks/lifecycle/lifecycle" "github.com/buildpacks/lifecycle/lifecycle/testmock" "github.com/buildpacks/lifecycle/log" + "github.com/buildpacks/lifecycle/platform" "github.com/buildpacks/lifecycle/platform/files" h "github.com/buildpacks/lifecycle/testhelpers" ) @@ -37,7 +38,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) { dirStore *testmock.MockDirStore logger log.LoggerHandlerWithLevel - detectorFactory *lifecycle.DetectorFactory + detectorFactory *lifecycle.HermeticFactory ) it.Before(func() { @@ -48,7 +49,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) { dirStore = testmock.NewMockDirStore(mockController) logger = log.NewDefaultLogger(io.Discard) - detectorFactory = lifecycle.NewDetectorFactory( + detectorFactory = lifecycle.NewHermeticFactory( api.Platform.Latest(), apiVerifier, configHandler, @@ -61,6 +62,10 @@ func testDetector(t *testing.T, when spec.G, it spec.S) { }) when("#NewDetector", func() { + it.Before(func() { + configHandler.EXPECT().ReadAnalyzed("some-analyzed-path", gomock.Any()).Return(files.Analyzed{}, nil).AnyTimes() + }) + it("configures the detector", func() { order := buildpack.Order{ buildpack.Group{Group: []buildpack.GroupElement{{ID: "A", Version: "v1"}}}, @@ -71,9 +76,14 @@ func testDetector(t *testing.T, when spec.G, it spec.S) { bpA1 := &buildpack.BpDescriptor{WithAPI: "0.2"} dirStore.EXPECT().Lookup(buildpack.KindBuildpack, "A", "v1").Return(bpA1, nil) apiVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindBuildpack, "A@v1", "0.2", logger) - amd := files.Analyzed{} - detector, err := detectorFactory.NewDetector(amd, "some-app-dir", "some-build-config-dir", "some-order-path", "some-platform-dir", logger) + detector, err := detectorFactory.NewDetector(platform.LifecycleInputs{ + AnalyzedPath: "some-analyzed-path", + AppDir: "some-app-dir", + BuildConfigDir: "some-build-config-dir", + OrderPath: "some-order-path", + PlatformDir: "some-platform-dir", + }, logger) h.AssertNil(t, err) h.AssertEq(t, detector.AppDir, "some-app-dir") @@ -133,8 +143,13 @@ func testDetector(t *testing.T, when spec.G, it spec.S) { dirStore.EXPECT().Lookup(buildpack.KindExtension, "D", "v1").Return(extD1, nil) apiVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindExtension, "D@v1", "some-other-api-version", logger) - amd := files.Analyzed{} - detector, err := detectorFactory.NewDetector(amd, "some-app-dir", "some-build-config-dir", "some-order-path", "some-platform-dir", logger) + detector, err := detectorFactory.NewDetector(platform.LifecycleInputs{ + AnalyzedPath: "some-analyzed-path", + AppDir: "some-app-dir", + BuildConfigDir: "some-build-config-dir", + OrderPath: "some-order-path", + PlatformDir: "some-platform-dir", + }, logger) h.AssertNil(t, err) h.AssertEq(t, detector.AppDir, "some-app-dir") @@ -157,17 +172,16 @@ func testDetector(t *testing.T, when spec.G, it spec.S) { ) it.Before(func() { - configHandler.EXPECT().ReadOrder(gomock.Any()).Return(buildpack.Order{}, buildpack.Order{}, nil) - amd := files.Analyzed{} + configHandler.EXPECT().ReadAnalyzed("some-analyzed-path", gomock.Any()).Return(files.Analyzed{}, nil).AnyTimes() + configHandler.EXPECT().ReadOrder("some-order-path").Return(buildpack.Order{}, buildpack.Order{}, nil) var err error - detector, err = detectorFactory.NewDetector( - amd, - "some-app-dir", - "some-build-config-dir", - "some-order-path", - "some-platform-dir", - logger, - ) + detector, err = detectorFactory.NewDetector(platform.LifecycleInputs{ + AnalyzedPath: "some-analyzed-path", + AppDir: "some-app-dir", + BuildConfigDir: "some-build-config-dir", + OrderPath: "some-order-path", + PlatformDir: "some-platform-dir", + }, logger) h.AssertNil(t, err) // override factory-provided services executor = testmock.NewMockDetectExecutor(mockController) diff --git a/lifecycle/extender.go b/lifecycle/extender.go index a6f786728..b6d64c109 100644 --- a/lifecycle/extender.go +++ b/lifecycle/extender.go @@ -24,6 +24,7 @@ import ( "github.com/buildpacks/lifecycle/launch" "github.com/buildpacks/lifecycle/layers" "github.com/buildpacks/lifecycle/log" + "github.com/buildpacks/lifecycle/platform" ) type Extender struct { @@ -48,89 +49,39 @@ type DockerfileApplier interface { Cleanup() error } -type ExtenderFactory struct { - apiVerifier BuildpackAPIVerifier - configHandler ConfigHandler -} - -func NewExtenderFactory(apiVerifier BuildpackAPIVerifier, configHandler ConfigHandler) *ExtenderFactory { - return &ExtenderFactory{ - apiVerifier: apiVerifier, - configHandler: configHandler, - } -} - -func (f *ExtenderFactory) NewExtender( - analyzedPath string, - appDir string, - extendedDir string, - generatedDir string, - groupPath string, - layersDir string, - platformDir string, - cacheTTL time.Duration, - dockerfileApplier DockerfileApplier, - kind string, - logger log.Logger, -) (*Extender, error) { +// NewExtender TODO +func (f *HermeticFactory) NewExtender(inputs platform.LifecycleInputs, dockerfileApplier DockerfileApplier, logger log.Logger) (*Extender, error) { extender := &Extender{ - AppDir: appDir, - ExtendedDir: extendedDir, - GeneratedDir: generatedDir, - LayersDir: layersDir, - PlatformDir: platformDir, - CacheTTL: cacheTTL, + AppDir: inputs.AppDir, + ExtendedDir: inputs.ExtendedDir, + GeneratedDir: inputs.GeneratedDir, + LayersDir: inputs.LayersDir, + PlatformDir: inputs.PlatformDir, + CacheTTL: inputs.KanikoCacheTTL, DockerfileApplier: dockerfileApplier, } - if err := f.setImageRef(extender, kind, analyzedPath, logger); err != nil { + var err error + if extender.ImageRef, err = f.getExtendImageRef(inputs, logger); err != nil { return nil, err } - if err := f.setExtensions(extender, groupPath, logger); err != nil { + if extender.Extensions, err = f.getExtensions(inputs.GroupPath, logger); err != nil { return nil, err } return extender, nil } -func (f *ExtenderFactory) setImageRef(extender *Extender, kind, path string, logr log.Logger) error { - analyzedMD, err := f.configHandler.ReadAnalyzed(path, logr) - if err != nil { - return err - } - if kind == "build" { - if analyzedMD.BuildImage != nil { - extender.ImageRef = analyzedMD.BuildImage.Reference - } - } else if kind == "run" { - if analyzedMD.RunImage != nil { - extender.ImageRef = analyzedMD.RunImage.Reference - } - } - - return nil -} - -func (f *ExtenderFactory) setExtensions(extender *Extender, path string, logger log.Logger) error { - _, groupExt, err := f.configHandler.ReadGroup(path) +func (f *HermeticFactory) getExtendImageRef(inputs platform.LifecycleInputs, logger log.Logger) (string, error) { + analyzedMD, err := f.configHandler.ReadAnalyzed(inputs.AnalyzedPath, logger) if err != nil { - return fmt.Errorf("reading group: %w", err) + return "", err } - for i := range groupExt { - groupExt[i].Extension = true + if inputs.ExtendKind == "build" && analyzedMD.BuildImage != nil { + return analyzedMD.BuildImage.Reference, nil } - if err = f.verifyAPIs(groupExt, logger); err != nil { - return err + if inputs.ExtendKind == "run" && analyzedMD.RunImage != nil { + return analyzedMD.RunImage.Reference, nil } - extender.Extensions = groupExt - return nil -} - -func (f *ExtenderFactory) verifyAPIs(groupExt []buildpack.GroupElement, logger log.Logger) error { - for _, groupEl := range groupExt { - if err := f.apiVerifier.VerifyBuildpackAPI(groupEl.Kind(), groupEl.String(), groupEl.API, logger); err != nil { - return err - } - } - return nil + return "", nil } func (e *Extender) Extend(kind string, logger log.Logger) error { diff --git a/lifecycle/extender_test.go b/lifecycle/extender_test.go index 569a4c836..eb71a6435 100644 --- a/lifecycle/extender_test.go +++ b/lifecycle/extender_test.go @@ -25,6 +25,7 @@ import ( "github.com/buildpacks/lifecycle/lifecycle" "github.com/buildpacks/lifecycle/lifecycle/testmock" llog "github.com/buildpacks/lifecycle/log" + "github.com/buildpacks/lifecycle/platform" "github.com/buildpacks/lifecycle/platform/files" h "github.com/buildpacks/lifecycle/testhelpers" ) @@ -40,7 +41,7 @@ func testExtenderFactory(t *testing.T, when spec.G, it spec.S) { when("#NewExtender", func() { var ( mockController *gomock.Controller - extenderFactory *lifecycle.ExtenderFactory + extenderFactory *lifecycle.HermeticFactory fakeAPIVerifier *testmock.MockBuildpackAPIVerifier fakeConfigHandler *testmock.MockConfigHandler fakeDirStore *testmock.MockDirStore @@ -56,10 +57,10 @@ func testExtenderFactory(t *testing.T, when spec.G, it spec.S) { createExtender := func() { fakeConfigHandler.EXPECT().ReadAnalyzed("some-analyzed-path", logger).Return( analyzedMD, nil, - ) + ).AnyTimes() fakeConfigHandler.EXPECT().ReadGroup("some-group-path").Return( []buildpack.GroupElement{}, []buildpack.GroupElement{{ID: "A", Version: "v1", API: "0.9"}}, nil, - ) + ).AnyTimes() fakeDirStore.EXPECT().LookupExt("A", "v1").Return(&buildpack.ExtDescriptor{ WithAPI: "0.9", Extension: buildpack.ExtInfo{ @@ -69,23 +70,21 @@ func testExtenderFactory(t *testing.T, when spec.G, it spec.S) { }, }, }, nil).AnyTimes() - fakeAPIVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindExtension, "A@v1", "0.9", logger) - + fakeAPIVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindExtension, "A@v1", "0.9", logger).AnyTimes() fakeDockerfileApplier := testmock.NewMockDockerfileApplier(mockController) + var err error - extender, err = extenderFactory.NewExtender( - "some-analyzed-path", - "some-app-dir", - "some-extended-dir", - "some-generated-dir", - "some-group-path", - "some-layers-dir", - "some-platform-dir", - 7*(24*time.Hour), - fakeDockerfileApplier, - kind, - logger, - ) + extender, err = extenderFactory.NewExtender(platform.LifecycleInputs{ + AnalyzedPath: "some-analyzed-path", + AppDir: "some-app-dir", + ExtendedDir: "some-extended-dir", + GeneratedDir: "some-generated-dir", + GroupPath: "some-group-path", + LayersDir: "some-layers-dir", + PlatformDir: "some-platform-dir", + KanikoCacheTTL: 7 * (24 * time.Hour), + ExtendKind: kind, + }, fakeDockerfileApplier, logger) h.AssertNil(t, err) } @@ -94,7 +93,7 @@ func testExtenderFactory(t *testing.T, when spec.G, it spec.S) { fakeAPIVerifier = testmock.NewMockBuildpackAPIVerifier(mockController) fakeConfigHandler = testmock.NewMockConfigHandler(mockController) fakeDirStore = testmock.NewMockDirStore(mockController) - extenderFactory = lifecycle.NewExtenderFactory(fakeAPIVerifier, fakeConfigHandler) + extenderFactory = lifecycle.NewHermeticFactory(api.Platform.Latest(), fakeAPIVerifier, fakeConfigHandler, nil) logger = &log.Logger{Handler: &discard.Handler{}} }) diff --git a/lifecycle/generator.go b/lifecycle/generator.go index 8cd091712..349627a7d 100644 --- a/lifecycle/generator.go +++ b/lifecycle/generator.go @@ -33,82 +33,36 @@ type Generator struct { RunMetadata files.Run } -type GeneratorFactory struct { - apiVerifier BuildpackAPIVerifier - configHandler ConfigHandler - dirStore DirStore -} - -func NewGeneratorFactory( - apiVerifier BuildpackAPIVerifier, - configHandler ConfigHandler, - dirStore DirStore, -) *GeneratorFactory { - return &GeneratorFactory{ - apiVerifier: apiVerifier, - configHandler: configHandler, - dirStore: dirStore, - } -} - -func (f *GeneratorFactory) NewGenerator( - analyzedPath string, - appDir string, - buildConfigDir string, - extensions []buildpack.GroupElement, - generatedDir string, - plan files.Plan, - platformAPI *api.Version, - platformDir string, - runPath string, - stdout, stderr io.Writer, - logger log.Logger, -) (*Generator, error) { +// NewGenerator TODO +func (f *HermeticFactory) NewGenerator(inputs platform.LifecycleInputs, stdout, stderr io.Writer, logger log.Logger) (*Generator, error) { generator := &Generator{ - AppDir: appDir, - BuildConfigDir: buildConfigDir, - GeneratedDir: generatedDir, - PlatformAPI: platformAPI, - PlatformDir: platformDir, + AppDir: inputs.AppDir, + BuildConfigDir: inputs.BuildConfigDir, + GeneratedDir: inputs.GeneratedDir, + PlatformAPI: inputs.PlatformAPI, + PlatformDir: inputs.PlatformDir, DirStore: f.dirStore, Executor: &buildpack.DefaultGenerateExecutor{}, Logger: logger, - Plan: plan, Out: stdout, Err: stderr, } - if err := f.setExtensions(generator, extensions, logger); err != nil { + var err error + if generator.Extensions, err = f.getExtensions(inputs.GroupPath, logger); err != nil { + // TODO: fix detector return nil, err } - if err := f.setAnalyzedMD(generator, analyzedPath, logger); err != nil { + if generator.Plan, err = f.configHandler.ReadPlan(inputs.PlanPath); err != nil { + // TODO: fix creator return nil, err } - if err := f.setRunMD(generator, runPath, logger); err != nil { + if generator.RunMetadata, err = f.configHandler.ReadRun(inputs.RunPath, logger); err != nil { return nil, err } - return generator, nil -} - -func (f *GeneratorFactory) setExtensions(generator *Generator, extensions []buildpack.GroupElement, logger log.Logger) error { - generator.Extensions = extensions - for _, el := range generator.Extensions { - if err := f.apiVerifier.VerifyBuildpackAPI(buildpack.KindExtension, el.String(), el.API, logger); err != nil { - return err - } + if generator.AnalyzedMD, err = f.configHandler.ReadAnalyzed(inputs.AnalyzedPath, logger); err != nil { + return nil, err } - return nil -} - -func (f *GeneratorFactory) setAnalyzedMD(generator *Generator, analyzedPath string, logger log.Logger) error { - var err error - generator.AnalyzedMD, err = f.configHandler.ReadAnalyzed(analyzedPath, logger) - return err -} - -func (f *GeneratorFactory) setRunMD(generator *Generator, runPath string, logger log.Logger) error { - var err error - generator.RunMetadata, err = f.configHandler.ReadRun(runPath, logger) - return err + return generator, nil } type GenerateResult struct { diff --git a/lifecycle/generator_test.go b/lifecycle/generator_test.go index 95059ed71..6ef3124ce 100644 --- a/lifecycle/generator_test.go +++ b/lifecycle/generator_test.go @@ -21,6 +21,7 @@ import ( "github.com/buildpacks/lifecycle/lifecycle" "github.com/buildpacks/lifecycle/lifecycle/testmock" llog "github.com/buildpacks/lifecycle/log" + "github.com/buildpacks/lifecycle/platform" "github.com/buildpacks/lifecycle/platform/files" h "github.com/buildpacks/lifecycle/testhelpers" ) @@ -35,7 +36,7 @@ func TestGenerator(t *testing.T) { func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) { when("#NewGenerator", func() { var ( - generatorFactory *lifecycle.GeneratorFactory + generatorFactory *lifecycle.HermeticFactory fakeAPIVerifier *testmock.MockBuildpackAPIVerifier fakeConfigHandler *testmock.MockConfigHandler fakeDirStore *testmock.MockDirStore @@ -51,7 +52,8 @@ func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) { fakeDirStore = testmock.NewMockDirStore(mockController) logger = &log.Logger{Handler: &discard.Handler{}} - generatorFactory = lifecycle.NewGeneratorFactory( + generatorFactory = lifecycle.NewHermeticFactory( + api.Platform.Latest(), fakeAPIVerifier, fakeConfigHandler, fakeDirStore, @@ -66,7 +68,10 @@ func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) { fakeAPIVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindExtension, "A@v1", "0.9", logger) fakeConfigHandler.EXPECT().ReadAnalyzed("some-analyzed-path", logger).Return(files.Analyzed{RunImage: &files.RunImage{Reference: "some-run-image-ref"}}, nil) fakeConfigHandler.EXPECT().ReadRun("some-run-path", logger).Return(files.Run{Images: []files.RunImageForExport{{Image: "some-run-image"}}}, nil) - + providedExtensions := []buildpack.GroupElement{ + {ID: "A", Version: "v1", API: "0.9"}, + } + fakeConfigHandler.EXPECT().ReadGroup("some-group-path").Return([]buildpack.GroupElement{}, providedExtensions, nil) providedPlan := files.Plan{Entries: []files.BuildPlanEntry{ { Providers: []buildpack.GroupElement{ @@ -77,20 +82,19 @@ func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) { }, }, }} - generator, err := generatorFactory.NewGenerator( - "some-analyzed-path", - "some-app-dir", - "some-build-config-dir", - []buildpack.GroupElement{ - {ID: "A", Version: "v1", API: "0.9"}, - }, - "some-output-dir", - providedPlan, - api.Platform.Latest(), - "some-platform-dir", - "some-run-path", - stdout, stderr, - logger, + fakeConfigHandler.EXPECT().ReadPlan("some-plan-path").Return(providedPlan, nil) + + generator, err := generatorFactory.NewGenerator(platform.LifecycleInputs{ + AnalyzedPath: "some-analyzed-path", + AppDir: "some-app-dir", + BuildConfigDir: "some-build-config-dir", + GroupPath: "some-group-path", + GeneratedDir: "some-output-dir", + PlanPath: "some-plan-path", + PlatformAPI: api.Platform.Latest(), + PlatformDir: "some-platform-dir", + RunPath: "some-run-path", + }, stdout, stderr, logger, ) h.AssertNil(t, err) @@ -98,7 +102,7 @@ func testGeneratorFactory(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, generator.AppDir, "some-app-dir") h.AssertNotNil(t, generator.DirStore) h.AssertEq(t, generator.Extensions, []buildpack.GroupElement{ - {ID: "A", Version: "v1", API: "0.9"}, + {ID: "A", Version: "v1", API: "0.9", Extension: true}, }) h.AssertEq(t, generator.GeneratedDir, "some-output-dir") h.AssertEq(t, generator.Logger, logger) diff --git a/lifecycle/handlers.go b/lifecycle/handlers.go index 8188a93ca..651ececf3 100644 --- a/lifecycle/handlers.go +++ b/lifecycle/handlers.go @@ -46,6 +46,7 @@ type ConfigHandler interface { ReadGroup(path string) (buildpackGroup []buildpack.GroupElement, extensionsGroup []buildpack.GroupElement, err error) ReadOrder(path string) (buildpack.Order, buildpack.Order, error) ReadRun(runPath string, logger log.Logger) (files.Run, error) + ReadPlan(path string) (files.Plan, error) } type DefaultConfigHandler struct{} @@ -77,6 +78,15 @@ func ReadGroup(path string) (buildpack.Group, error) { return group, err } +// ReadPlan TODO +func (h *DefaultConfigHandler) ReadPlan(path string) (files.Plan, error) { + var plan files.Plan + if _, err := toml.DecodeFile(path, &plan); err != nil { + return files.Plan{}, err + } + return plan, nil +} + func (h *DefaultConfigHandler) ReadOrder(path string) (buildpack.Order, buildpack.Order, error) { orderBp, orderExt, err := ReadOrder(path) if err != nil { diff --git a/lifecycle/hermetic_factory.go b/lifecycle/hermetic_factory.go new file mode 100644 index 000000000..ce41c30b6 --- /dev/null +++ b/lifecycle/hermetic_factory.go @@ -0,0 +1,88 @@ +package lifecycle + +import ( + "fmt" + + "github.com/pkg/errors" + + "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/buildpack" + "github.com/buildpacks/lifecycle/log" +) + +// HermeticFactory TODO +type HermeticFactory struct { + platformAPI *api.Version + apiVerifier BuildpackAPIVerifier + configHandler ConfigHandler + dirStore DirStore +} + +// NewHermeticFactory TODO +func NewHermeticFactory( + platformAPI *api.Version, + apiVerifier BuildpackAPIVerifier, + configHandler ConfigHandler, + dirStore DirStore, +) *HermeticFactory { + return &HermeticFactory{ + platformAPI: platformAPI, + apiVerifier: apiVerifier, + configHandler: configHandler, + dirStore: dirStore, + } +} + +func (f *HermeticFactory) getExtensions(groupPath string, logger log.Logger) ([]buildpack.GroupElement, error) { + _, groupExt, err := f.configHandler.ReadGroup(groupPath) + if err != nil { + return nil, fmt.Errorf("reading group: %w", err) + } + for i := range groupExt { + groupExt[i].Extension = true + } + if err = f.verifyGroup(groupExt, logger); err != nil { + return nil, err + } + return groupExt, nil +} + +func (f *HermeticFactory) getOrder(path string, logger log.Logger) (order buildpack.Order, hasExtensions bool, err error) { + orderBp, orderExt, orderErr := f.configHandler.ReadOrder(path) + if orderErr != nil { + err = errors.Wrap(orderErr, "reading order") + return + } + if len(orderExt) > 0 { + hasExtensions = true + } + if err = f.verifyOrder(orderBp, orderExt, logger); err != nil { + return + } + order = PrependExtensions(orderBp, orderExt) + return +} + +func (f *HermeticFactory) verifyGroup(group []buildpack.GroupElement, logger log.Logger) error { + for _, groupEl := range group { + if err := f.apiVerifier.VerifyBuildpackAPI(groupEl.Kind(), groupEl.String(), groupEl.API, logger); err != nil { + return err + } + } + return nil +} + +func (f *HermeticFactory) verifyOrder(orderBp buildpack.Order, orderExt buildpack.Order, logger log.Logger) error { + for _, group := range append(orderBp, orderExt...) { + for _, groupEl := range group.Group { + module, err := f.dirStore.Lookup(groupEl.Kind(), groupEl.ID, groupEl.Version) + if err != nil { + return err + } + if err = f.apiVerifier.VerifyBuildpackAPI(groupEl.Kind(), groupEl.String(), module.API(), logger); err != nil { + return err + } + } + } + return nil +} diff --git a/lifecycle/testmock/config_handler.go b/lifecycle/testmock/config_handler.go index d7ce63392..5600685ed 100644 --- a/lifecycle/testmock/config_handler.go +++ b/lifecycle/testmock/config_handler.go @@ -84,6 +84,21 @@ func (mr *MockConfigHandlerMockRecorder) ReadOrder(arg0 interface{}) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadOrder", reflect.TypeOf((*MockConfigHandler)(nil).ReadOrder), arg0) } +// ReadPlan mocks base method. +func (m *MockConfigHandler) ReadPlan(arg0 string) (files.Plan, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReadPlan", arg0) + ret0, _ := ret[0].(files.Plan) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReadPlan indicates an expected call of ReadPlan. +func (mr *MockConfigHandlerMockRecorder) ReadPlan(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadPlan", reflect.TypeOf((*MockConfigHandler)(nil).ReadPlan), arg0) +} + // ReadRun mocks base method. func (m *MockConfigHandler) ReadRun(arg0 string, arg1 log.Logger) (files.Run, error) { m.ctrl.T.Helper()