From 21f46335777b9efabf044af7ca9f2e4fca7a306f Mon Sep 17 00:00:00 2001 From: Natalie Arellano Date: Thu, 2 Jun 2022 11:06:41 -0400 Subject: [PATCH] Add support for pre-release apis and experimental features - Refactors cmd package: - Moves platform logic to platform or platform/launch - Moves env and flag parsing to cmd/lifecycle/cli and cmd/launcher/cli - Moves constants for supported & deprecated apis from api package to platform and buildpack packages (this removes all CNB business logic from api) - Creates log package to hold interface, removing duplication Signed-off-by: Natalie Arellano --- acceptance/acceptance_test.go | 4 +- acceptance/analyzer_test.go | 4 +- acceptance/builder_test.go | 3 +- acceptance/creator_test.go | 3 +- acceptance/detector_test.go | 3 +- acceptance/exporter_test.go | 2 +- acceptance/restorer_test.go | 3 +- analyzer.go | 7 +- analyzer_test.go | 12 +- api/apis.go | 14 +- api/version.go | 45 ++- api/version_test.go | 34 ++- builder.go | 3 +- builder_test.go | 27 +- buildpack/apis.go | 9 + buildpack/bom.go | 5 +- buildpack/bomfile.go | 3 +- buildpack/build.go | 9 +- buildpack/build_test.go | 2 +- buildpack/detect.go | 19 +- buildpack/detect_test.go | 3 +- buildpack/layers.go | 7 +- cache.go | 3 +- cache_test.go | 8 +- cmd/exit.go | 17 +- cmd/flags.go | 256 ------------------ cmd/launcher/cli/env.go | 38 +++ cmd/launcher/cli/launcher.go | 23 +- cmd/lifecycle/analyzer.go | 67 ++--- cmd/lifecycle/builder.go | 23 +- cmd/{ => lifecycle/cli}/command.go | 22 +- cmd/lifecycle/cli/env.go | 69 +++++ cmd/lifecycle/cli/flags.go | 172 ++++++++++++ cmd/lifecycle/cli/flags_unix.go | 8 + cmd/lifecycle/cli/flags_windows.go | 5 + cmd/lifecycle/creator.go | 63 ++--- cmd/lifecycle/detector.go | 36 +-- cmd/lifecycle/exporter.go | 74 ++--- cmd/lifecycle/main.go | 50 ++-- cmd/lifecycle/rebaser.go | 25 +- cmd/lifecycle/restorer.go | 27 +- cmd/logs.go | 2 +- cmd/version.go | 80 ------ cmd/version_test.go | 157 ----------- detector.go | 3 +- detector_test.go | 3 +- exporter.go | 36 +-- exporter_test.go | 12 +- internal/layer/metadata_restorer.go | 5 +- internal/layer/metadata_restorer_test.go | 13 +- internal/layer/sbom_restorer.go | 5 +- internal/layer/sbom_restorer_test.go | 8 +- layers/factory.go | 17 +- {internal/layer => log}/logger.go | 2 +- logger.go | 15 - platform/analyze_inputs.go | 9 +- platform/analyze_inputs_test.go | 5 +- platform/apis.go | 15 + platform/default_paths.go | 28 -- platform/env.go | 28 ++ platform/exit.go | 15 +- platform/files_test.go | 2 +- platform/guard/apis.go | 119 ++++++++ platform/guard/apis_test.go | 205 ++++++++++++++ platform/guard/experimental_features.go | 32 +++ platform/guard/experimental_features_test.go | 61 +++++ platform/launch/apis.go | 13 + platform/launch/env.go | 16 ++ platform/launch/env_unix.go | 8 + platform/launch/env_windows.go | 5 + platform/launch/exit.go | 15 +- platform/launch/platform_test.go | 26 +- platform/logger.go | 15 - platform/paths.go | 43 +++ cmd/flags_unix.go => platform/paths_unix.go | 2 +- .../paths_windows.go | 2 +- platform/platform_test.go | 2 +- platform/utils.go | 4 +- rebaser.go | 3 +- rebaser_test.go | 2 +- restorer.go | 3 +- restorer_test.go | 4 +- save.go | 3 +- tools/packager/main.go | 11 +- 84 files changed, 1315 insertions(+), 946 deletions(-) create mode 100644 buildpack/apis.go delete mode 100644 cmd/flags.go create mode 100644 cmd/launcher/cli/env.go rename cmd/{ => lifecycle/cli}/command.go (80%) create mode 100644 cmd/lifecycle/cli/env.go create mode 100644 cmd/lifecycle/cli/flags.go create mode 100644 cmd/lifecycle/cli/flags_unix.go create mode 100644 cmd/lifecycle/cli/flags_windows.go delete mode 100644 cmd/version_test.go rename {internal/layer => log}/logger.go (94%) delete mode 100644 logger.go create mode 100644 platform/apis.go delete mode 100644 platform/default_paths.go create mode 100644 platform/env.go create mode 100644 platform/guard/apis.go create mode 100644 platform/guard/apis_test.go create mode 100644 platform/guard/experimental_features.go create mode 100644 platform/guard/experimental_features_test.go create mode 100644 platform/launch/apis.go create mode 100644 platform/launch/env.go create mode 100644 platform/launch/env_unix.go create mode 100644 platform/launch/env_windows.go delete mode 100644 platform/logger.go create mode 100644 platform/paths.go rename cmd/flags_unix.go => platform/paths_unix.go (84%) rename cmd/flags_windows.go => platform/paths_windows.go (73%) diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index a5a5f77b4..46b4f09dd 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -12,7 +12,7 @@ import ( "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/platform" h "github.com/buildpacks/lifecycle/testhelpers" ) @@ -22,7 +22,7 @@ const ( ) var ( - latestPlatformAPI = api.Platform.Latest().String() + latestPlatformAPI = platform.APIs.Latest().String() buildDir string ) diff --git a/acceptance/analyzer_test.go b/acceptance/analyzer_test.go index 04dab8cd8..f6ce34a3c 100644 --- a/acceptance/analyzer_test.go +++ b/acceptance/analyzer_test.go @@ -49,7 +49,7 @@ func TestAnalyzer(t *testing.T) { analyzeDaemonFixtures = analyzeTest.targetDaemon.fixtures analyzeRegFixtures = analyzeTest.targetRegistry.fixtures - for _, platformAPI := range api.Platform.Supported { + for _, platformAPI := range platform.APIs.Supported { spec.Run(t, "acceptance-analyzer/"+platformAPI.String(), testAnalyzerFunc(platformAPI.String()), spec.Parallel(), spec.Report(report.Terminal{})) } } @@ -240,7 +240,7 @@ func testAnalyzerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe t.Fatalf("expected an error of type exec.ExitError") } h.AssertEq(t, failErr.ExitCode(), 12) // platform code for buildpack api incompatibility - expected := "buildpack API version '0.1' is incompatible with the lifecycle" + expected := "requests buildpack API version '0.1' which is incompatible with the lifecycle" h.AssertStringContains(t, string(output), expected) }) }) diff --git a/acceptance/builder_test.go b/acceptance/builder_test.go index dd76fee8f..1836dadef 100644 --- a/acceptance/builder_test.go +++ b/acceptance/builder_test.go @@ -16,7 +16,6 @@ import ( "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/platform" h "github.com/buildpacks/lifecycle/testhelpers" ) @@ -50,7 +49,7 @@ func TestBuilder(t *testing.T) { h.DockerBuild(t, builderImage, builderDockerContext, - h.WithArgs("--build-arg", fmt.Sprintf("cnb_platform_api=%s", api.Platform.Latest())), + h.WithArgs("--build-arg", fmt.Sprintf("cnb_platform_api=%s", platform.APIs.Latest())), h.WithFlags( "-f", filepath.Join(builderDockerContext, dockerfileName), ), diff --git a/acceptance/creator_test.go b/acceptance/creator_test.go index ed96a341a..3d2daf159 100644 --- a/acceptance/creator_test.go +++ b/acceptance/creator_test.go @@ -17,6 +17,7 @@ import ( "github.com/sclevine/spec/report" "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/platform" h "github.com/buildpacks/lifecycle/testhelpers" ) @@ -48,7 +49,7 @@ func TestCreator(t *testing.T) { createDaemonFixtures = createTest.targetDaemon.fixtures createRegFixtures = createTest.targetRegistry.fixtures - for _, platformAPI := range api.Platform.Supported { + for _, platformAPI := range platform.APIs.Supported { spec.Run(t, "acceptance-creator/"+platformAPI.String(), testCreatorFunc(platformAPI.String()), spec.Parallel(), spec.Report(report.Terminal{})) } } diff --git a/acceptance/detector_test.go b/acceptance/detector_test.go index c2e2a756f..f187418d2 100644 --- a/acceptance/detector_test.go +++ b/acceptance/detector_test.go @@ -18,7 +18,6 @@ import ( "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/platform" h "github.com/buildpacks/lifecycle/testhelpers" @@ -41,7 +40,7 @@ func TestDetector(t *testing.T) { h.DockerBuild(t, detectImage, detectDockerContext, - h.WithArgs("--build-arg", fmt.Sprintf("cnb_platform_api=%s", api.Platform.Latest())), + h.WithArgs("--build-arg", fmt.Sprintf("cnb_platform_api=%s", platform.APIs.Latest())), ) defer h.DockerImageRemove(t, detectImage) diff --git a/acceptance/exporter_test.go b/acceptance/exporter_test.go index e3921d926..1d500675c 100644 --- a/acceptance/exporter_test.go +++ b/acceptance/exporter_test.go @@ -59,7 +59,7 @@ func TestExporter(t *testing.T) { rand.Seed(time.Now().UTC().UnixNano()) - for _, platformAPI := range api.Platform.Supported { + for _, platformAPI := range platform.APIs.Supported { spec.Run(t, "acceptance-exporter/"+platformAPI.String(), testExporterFunc(platformAPI.String()), spec.Parallel(), spec.Report(report.Terminal{})) } } diff --git a/acceptance/restorer_test.go b/acceptance/restorer_test.go index 7452b6707..edc5d0a9e 100644 --- a/acceptance/restorer_test.go +++ b/acceptance/restorer_test.go @@ -17,6 +17,7 @@ import ( "github.com/sclevine/spec/report" "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/platform" h "github.com/buildpacks/lifecycle/testhelpers" ) @@ -36,7 +37,7 @@ func TestRestorer(t *testing.T) { h.DockerBuild(t, restorerImage, restoreDockerContext) defer h.DockerImageRemove(t, restorerImage) - for _, platformAPI := range api.Platform.Supported { + for _, platformAPI := range platform.APIs.Supported { spec.Run(t, "acceptance-restorer/"+platformAPI.String(), testRestorerFunc(platformAPI.String()), spec.Parallel(), spec.Report(report.Terminal{})) } } diff --git a/analyzer.go b/analyzer.go index 25a31648c..7dac57089 100644 --- a/analyzer.go +++ b/analyzer.go @@ -9,6 +9,7 @@ import ( "github.com/buildpacks/lifecycle/cache" "github.com/buildpacks/lifecycle/image" "github.com/buildpacks/lifecycle/internal/layer" + "github.com/buildpacks/lifecycle/log" "github.com/buildpacks/lifecycle/platform" ) @@ -37,7 +38,7 @@ func NewAnalyzerFactory(platformAPI *api.Version, cacheHandler CacheHandler, con type Analyzer struct { PreviousImage imgutil.Image RunImage imgutil.Image - Logger Logger + Logger log.Logger SBOMRestorer layer.SBOMRestorer // Platform API < 0.7 @@ -59,7 +60,7 @@ func (f *AnalyzerFactory) NewAnalyzer( previousImageRef string, runImageRef string, skipLayers bool, - logger Logger, + logger log.Logger, ) (*Analyzer, error) { analyzer := &Analyzer{ LayerMetadataRestorer: &layer.NopMetadataRestorer{}, @@ -246,7 +247,7 @@ func bomSHA(appMeta platform.LayersMetadata) string { return appMeta.BOM.SHA } -func retrieveCacheMetadata(fromCache Cache, logger Logger) (platform.CacheMetadata, error) { +func retrieveCacheMetadata(fromCache Cache, logger log.Logger) (platform.CacheMetadata, error) { // Create empty cache metadata in case a usable cache is not provided. var cacheMeta platform.CacheMetadata if fromCache != nil { diff --git a/analyzer_test.go b/analyzer_test.go index e865686c4..d509fe03c 100644 --- a/analyzer_test.go +++ b/analyzer_test.go @@ -28,7 +28,7 @@ import ( ) func TestAnalyzer(t *testing.T) { - for _, api := range api.Platform.Supported { + for _, api := range platform.APIs.Supported { spec.Run(t, "unit-analyzer/"+api.String(), testAnalyzer(api.String()), spec.Parallel(), spec.Report(report.Terminal{})) } spec.Run(t, "unit-new-analyzer", testAnalyzerFactory, spec.Parallel(), spec.Report(report.Terminal{})) @@ -67,7 +67,7 @@ func testAnalyzerFactory(t *testing.T, when spec.G, it spec.S) { when("platform api >= 0.8", func() { it.Before(func() { analyzerFactory = lifecycle.NewAnalyzerFactory( - api.Platform.Latest(), + platform.APIs.Latest(), fakeCacheHandler, fakeConfigHandler, fakeImageHandler, @@ -376,9 +376,9 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) Logger: &discardLogger, SBOMRestorer: sbomRestorer, Buildpacks: []buildpack.GroupBuildpack{ - {ID: "metadata.buildpack", API: api.Buildpack.Latest().String()}, - {ID: "no.cache.buildpack", API: api.Buildpack.Latest().String()}, - {ID: "no.metadata.buildpack", API: api.Buildpack.Latest().String()}, + {ID: "metadata.buildpack", API: buildpack.APIs.Latest().String()}, + {ID: "no.cache.buildpack", API: buildpack.APIs.Latest().String()}, + {ID: "no.metadata.buildpack", API: buildpack.APIs.Latest().String()}, }, Cache: testCache, LayerMetadataRestorer: metadataRestorer, @@ -444,7 +444,7 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S) h.AssertNil(t, testCache.SetMetadata(expectedCacheMetadata)) h.AssertNil(t, testCache.Commit()) - analyzer.Buildpacks = append(analyzer.Buildpacks, buildpack.GroupBuildpack{ID: "escaped/buildpack/id", API: api.Buildpack.Latest().String()}) + analyzer.Buildpacks = append(analyzer.Buildpacks, buildpack.GroupBuildpack{ID: "escaped/buildpack/id", API: buildpack.APIs.Latest().String()}) expectRestoresLayerMetadataIfSupported() }) diff --git a/api/apis.go b/api/apis.go index 0dccd1719..994eb528c 100644 --- a/api/apis.go +++ b/api/apis.go @@ -7,11 +7,6 @@ import ( "github.com/pkg/errors" ) -var ( - Platform = newApisMustParse([]string{"0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9"}, nil) - Buildpack = newApisMustParse([]string{"0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8"}, nil) -) - type APIs struct { Supported List Deprecated List @@ -27,8 +22,8 @@ func (l List) String() string { return "[" + strings.Join(els, ", ") + "]" } -// newApisMustParse calls NewApis and panics on error -func newApisMustParse(supported []string, deprecated []string) APIs { +// NewAPIsMustParse calls NewApis and panics on error +func NewAPIsMustParse(supported []string, deprecated []string) APIs { apis, err := NewAPIs(supported, deprecated) if err != nil { panic(err) @@ -88,6 +83,11 @@ func (a APIs) IsDeprecated(target *Version) bool { return false } +// IsPrelease returns true or false depending on whether the target API is experimental +func (a APIs) IsPrelease(target *Version) bool { + return target.Prerelease != "" +} + // Latest returns the latest API that is supported func (a APIs) Latest() *Version { latest := a.Supported[0] diff --git a/api/version.go b/api/version.go index b7727a5ad..6683f9ed4 100644 --- a/api/version.go +++ b/api/version.go @@ -8,11 +8,12 @@ import ( "github.com/pkg/errors" ) -var regex = regexp.MustCompile(`^v?(\d+)\.?(\d*)$`) +var regex = regexp.MustCompile(`^v?(\d+)\.?(\d*)(-[a-zA-Z0-9-]+)?$`) type Version struct { - Major, - Minor uint64 + Major uint64 + Minor uint64 + Prerelease string } func MustParse(v string) *Version { @@ -32,9 +33,10 @@ func NewVersion(v string) (*Version, error) { var ( major, minor uint64 + prerelease string err error ) - if len(matches[0]) == 3 { + if len(matches[0]) == 4 { major, err = strconv.ParseUint(matches[0][1], 10, 64) if err != nil { return nil, errors.Wrapf(err, "parsing Major '%s'", matches[0][1]) @@ -48,14 +50,19 @@ func NewVersion(v string) (*Version, error) { return nil, errors.Wrapf(err, "parsing Minor '%s'", matches[0][2]) } } + + prerelease = matches[0][3] } else { return nil, errors.Errorf("could not parse version '%s'", v) } - return &Version{Major: major, Minor: minor}, nil + return &Version{Major: major, Minor: minor, Prerelease: prerelease}, nil } func (v *Version) String() string { + if v.Prerelease != "" { + return fmt.Sprintf("%d.%d-%s", v.Major, v.Minor, v.Prerelease) + } return fmt.Sprintf("%d.%d", v.Major, v.Minor) } @@ -89,21 +96,35 @@ func (v *Version) Equal(o *Version) bool { // 1 is greater than *Version o func (v *Version) Compare(o *Version) int { if v.Major != o.Major { - if v.Major < o.Major { + switch { + case v.Major < o.Major: return -1 - } - - if v.Major > o.Major { + default: + // v.Major > o.Major return 1 } } if v.Minor != o.Minor { - if v.Minor < o.Minor { + switch { + case v.Minor < o.Minor: return -1 + default: + // v.Minor > o.Minor + return 1 } + } - if v.Minor > o.Minor { + if v.Prerelease != o.Prerelease { + switch { + case o.Prerelease == "": + return -1 + case v.Prerelease == "": + return 1 + case v.Prerelease < o.Prerelease: + return -1 + default: + // v.Prerelease > o.Prerelease return 1 } } @@ -113,7 +134,7 @@ func (v *Version) Compare(o *Version) int { func (v *Version) IsSupersetOf(o *Version) bool { if v.Major == 0 { - return v.Equal(o) + return v.Major == o.Major && v.Minor == o.Minor } return v.Major == o.Major && v.Minor >= o.Minor } diff --git a/api/version_test.go b/api/version_test.go index 25cfacf35..577d5445b 100644 --- a/api/version_test.go +++ b/api/version_test.go @@ -19,14 +19,20 @@ func testAPIVersion(t *testing.T, when spec.G, it spec.S) { it("is equal to comparison", func() { subject := api.MustParse("0.2") comparison := api.MustParse("0.2") + h.AssertEq(t, subject.Equal(comparison), true) + subject = api.MustParse("0.2-alpha-1") + comparison = api.MustParse("0.2-alpha-1") h.AssertEq(t, subject.Equal(comparison), true) }) it("is not equal to comparison", func() { subject := api.MustParse("0.2") comparison := api.MustParse("0.3") + h.AssertEq(t, subject.Equal(comparison), false) + subject = api.MustParse("0.2-alpha-1") + comparison = api.MustParse("0.2") h.AssertEq(t, subject.Equal(comparison), false) }) }) @@ -53,6 +59,13 @@ func testAPIVersion(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, v.IsSupersetOf(target), false) }) + + it("Prerelease", func() { + v := api.MustParse("0.2") + target := api.MustParse("0.2-alpha-2") + + h.AssertEq(t, v.IsSupersetOf(target), true) + }) }) when("1.x", func() { @@ -90,15 +103,23 @@ func testAPIVersion(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, v.IsSupersetOf(target), false) }) + + it("Prerelease", func() { + v := api.MustParse("2.0") + target := api.MustParse("2.0-alpha-2") + + h.AssertEq(t, v.IsSupersetOf(target), true) + }) }) }) when("#LessThan", func() { var subject = api.MustParse("0.3") var toTest = map[string]bool{ - "0.2": false, - "0.3": false, - "0.4": true, + "0.2": false, + "0.3": false, + "0.4": true, + "0.3-alpha-1": false, } it("returns the expected value", func() { for comparison, expected := range toTest { @@ -110,9 +131,10 @@ func testAPIVersion(t *testing.T, when spec.G, it spec.S) { when("#AtLeast", func() { var subject = api.MustParse("0.3") var toTest = map[string]bool{ - "0.2": true, - "0.3": true, - "0.4": false, + "0.2": true, + "0.3": true, + "0.4": false, + "0.3-alpha-1": true, } it("returns the expected value", func() { for comparison, expected := range toTest { diff --git a/builder.go b/builder.go index a3fc4a715..9635ed5fd 100644 --- a/builder.go +++ b/builder.go @@ -16,6 +16,7 @@ import ( "github.com/buildpacks/lifecycle/internal/fsutil" "github.com/buildpacks/lifecycle/launch" "github.com/buildpacks/lifecycle/layers" + "github.com/buildpacks/lifecycle/log" "github.com/buildpacks/lifecycle/platform" ) @@ -44,7 +45,7 @@ type Builder struct { Group buildpack.Group Plan platform.BuildPlan Out, Err io.Writer - Logger Logger + Logger log.Logger BuildpackStore BuildpackStore } diff --git a/builder_test.go b/builder_test.go index 845cdc865..9304502a9 100644 --- a/builder_test.go +++ b/builder_test.go @@ -19,7 +19,6 @@ import ( "github.com/sclevine/spec/report" "github.com/buildpacks/lifecycle" - "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/env" "github.com/buildpacks/lifecycle/launch" @@ -70,11 +69,11 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { AppDir: appDir, LayersDir: layersDir, PlatformDir: platformDir, - Platform: platform.NewPlatform(api.Platform.Latest().String()), + Platform: platform.NewPlatform(platform.APIs.Latest().String()), Group: buildpack.Group{ Group: []buildpack.GroupBuildpack{ - {ID: "A", Version: "v1", API: api.Buildpack.Latest().String(), Homepage: "Buildpack A Homepage"}, - {ID: "B", Version: "v2", API: api.Buildpack.Latest().String()}, + {ID: "A", Version: "v1", API: buildpack.APIs.Latest().String(), Homepage: "Buildpack A Homepage"}, + {ID: "B", Version: "v2", API: buildpack.APIs.Latest().String()}, }, }, Out: stdout, @@ -421,8 +420,8 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { t.Fatalf("Unexpected error:\n%s\n", err) } if s := cmp.Diff(metadata.Buildpacks, []buildpack.GroupBuildpack{ - {ID: "A", Version: "v1", API: api.Buildpack.Latest().String(), Homepage: "Buildpack A Homepage"}, - {ID: "B", Version: "v2", API: api.Buildpack.Latest().String()}, + {ID: "A", Version: "v1", API: buildpack.APIs.Latest().String(), Homepage: "Buildpack A Homepage"}, + {ID: "B", Version: "v2", API: buildpack.APIs.Latest().String()}, }); s != "" { t.Fatalf("Unexpected:\n%s\n", s) } @@ -541,9 +540,9 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { when("multiple default process types", func() { it.Before(func() { builder.Group.Group = []buildpack.GroupBuildpack{ - {ID: "A", Version: "v1", API: api.Buildpack.Latest().String()}, - {ID: "B", Version: "v2", API: api.Buildpack.Latest().String()}, - {ID: "C", Version: "v3", API: api.Buildpack.Latest().String()}, + {ID: "A", Version: "v1", API: buildpack.APIs.Latest().String()}, + {ID: "B", Version: "v2", API: buildpack.APIs.Latest().String()}, + {ID: "C", Version: "v3", API: buildpack.APIs.Latest().String()}, } }) @@ -621,9 +620,9 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { when("overriding default process type, with a non-default process type", func() { it.Before(func() { builder.Group.Group = []buildpack.GroupBuildpack{ - {ID: "A", Version: "v1", API: api.Buildpack.Latest().String()}, - {ID: "B", Version: "v2", API: api.Buildpack.Latest().String()}, - {ID: "C", Version: "v3", API: api.Buildpack.Latest().String()}, + {ID: "A", Version: "v1", API: buildpack.APIs.Latest().String()}, + {ID: "B", Version: "v2", API: buildpack.APIs.Latest().String()}, + {ID: "C", Version: "v3", API: buildpack.APIs.Latest().String()}, } }) @@ -706,7 +705,7 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { when("buildpack API >= 0.6", func() { it.Before(func() { builder.Group.Group = []buildpack.GroupBuildpack{ - {ID: "A", Version: "v1", API: api.Buildpack.Latest().String()}, + {ID: "A", Version: "v1", API: buildpack.APIs.Latest().String()}, } }) it("shouldn't set it as a default process", func() { @@ -932,7 +931,7 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { when("buildpack API >= 0.6", func() { it.Before(func() { builder.Group.Group = []buildpack.GroupBuildpack{ - {ID: "A", Version: "v1", API: api.Buildpack.Latest().String()}, + {ID: "A", Version: "v1", API: buildpack.APIs.Latest().String()}, } }) diff --git a/buildpack/apis.go b/buildpack/apis.go new file mode 100644 index 000000000..6b8fb6393 --- /dev/null +++ b/buildpack/apis.go @@ -0,0 +1,9 @@ +package buildpack + +import ( + "github.com/buildpacks/lifecycle/api" +) + +var ( + APIs = api.NewAPIsMustParse([]string{"0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8"}, nil) +) diff --git a/buildpack/bom.go b/buildpack/bom.go index 2e00caf7c..c6d8a74f2 100644 --- a/buildpack/bom.go +++ b/buildpack/bom.go @@ -5,13 +5,14 @@ import ( "fmt" "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/log" ) type BOMValidator interface { ValidateBOM(GroupBuildpack, []BOMEntry) ([]BOMEntry, error) } -func NewBOMValidator(bpAPI string, layersDir string, logger Logger) BOMValidator { +func NewBOMValidator(bpAPI string, layersDir string, logger log.Logger) BOMValidator { switch { case api.MustParse(bpAPI).LessThan("0.5"): return &legacyBOMValidator{} @@ -23,7 +24,7 @@ func NewBOMValidator(bpAPI string, layersDir string, logger Logger) BOMValidator } type defaultBOMValidator struct { - logger Logger + logger log.Logger layersDir string } diff --git a/buildpack/bomfile.go b/buildpack/bomfile.go index 2db7db70e..9131fb199 100644 --- a/buildpack/bomfile.go +++ b/buildpack/bomfile.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/log" ) const ( @@ -102,7 +103,7 @@ func sbomGlob(layersDir string) (matches []string, err error) { return } -func (b *Descriptor) processBOMFiles(layersDir string, bp GroupBuildpack, bpLayers map[string]LayerMetadataFile, logger Logger) ([]BOMFile, error) { +func (b *Descriptor) processBOMFiles(layersDir string, bp GroupBuildpack, bpLayers map[string]LayerMetadataFile, logger log.Logger) ([]BOMFile, error) { var ( files []BOMFile ) diff --git a/buildpack/build.go b/buildpack/build.go index edc9fb770..1b20780ed 100644 --- a/buildpack/build.go +++ b/buildpack/build.go @@ -18,6 +18,7 @@ import ( "github.com/buildpacks/lifecycle/internal/fsutil" "github.com/buildpacks/lifecycle/launch" "github.com/buildpacks/lifecycle/layers" + "github.com/buildpacks/lifecycle/log" ) const ( @@ -38,7 +39,7 @@ type BuildConfig struct { LayersDir string Out io.Writer Err io.Writer - Logger Logger + Logger log.Logger } type BuildResult struct { @@ -122,7 +123,7 @@ func renameLayerDirIfNeeded(layerMetadataFile LayerMetadataFile, layerDir string return nil } -func (b *Descriptor) processLayers(layersDir string, logger Logger) (map[string]LayerMetadataFile, error) { +func (b *Descriptor) processLayers(layersDir string, logger log.Logger) (map[string]LayerMetadataFile, error) { if api.MustParse(b.API).LessThan("0.6") { return eachLayer(layersDir, b.API, func(path, buildpackAPI string) (LayerMetadataFile, error) { layerMetadataFile, msg, err := DecodeLayerMetadataFile(path+".toml", buildpackAPI) @@ -244,7 +245,7 @@ func eachLayer(bpLayersDir, buildpackAPI string, fn func(path, api string) (Laye return bpLayers, nil } -func (b *Descriptor) readOutputFiles(bpLayersDir, bpPlanPath string, bpPlanIn Plan, bpLayers map[string]LayerMetadataFile, logger Logger) (BuildResult, error) { +func (b *Descriptor) readOutputFiles(bpLayersDir, bpPlanPath string, bpPlanIn Plan, bpLayers map[string]LayerMetadataFile, logger log.Logger) (BuildResult, error) { br := BuildResult{} bpFromBpInfo := GroupBuildpack{ID: b.Buildpack.ID, Version: b.Buildpack.Version} @@ -347,7 +348,7 @@ func (b *Descriptor) readOutputFiles(bpLayersDir, bpPlanPath string, bpPlanIn Pl return br, nil } -func overrideDefaultForOldBuildpacks(processes []launch.Process, bpAPI string, logger Logger) error { +func overrideDefaultForOldBuildpacks(processes []launch.Process, bpAPI string, logger log.Logger) error { if api.MustParse(bpAPI).AtLeast("0.6") { return nil } diff --git a/buildpack/build_test.go b/buildpack/build_test.go index bbaab5066..fbe599cea 100644 --- a/buildpack/build_test.go +++ b/buildpack/build_test.go @@ -27,7 +27,7 @@ import ( "github.com/buildpacks/lifecycle/testmock" ) -var latestBuildpackAPI = api.Buildpack.Latest() +var latestBuildpackAPI = buildpack.APIs.Latest() func TestBuild(t *testing.T) { spec.Run(t, "Build", testBuild, spec.Report(report.Terminal{})) diff --git a/buildpack/detect.go b/buildpack/detect.go index fe5386b20..efb90220e 100644 --- a/buildpack/detect.go +++ b/buildpack/detect.go @@ -12,32 +12,19 @@ import ( "github.com/pkg/errors" "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/log" ) const ( + EnvBuildPlanPath = "CNB_BUILD_PLAN_PATH" EnvBuildpackDir = "CNB_BUILDPACK_DIR" EnvPlatformDir = "CNB_PLATFORM_DIR" - EnvBuildPlanPath = "CNB_BUILD_PLAN_PATH" ) -type Logger interface { - Debug(msg string) - Debugf(fmt string, v ...interface{}) - - Info(msg string) - Infof(fmt string, v ...interface{}) - - Warn(msg string) - Warnf(fmt string, v ...interface{}) - - Error(msg string) - Errorf(fmt string, v ...interface{}) -} - type DetectConfig struct { AppDir string PlatformDir string - Logger Logger + Logger log.Logger } func (b *Descriptor) Detect(config *DetectConfig, bpEnv BuildEnv) DetectRun { diff --git a/buildpack/detect_test.go b/buildpack/detect_test.go index 166867ac4..6b885ff95 100644 --- a/buildpack/detect_test.go +++ b/buildpack/detect_test.go @@ -14,7 +14,6 @@ import ( "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/buildpack/testmock" h "github.com/buildpacks/lifecycle/testhelpers" @@ -82,7 +81,7 @@ func testDetect(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, err) bpTOML = buildpack.Descriptor{ - API: api.Buildpack.Latest().String(), + API: buildpack.APIs.Latest().String(), Buildpack: buildpack.Info{ ID: "A", }, diff --git a/buildpack/layers.go b/buildpack/layers.go index 447a2cac2..a6812a76b 100644 --- a/buildpack/layers.go +++ b/buildpack/layers.go @@ -13,6 +13,7 @@ import ( "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/launch" + "github.com/buildpacks/lifecycle/log" ) type LayersDir struct { @@ -23,7 +24,7 @@ type LayersDir struct { Store *StoreTOML } -func ReadLayersDir(layersDir string, bp GroupBuildpack, logger Logger) (LayersDir, error) { +func ReadLayersDir(layersDir string, bp GroupBuildpack, logger log.Logger) (LayersDir, error) { path := filepath.Join(layersDir, launch.EscapeID(bp.ID)) logger.Debugf("Reading buildpack directory: %s", path) bpDir := LayersDir{ @@ -106,7 +107,7 @@ func Malformed(l Layer) bool { return err != nil } -func (d *LayersDir) NewLayer(name, buildpackAPI string, logger Logger) *Layer { +func (d *LayersDir) NewLayer(name, buildpackAPI string, logger log.Logger) *Layer { return &Layer{ layerDir: layerDir{ path: filepath.Join(d.Path, name), @@ -120,7 +121,7 @@ func (d *LayersDir) NewLayer(name, buildpackAPI string, logger Logger) *Layer { type Layer struct { // TODO: need to refactor so api and logger won't be part of this struct layerDir api string - logger Logger + logger log.Logger } type layerDir struct { diff --git a/cache.go b/cache.go index eed616d66..839daf470 100644 --- a/cache.go +++ b/cache.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/buildpacks/lifecycle/buildpack" + "github.com/buildpacks/lifecycle/log" "github.com/buildpacks/lifecycle/platform" ) @@ -127,7 +128,7 @@ func (e *Exporter) addSBOMCacheLayer(layersDir string, cacheStore Cache, origMet return nil } -func readLayersSBOM(layersDir string, bomType string, logger Logger) (LayerDir, error) { +func readLayersSBOM(layersDir string, bomType string, logger log.Logger) (LayerDir, error) { path := filepath.Join(layersDir, "sbom", bomType) _, err := ioutil.ReadDir(path) if err != nil { diff --git a/cache_test.go b/cache_test.go index 109424d42..ce8910b36 100644 --- a/cache_test.go +++ b/cache_test.go @@ -18,10 +18,10 @@ import ( "github.com/sclevine/spec/report" "github.com/buildpacks/lifecycle" - "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/cache" "github.com/buildpacks/lifecycle/layers" + "github.com/buildpacks/lifecycle/platform" h "github.com/buildpacks/lifecycle/testhelpers" "github.com/buildpacks/lifecycle/testmock" ) @@ -64,10 +64,10 @@ func testCache(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, err) exporter = &lifecycle.Exporter{ - PlatformAPI: api.Platform.Latest(), + PlatformAPI: platform.APIs.Latest(), Buildpacks: []buildpack.GroupBuildpack{ - {ID: "buildpack.id", API: api.Buildpack.Latest().String()}, - {ID: "other.buildpack.id", API: api.Buildpack.Latest().String()}, + {ID: "buildpack.id", API: buildpack.APIs.Latest().String()}, + {ID: "other.buildpack.id", API: buildpack.APIs.Latest().String()}, }, Logger: &log.Logger{Handler: logHandler, Level: level}, LayerFactory: layerFactory, diff --git a/cmd/exit.go b/cmd/exit.go index 405251e90..a8bc27a18 100644 --- a/cmd/exit.go +++ b/cmd/exit.go @@ -7,17 +7,8 @@ import ( ) const ( - // lifecycle errors not specific to any phase: 1-99 - CodeFailed = 1 // CodeFailed indicates generic lifecycle error - // 2: reserved - CodeInvalidArgs = 3 - // 4: CodeInvalidEnv - // 5: CodeNotFound - // 9: CodeFailedUpdate - - // API errors - CodeIncompatiblePlatformAPI = 11 - CodeIncompatibleBuildpackAPI = 12 + CodeForFailed = 1 // CodeForFailed indicates generic lifecycle error + CodeForInvalidArgs = 3 ) type ErrorFail struct { @@ -39,7 +30,7 @@ func FailCode(code int, action ...string) *ErrorFail { } func FailErr(err error, action ...string) *ErrorFail { - code := CodeFailed + code := CodeForFailed if err, ok := err.(*ErrorFail); ok { code = err.Code } @@ -58,7 +49,7 @@ func Exit(err error) { if err, ok := err.(*ErrorFail); ok { os.Exit(err.Code) } - os.Exit(CodeFailed) + os.Exit(CodeForFailed) } func ExitWithVersion() { diff --git a/cmd/flags.go b/cmd/flags.go deleted file mode 100644 index d6070e315..000000000 --- a/cmd/flags.go +++ /dev/null @@ -1,256 +0,0 @@ -package cmd - -import ( - "flag" - "os" - "path/filepath" - "strconv" - - "github.com/buildpacks/lifecycle/api" - "github.com/buildpacks/lifecycle/internal/str" -) - -var ( - DefaultAppDir = filepath.Join(rootDir, "workspace") - DefaultBuildpacksDir = filepath.Join(rootDir, "cnb", "buildpacks") - DefaultDeprecationMode = DeprecationModeWarn - DefaultLauncherPath = filepath.Join(rootDir, "cnb", "lifecycle", "launcher"+execExt) - DefaultLayersDir = filepath.Join(rootDir, "layers") - DefaultLogLevel = "info" - DefaultPlatformAPI = "0.3" - DefaultPlatformDir = filepath.Join(rootDir, "platform") - DefaultProcessType = "web" - DefaultStackPath = filepath.Join(rootDir, "cnb", "stack.toml") - - DefaultAnalyzedFile = "analyzed.toml" - DefaultGroupFile = "group.toml" - DefaultOrderFile = "order.toml" - DefaultPlanFile = "plan.toml" - DefaultProjectMetadataFile = "project-metadata.toml" - DefaultReportFile = "report.toml" - - PlaceholderAnalyzedPath = filepath.Join("", DefaultAnalyzedFile) - PlaceholderGroupPath = filepath.Join("", DefaultGroupFile) - PlaceholderPlanPath = filepath.Join("", DefaultPlanFile) - PlaceholderProjectMetadataPath = filepath.Join("", DefaultProjectMetadataFile) - PlaceholderReportPath = filepath.Join("", DefaultReportFile) - PlaceholderOrderPath = filepath.Join("", DefaultOrderFile) -) - -const ( - EnvAnalyzedPath = "CNB_ANALYZED_PATH" - EnvAppDir = "CNB_APP_DIR" - EnvBuildpacksDir = "CNB_BUILDPACKS_DIR" - EnvCacheDir = "CNB_CACHE_DIR" - EnvCacheImage = "CNB_CACHE_IMAGE" - EnvDeprecationMode = "CNB_DEPRECATION_MODE" - EnvGID = "CNB_GROUP_ID" - EnvGroupPath = "CNB_GROUP_PATH" - EnvLaunchCacheDir = "CNB_LAUNCH_CACHE_DIR" - EnvLayersDir = "CNB_LAYERS_DIR" - EnvLogLevel = "CNB_LOG_LEVEL" - EnvNoColor = "CNB_NO_COLOR" // defaults to false - EnvOrderPath = "CNB_ORDER_PATH" - EnvPlanPath = "CNB_PLAN_PATH" - EnvPlatformAPI = "CNB_PLATFORM_API" - EnvPlatformDir = "CNB_PLATFORM_DIR" - EnvPreviousImage = "CNB_PREVIOUS_IMAGE" - EnvProcessType = "CNB_PROCESS_TYPE" - EnvProjectMetadataPath = "CNB_PROJECT_METADATA_PATH" - EnvReportPath = "CNB_REPORT_PATH" - EnvRunImage = "CNB_RUN_IMAGE" - EnvSkipLayers = "CNB_ANALYZE_SKIP_LAYERS" // defaults to false - EnvSkipRestore = "CNB_SKIP_RESTORE" // defaults to false - EnvStackPath = "CNB_STACK_PATH" - EnvUID = "CNB_USER_ID" - EnvUseDaemon = "CNB_USE_DAEMON" // defaults to false -) - -var flagSet = flag.NewFlagSet("lifecycle", flag.ExitOnError) - -func FlagAnalyzedPath(analyzedPath *string) { - flagSet.StringVar(analyzedPath, "analyzed", EnvOrDefault(EnvAnalyzedPath, PlaceholderAnalyzedPath), "path to analyzed.toml") -} - -func DefaultAnalyzedPath(platformAPI, layersDir string) string { - return defaultPath(DefaultAnalyzedFile, platformAPI, layersDir) -} - -func FlagAppDir(appDir *string) { - flagSet.StringVar(appDir, "app", EnvOrDefault(EnvAppDir, DefaultAppDir), "path to app directory") -} - -func FlagBuildpacksDir(buildpacksDir *string) { - flagSet.StringVar(buildpacksDir, "buildpacks", EnvOrDefault(EnvBuildpacksDir, DefaultBuildpacksDir), "path to buildpacks directory") -} - -func FlagCacheDir(cacheDir *string) { - flagSet.StringVar(cacheDir, "cache-dir", os.Getenv(EnvCacheDir), "path to cache directory") -} - -func FlagCacheImage(cacheImage *string) { - flagSet.StringVar(cacheImage, "cache-image", os.Getenv(EnvCacheImage), "cache image tag name") -} - -func FlagGID(gid *int) { - flagSet.IntVar(gid, "gid", intEnv(EnvGID), "GID of user's group in the stack's build and run images") -} - -func FlagGroupPath(groupPath *string) { - flagSet.StringVar(groupPath, "group", EnvOrDefault(EnvGroupPath, PlaceholderGroupPath), "path to group.toml") -} - -func DefaultGroupPath(platformAPI, layersDir string) string { - return defaultPath(DefaultGroupFile, platformAPI, layersDir) -} - -func FlagLaunchCacheDir(launchCacheDir *string) { - flagSet.StringVar(launchCacheDir, "launch-cache", os.Getenv(EnvLaunchCacheDir), "path to launch cache directory") -} - -func FlagLauncherPath(launcherPath *string) { - flagSet.StringVar(launcherPath, "launcher", DefaultLauncherPath, "path to launcher binary") -} - -func FlagLayersDir(layersDir *string) { - flagSet.StringVar(layersDir, "layers", EnvOrDefault(EnvLayersDir, DefaultLayersDir), "path to layers directory") -} - -func FlagNoColor(skip *bool) { - flagSet.BoolVar(skip, "no-color", BoolEnv(EnvNoColor), "disable color output") -} - -func FlagOrderPath(orderPath *string) { - flagSet.StringVar(orderPath, "order", EnvOrDefault(EnvOrderPath, PlaceholderOrderPath), "path to order.toml") -} - -func DefaultOrderPath(platformAPI, layersDir string) string { - cnbOrderPath := filepath.Join(rootDir, "cnb", "order.toml") - - // prior to Platform API 0.6, the default is /cnb/order.toml - if api.MustParse(platformAPI).LessThan("0.6") { - return cnbOrderPath - } - - // the default is //order.toml or /cnb/order.toml if not present - layersOrderPath := filepath.Join(layersDir, "order.toml") - if _, err := os.Stat(layersOrderPath); os.IsNotExist(err) { - return cnbOrderPath - } - return layersOrderPath -} - -func FlagPlanPath(planPath *string) { - flagSet.StringVar(planPath, "plan", EnvOrDefault(EnvPlanPath, PlaceholderPlanPath), "path to plan.toml") -} - -func DefaultPlanPath(platformAPI, layersDir string) string { - return defaultPath(DefaultPlanFile, platformAPI, layersDir) -} - -func FlagPlatformDir(platformDir *string) { - flagSet.StringVar(platformDir, "platform", EnvOrDefault(EnvPlatformDir, DefaultPlatformDir), "path to platform directory") -} - -func FlagPreviousImage(image *string) { - flagSet.StringVar(image, "previous-image", os.Getenv(EnvPreviousImage), "reference to previous image") -} - -func FlagReportPath(reportPath *string) { - flagSet.StringVar(reportPath, "report", EnvOrDefault(EnvReportPath, PlaceholderReportPath), "path to report.toml") -} - -func DefaultReportPath(platformAPI, layersDir string) string { - return defaultPath(DefaultReportFile, platformAPI, layersDir) -} - -func FlagRunImage(runImage *string) { - flagSet.StringVar(runImage, "run-image", os.Getenv(EnvRunImage), "reference to run image") -} - -func FlagSkipLayers(skip *bool) { - flagSet.BoolVar(skip, "skip-layers", BoolEnv(EnvSkipLayers), "do not provide layer metadata to buildpacks") -} - -func FlagSkipRestore(skip *bool) { - flagSet.BoolVar(skip, "skip-restore", BoolEnv(EnvSkipRestore), "do not restore layers or layer metadata") -} - -func FlagStackPath(stackPath *string) { - flagSet.StringVar(stackPath, "stack", EnvOrDefault(EnvStackPath, DefaultStackPath), "path to stack.toml") -} - -func FlagTags(tags *str.Slice) { - flagSet.Var(tags, "tag", "additional tags") -} - -func FlagUID(uid *int) { - flagSet.IntVar(uid, "uid", intEnv(EnvUID), "UID of user in the stack's build and run images") -} - -func FlagUseDaemon(use *bool) { - flagSet.BoolVar(use, "daemon", BoolEnv(EnvUseDaemon), "export to docker daemon") -} - -func FlagVersion(version *bool) { - flagSet.BoolVar(version, "version", false, "show version") -} - -func FlagLogLevel(level *string) { - flagSet.StringVar(level, "log-level", EnvOrDefault(EnvLogLevel, DefaultLogLevel), "logging level") -} - -func FlagProjectMetadataPath(projectMetadataPath *string) { - flagSet.StringVar(projectMetadataPath, "project-metadata", EnvOrDefault(EnvProjectMetadataPath, PlaceholderProjectMetadataPath), "path to project-metadata.toml") -} - -func DefaultProjectMetadataPath(platformAPI, layersDir string) string { - return defaultPath(DefaultProjectMetadataFile, platformAPI, layersDir) -} - -func FlagProcessType(processType *string) { - flagSet.StringVar(processType, "process-type", os.Getenv(EnvProcessType), "default process type") -} - -func DeprecatedFlagRunImage(image *string) { - flagSet.StringVar(image, "image", "", "reference to run image") -} - -type StringSlice interface { - String() string - Set(value string) error -} - -func intEnv(k string) int { - v := os.Getenv(k) - d, err := strconv.Atoi(v) - if err != nil { - return 0 - } - return d -} - -func BoolEnv(k string) bool { - v := os.Getenv(k) - b, err := strconv.ParseBool(v) - if err != nil { - return false - } - return b -} - -func EnvOrDefault(key string, defaultVal string) string { - if envVal := os.Getenv(key); envVal != "" { - return envVal - } - return defaultVal -} - -func defaultPath(fileName, platformAPI, layersDir string) string { - if (api.MustParse(platformAPI).LessThan("0.5")) || (layersDir == "") { - // prior to platform api 0.5, the default directory was the working dir. - // layersDir is unset when this call comes from the rebaser - will be fixed as part of https://github.com/buildpacks/spec/issues/156 - return filepath.Join(".", fileName) - } - return filepath.Join(layersDir, fileName) // starting from platform api 0.5, the default directory is the layers dir. -} diff --git a/cmd/launcher/cli/env.go b/cmd/launcher/cli/env.go new file mode 100644 index 000000000..a63abaae0 --- /dev/null +++ b/cmd/launcher/cli/env.go @@ -0,0 +1,38 @@ +package cli + +import ( + "os" + "strconv" + + platform "github.com/buildpacks/lifecycle/platform/launch" +) + +const ( + EnvNoColor = "CNB_NO_COLOR" // defaults to false +) + +var ( + DefaultPlatformAPI = "0.3" + + appDir = envOrDefault(platform.EnvAppDir, platform.DefaultAppDir) + layersDir = envOrDefault(platform.EnvLayersDir, platform.DefaultLayersDir) + noColor = boolEnv(EnvNoColor) + platformAPI = envOrDefault(platform.EnvPlatformAPI, DefaultPlatformAPI) + processType = envOrDefault(platform.EnvProcessType, platform.DefaultProcessType) +) + +func boolEnv(k string) bool { + v := os.Getenv(k) + b, err := strconv.ParseBool(v) + if err != nil { + return false + } + return b +} + +func envOrDefault(key string, defaultVal string) string { + if envVal := os.Getenv(key); envVal != "" { + return envVal + } + return defaultVal +} diff --git a/cmd/launcher/cli/launcher.go b/cmd/launcher/cli/launcher.go index 2dddf03fb..c271a2dc9 100644 --- a/cmd/launcher/cli/launcher.go +++ b/cmd/launcher/cli/launcher.go @@ -9,23 +9,24 @@ import ( "github.com/heroku/color" "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/cmd" "github.com/buildpacks/lifecycle/env" "github.com/buildpacks/lifecycle/launch" + "github.com/buildpacks/lifecycle/platform/guard" platform "github.com/buildpacks/lifecycle/platform/launch" ) func RunLaunch() error { - color.Disable(cmd.BoolEnv(cmd.EnvNoColor)) + color.Disable(noColor) - platformAPI := cmd.EnvOrDefault(cmd.EnvPlatformAPI, cmd.DefaultPlatformAPI) - if err := cmd.VerifyPlatformAPI(platformAPI); err != nil { - cmd.Exit(err) + if err := platform.GuardAPI(platformAPI, cmd.DefaultLogger); err != nil { + return cmd.FailErrCode(err, platform.CodeForIncompatiblePlatformAPI, "set platform API") } p := platform.NewPlatform(platformAPI) var md launch.Metadata - if _, err := toml.DecodeFile(launch.GetMetadataFilePath(cmd.EnvOrDefault(cmd.EnvLayersDir, cmd.DefaultLayersDir)), &md); err != nil { + if _, err := toml.DecodeFile(launch.GetMetadataFilePath(layersDir), &md); err != nil { return cmd.FailErr(err, "read metadata") } if err := verifyBuildpackAPIs(md.Buildpacks); err != nil { @@ -36,8 +37,8 @@ func RunLaunch() error { launcher := &launch.Launcher{ DefaultProcessType: defaultProcessType, - LayersDir: cmd.EnvOrDefault(cmd.EnvLayersDir, cmd.DefaultLayersDir), - AppDir: cmd.EnvOrDefault(cmd.EnvAppDir, cmd.DefaultAppDir), + LayersDir: layersDir, + AppDir: appDir, PlatformAPI: p.API(), Processes: md.Processes, Buildpacks: md.Buildpacks, @@ -56,9 +57,9 @@ func RunLaunch() error { func defaultProcessType(platformAPI *api.Version, launchMD launch.Metadata) string { if platformAPI.LessThan("0.4") { - return cmd.EnvOrDefault(cmd.EnvProcessType, cmd.DefaultProcessType) + return processType } - if pType := os.Getenv(cmd.EnvProcessType); pType != "" { + if pType := os.Getenv(platform.EnvProcessType); pType != "" { cmd.DefaultLogger.Warnf("CNB_PROCESS_TYPE is not supported in Platform API %s", platformAPI) cmd.DefaultLogger.Warnf("Run with ENTRYPOINT '%s' to invoke the '%s' process type", pType, pType) } @@ -76,8 +77,8 @@ func verifyBuildpackAPIs(bps []launch.Buildpack) error { // but if for some reason we do, default to 0.2 bp.API = "0.2" } - if err := cmd.VerifyBuildpackAPI(bp.ID, bp.API); err != nil { - return err + if err := guard.BuildpackAPI(bp.ID, bp.API, buildpack.APIs, cmd.DefaultLogger); err != nil { + return cmd.FailErrCode(err, platform.CodeForIncompatibleBuildpackAPI, "set buildpack API") } } return nil diff --git a/cmd/lifecycle/analyzer.go b/cmd/lifecycle/analyzer.go index c8aa9b44c..3cffc35fa 100644 --- a/cmd/lifecycle/analyzer.go +++ b/cmd/lifecycle/analyzer.go @@ -11,6 +11,7 @@ import ( "github.com/buildpacks/lifecycle/auth" "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/cmd" + "github.com/buildpacks/lifecycle/cmd/lifecycle/cli" "github.com/buildpacks/lifecycle/internal/encoding" "github.com/buildpacks/lifecycle/platform" "github.com/buildpacks/lifecycle/priv" @@ -28,39 +29,39 @@ type analyzeCmd struct { func (a *analyzeCmd) DefineFlags() { switch { case a.platform.API().AtLeast("0.9"): - cmd.FlagAnalyzedPath(&a.AnalyzedPath) - cmd.FlagCacheImage(&a.CacheImageRef) - cmd.FlagGID(&a.GID) - cmd.FlagLaunchCacheDir(&a.LaunchCacheDir) - cmd.FlagLayersDir(&a.LayersDir) - cmd.FlagPreviousImage(&a.PreviousImageRef) - cmd.FlagRunImage(&a.RunImageRef) - cmd.FlagSkipLayers(&a.SkipLayers) - cmd.FlagStackPath(&a.StackPath) - cmd.FlagTags(&a.AdditionalTags) - cmd.FlagUID(&a.UID) - cmd.FlagUseDaemon(&a.UseDaemon) + cli.FlagAnalyzedPath(&a.AnalyzedPath) + cli.FlagCacheImage(&a.CacheImageRef) + cli.FlagGID(&a.GID) + cli.FlagLaunchCacheDir(&a.LaunchCacheDir) + cli.FlagLayersDir(&a.LayersDir) + cli.FlagPreviousImage(&a.PreviousImageRef) + cli.FlagRunImage(&a.RunImageRef) + cli.FlagSkipLayers(&a.SkipLayers) + cli.FlagStackPath(&a.StackPath) + cli.FlagTags(&a.AdditionalTags) + cli.FlagUID(&a.UID) + cli.FlagUseDaemon(&a.UseDaemon) case a.platform.API().AtLeast("0.7"): - cmd.FlagAnalyzedPath(&a.AnalyzedPath) - cmd.FlagCacheImage(&a.CacheImageRef) - cmd.FlagGID(&a.GID) - cmd.FlagLayersDir(&a.LayersDir) - cmd.FlagPreviousImage(&a.PreviousImageRef) - cmd.FlagRunImage(&a.RunImageRef) - cmd.FlagStackPath(&a.StackPath) - cmd.FlagTags(&a.AdditionalTags) - cmd.FlagUID(&a.UID) - cmd.FlagUseDaemon(&a.UseDaemon) + cli.FlagAnalyzedPath(&a.AnalyzedPath) + cli.FlagCacheImage(&a.CacheImageRef) + cli.FlagGID(&a.GID) + cli.FlagLayersDir(&a.LayersDir) + cli.FlagPreviousImage(&a.PreviousImageRef) + cli.FlagRunImage(&a.RunImageRef) + cli.FlagStackPath(&a.StackPath) + cli.FlagTags(&a.AdditionalTags) + cli.FlagUID(&a.UID) + cli.FlagUseDaemon(&a.UseDaemon) default: - cmd.FlagAnalyzedPath(&a.AnalyzedPath) - cmd.FlagCacheDir(&a.LegacyCacheDir) - cmd.FlagCacheImage(&a.CacheImageRef) - cmd.FlagGID(&a.GID) - cmd.FlagGroupPath(&a.LegacyGroupPath) - cmd.FlagLayersDir(&a.LayersDir) - cmd.FlagSkipLayers(&a.SkipLayers) - cmd.FlagUID(&a.UID) - cmd.FlagUseDaemon(&a.UseDaemon) + cli.FlagAnalyzedPath(&a.AnalyzedPath) + cli.FlagCacheDir(&a.LegacyCacheDir) + cli.FlagCacheImage(&a.CacheImageRef) + cli.FlagGID(&a.GID) + cli.FlagGroupPath(&a.LegacyGroupPath) + cli.FlagLayersDir(&a.LayersDir) + cli.FlagSkipLayers(&a.SkipLayers) + cli.FlagUID(&a.UID) + cli.FlagUseDaemon(&a.UseDaemon) } } @@ -68,14 +69,14 @@ func (a *analyzeCmd) DefineFlags() { func (a *analyzeCmd) Args(nargs int, args []string) error { if nargs != 1 { err := fmt.Errorf("received %d arguments, but expected 1", nargs) - return cmd.FailErrCode(err, cmd.CodeInvalidArgs, "parse arguments") + return cmd.FailErrCode(err, platform.CodeForInvalidArgs, "parse arguments") } a.AnalyzeInputs.OutputImageRef = args[0] var err error a.AnalyzeInputs, err = a.platform.ResolveAnalyze(a.AnalyzeInputs, cmd.DefaultLogger) if err != nil { - return cmd.FailErrCode(err, cmd.CodeInvalidArgs, "resolve inputs") + return cmd.FailErrCode(err, platform.CodeForInvalidArgs, "resolve inputs") } return nil } diff --git a/cmd/lifecycle/builder.go b/cmd/lifecycle/builder.go index ae3ca0931..a075eb285 100644 --- a/cmd/lifecycle/builder.go +++ b/cmd/lifecycle/builder.go @@ -8,6 +8,7 @@ import ( "github.com/buildpacks/lifecycle" "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/cmd" + "github.com/buildpacks/lifecycle/cmd/lifecycle/cli" "github.com/buildpacks/lifecycle/internal/encoding" "github.com/buildpacks/lifecycle/launch" "github.com/buildpacks/lifecycle/platform" @@ -33,26 +34,26 @@ type buildArgs struct { // DefineFlags defines the flags that are considered valid and reads their values (if provided). func (b *buildCmd) DefineFlags() { - cmd.FlagBuildpacksDir(&b.buildpacksDir) - cmd.FlagGroupPath(&b.groupPath) - cmd.FlagPlanPath(&b.planPath) - cmd.FlagLayersDir(&b.layersDir) - cmd.FlagAppDir(&b.appDir) - cmd.FlagPlatformDir(&b.platformDir) + cli.FlagBuildpacksDir(&b.buildpacksDir) + cli.FlagGroupPath(&b.groupPath) + cli.FlagPlanPath(&b.planPath) + cli.FlagLayersDir(&b.layersDir) + cli.FlagAppDir(&b.appDir) + cli.FlagPlatformDir(&b.platformDir) } // Args validates arguments and flags, and fills in default values. func (b *buildCmd) Args(nargs int, args []string) error { if nargs != 0 { - return cmd.FailErrCode(errors.New("received unexpected arguments"), cmd.CodeInvalidArgs, "parse arguments") + return cmd.FailErrCode(errors.New("received unexpected arguments"), platform.CodeForInvalidArgs, "parse arguments") } - if b.groupPath == cmd.PlaceholderGroupPath { - b.groupPath = cmd.DefaultGroupPath(b.platform.API().String(), b.layersDir) + if b.groupPath == platform.PlaceholderGroupPath { + b.groupPath = cli.DefaultGroupPath(b.platform.API().String(), b.layersDir) } - if b.planPath == cmd.PlaceholderPlanPath { - b.planPath = cmd.DefaultPlanPath(b.platform.API().String(), b.layersDir) + if b.planPath == platform.PlaceholderPlanPath { + b.planPath = cli.DefaultPlanPath(b.platform.API().String(), b.layersDir) } return nil diff --git a/cmd/command.go b/cmd/lifecycle/cli/command.go similarity index 80% rename from cmd/command.go rename to cmd/lifecycle/cli/command.go index 50082776a..f556cfade 100644 --- a/cmd/command.go +++ b/cmd/lifecycle/cli/command.go @@ -1,9 +1,11 @@ -package cmd +package cli import ( "io/ioutil" "log" "os" + + "github.com/buildpacks/lifecycle/cmd" ) // Command defines the interface for running the lifecycle phases @@ -36,27 +38,27 @@ func Run(c Command, asSubcommand bool) { if asSubcommand { if err := flagSet.Parse(os.Args[2:]); err != nil { // flagSet exits on error, we shouldn't get here - Exit(err) + cmd.Exit(err) } } else { if err := flagSet.Parse(os.Args[1:]); err != nil { // flagSet exits on error, we shouldn't get here - Exit(err) + cmd.Exit(err) } } - DisableColor(noColor) + cmd.DisableColor(noColor) if printVersion { - ExitWithVersion() + cmd.ExitWithVersion() } - if err := SetLogLevel(logLevel); err != nil { - Exit(err) + if err := cmd.SetLogLevel(logLevel); err != nil { + cmd.Exit(err) } if err := c.Args(flagSet.NArg(), flagSet.Args()); err != nil { - Exit(err) + cmd.Exit(err) } if err := c.Privileges(); err != nil { - Exit(err) + cmd.Exit(err) } - Exit(c.Exec()) + cmd.Exit(c.Exec()) } diff --git a/cmd/lifecycle/cli/env.go b/cmd/lifecycle/cli/env.go new file mode 100644 index 000000000..27ad76717 --- /dev/null +++ b/cmd/lifecycle/cli/env.go @@ -0,0 +1,69 @@ +package cli + +import ( + "os" + "strconv" + + "github.com/buildpacks/lifecycle/platform" +) + +const ( + EnvNoColor = "CNB_NO_COLOR" // defaults to false +) + +var ( + PlatformAPI = envOrDefault(platform.EnvPlatformAPI, DefaultPlatformAPI) + + DefaultLogLevel = "info" + DefaultPlatformAPI = "0.3" + + analyzedPath = envOrDefault(platform.EnvAnalyzedPath, platform.PlaceholderAnalyzedPath) + appDir = envOrDefault(platform.EnvAppDir, platform.DefaultAppDir) + cacheDir = os.Getenv(platform.EnvCacheDir) + cacheImage = os.Getenv(platform.EnvCacheImage) + buildpacksDir = envOrDefault(platform.EnvBuildpacksDir, platform.DefaultBuildpacksDir) + groupPath = envOrDefault(platform.EnvGroupPath, platform.PlaceholderGroupPath) + gid = intEnv(platform.EnvGID) + launchCacheDir = os.Getenv(platform.EnvLaunchCacheDir) + layersDir = envOrDefault(platform.EnvLayersDir, platform.DefaultLayersDir) + logLevel = envOrDefault(platform.EnvLogLevel, DefaultLogLevel) + noColor = boolEnv(EnvNoColor) + orderPath = envOrDefault(platform.EnvOrderPath, platform.PlaceholderOrderPath) + planPath = envOrDefault(platform.EnvPlanPath, platform.PlaceholderPlanPath) + platformDir = envOrDefault(platform.EnvPlatformDir, platform.DefaultPlatformDir) + previousImage = os.Getenv(platform.EnvPreviousImage) + processType = os.Getenv(platform.EnvProcessType) + projectMetadataPath = envOrDefault(platform.EnvProjectMetadataPath, platform.PlaceholderProjectMetadataPath) + reportPath = envOrDefault(platform.EnvReportPath, platform.PlaceholderReportPath) + runImage = os.Getenv(platform.EnvRunImage) + skipLayers = boolEnv(platform.EnvSkipLayers) + skipRestore = boolEnv(platform.EnvSkipRestore) + stackPath = envOrDefault(platform.EnvStackPath, platform.DefaultStackPath) + uid = intEnv(platform.EnvUID) + useDaemon = boolEnv(platform.EnvUseDaemon) +) + +func boolEnv(k string) bool { + v := os.Getenv(k) + b, err := strconv.ParseBool(v) + if err != nil { + return false + } + return b +} + +func envOrDefault(key string, defaultVal string) string { + if envVal := os.Getenv(key); envVal != "" { + return envVal + } + return defaultVal +} + +func intEnv(k string) int { + v := os.Getenv(k) + d, err := strconv.Atoi(v) + if err != nil { + return 0 + } + return d +} diff --git a/cmd/lifecycle/cli/flags.go b/cmd/lifecycle/cli/flags.go new file mode 100644 index 000000000..7878ebf74 --- /dev/null +++ b/cmd/lifecycle/cli/flags.go @@ -0,0 +1,172 @@ +package cli + +import ( + "flag" + "os" + "path/filepath" + + "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/internal/str" + "github.com/buildpacks/lifecycle/platform" +) + +var flagSet = flag.NewFlagSet("lifecycle", flag.ExitOnError) + +func FlagAnalyzedPath(provided *string) { + flagSet.StringVar(provided, "analyzed", analyzedPath, "path to analyzed.toml") +} + +func DefaultAnalyzedPath(platformAPI, layersDir string) string { + return defaultPath(platform.DefaultAnalyzedFile, platformAPI, layersDir) +} + +func FlagAppDir(provided *string) { + flagSet.StringVar(provided, "app", appDir, "path to app directory") +} + +func FlagBuildpacksDir(provided *string) { + flagSet.StringVar(provided, "buildpacks", buildpacksDir, "path to buildpacks directory") +} + +func FlagCacheDir(provided *string) { + flagSet.StringVar(provided, "cache-dir", cacheDir, "path to cache directory") +} + +func FlagCacheImage(provided *string) { + flagSet.StringVar(provided, "cache-image", cacheImage, "cache image tag name") +} + +func FlagGID(provided *int) { + flagSet.IntVar(provided, "gid", gid, "GID of user's group in the stack's build and run images") +} + +func FlagGroupPath(provided *string) { + flagSet.StringVar(provided, "group", groupPath, "path to group.toml") +} + +func DefaultGroupPath(platformAPI, layersDir string) string { + return defaultPath(platform.DefaultGroupFile, platformAPI, layersDir) +} + +func FlagLaunchCacheDir(provided *string) { + flagSet.StringVar(provided, "launch-cache", launchCacheDir, "path to launch cache directory") +} + +func FlagLauncherPath(launcherPath *string) { + flagSet.StringVar(launcherPath, "launcher", platform.DefaultLauncherPath, "path to launcher binary") +} + +func FlagLayersDir(provided *string) { + flagSet.StringVar(provided, "layers", layersDir, "path to layers directory") +} + +func FlagLogLevel(provided *string) { + flagSet.StringVar(provided, "log-level", logLevel, "logging level") +} + +func FlagNoColor(provided *bool) { + flagSet.BoolVar(provided, "no-color", noColor, "disable color output") +} + +func FlagOrderPath(provided *string) { + flagSet.StringVar(provided, "order", orderPath, "path to order.toml") +} + +// TODO: remove when https://github.com/buildpacks/lifecycle/pull/860 is merged +func DefaultOrderPath(platformAPI, layersDir string) string { + cnbOrderPath := filepath.Join(rootDir, "cnb", "order.toml") + + // prior to Platform API 0.6, the default is /cnb/order.toml + if api.MustParse(platformAPI).LessThan("0.6") { + return cnbOrderPath + } + + // the default is //order.toml or /cnb/order.toml if not present + layersOrderPath := filepath.Join(layersDir, "order.toml") + if _, err := os.Stat(layersOrderPath); os.IsNotExist(err) { + return cnbOrderPath + } + return layersOrderPath +} + +func FlagPlanPath(provided *string) { + flagSet.StringVar(provided, "plan", planPath, "path to plan.toml") +} + +func DefaultPlanPath(platformAPI, layersDir string) string { + return defaultPath(platform.DefaultPlanFile, platformAPI, layersDir) +} + +func FlagPlatformDir(provided *string) { + flagSet.StringVar(provided, "platform", platformDir, "path to platform directory") +} + +func FlagPreviousImage(provided *string) { + flagSet.StringVar(provided, "previous-image", previousImage, "reference to previous image") +} + +func FlagProcessType(provided *string) { + flagSet.StringVar(provided, "process-type", processType, "default process type") +} + +func FlagProjectMetadataPath(provided *string) { + flagSet.StringVar(provided, "project-metadata", projectMetadataPath, "path to project-metadata.toml") +} + +func DefaultProjectMetadataPath(platformAPI, layersDir string) string { + return defaultPath(platform.DefaultProjectMetadataFile, platformAPI, layersDir) +} + +func FlagReportPath(provided *string) { + flagSet.StringVar(provided, "report", reportPath, "path to report.toml") +} + +func DefaultReportPath(platformAPI, layersDir string) string { + return defaultPath(platform.DefaultReportFile, platformAPI, layersDir) +} + +func FlagRunImage(provided *string) { + flagSet.StringVar(provided, "run-image", runImage, "reference to run image") +} + +func FlagSkipLayers(provided *bool) { + flagSet.BoolVar(provided, "skip-layers", skipLayers, "do not provide layer metadata to buildpacks") +} + +func FlagSkipRestore(provided *bool) { + flagSet.BoolVar(provided, "skip-restore", skipRestore, "do not restore layers or layer metadata") +} + +func FlagStackPath(provided *string) { + flagSet.StringVar(provided, "stack", stackPath, "path to stack.toml") +} + +func FlagTags(tags *str.Slice) { + flagSet.Var(tags, "tag", "additional tags") +} + +func FlagUID(provided *int) { + flagSet.IntVar(provided, "uid", uid, "UID of user in the stack's build and run images") +} + +func FlagUseDaemon(provided *bool) { + flagSet.BoolVar(provided, "daemon", useDaemon, "export to docker daemon") +} + +func FlagVersion(provided *bool) { + flagSet.BoolVar(provided, "version", false, "show version") +} + +func DeprecatedFlagRunImage(provided *string) { + flagSet.StringVar(provided, "image", "", "reference to run image") +} + +// TODO: remove when https://github.com/buildpacks/lifecycle/pull/860 is merged +func defaultPath(fileName, platformAPI, layersDir string) string { + if (api.MustParse(platformAPI).LessThan("0.5")) || (layersDir == "") { + // prior to platform api 0.5, the default directory was the working dir. + // layersDir is unset when this call comes from the rebaser - will be fixed as part of https://github.com/buildpacks/spec/issues/156 + return filepath.Join(".", fileName) + } + return filepath.Join(layersDir, fileName) // starting from platform api 0.5, the default directory is the layers dir. +} diff --git a/cmd/lifecycle/cli/flags_unix.go b/cmd/lifecycle/cli/flags_unix.go new file mode 100644 index 000000000..900e20a6e --- /dev/null +++ b/cmd/lifecycle/cli/flags_unix.go @@ -0,0 +1,8 @@ +//go:build linux || darwin +// +build linux darwin + +package cli + +const ( + rootDir = "/" +) diff --git a/cmd/lifecycle/cli/flags_windows.go b/cmd/lifecycle/cli/flags_windows.go new file mode 100644 index 000000000..88d8a4979 --- /dev/null +++ b/cmd/lifecycle/cli/flags_windows.go @@ -0,0 +1,5 @@ +package cli + +const ( + rootDir = `c:\` +) diff --git a/cmd/lifecycle/creator.go b/cmd/lifecycle/creator.go index ce55b686a..e2b9528b4 100644 --- a/cmd/lifecycle/creator.go +++ b/cmd/lifecycle/creator.go @@ -13,6 +13,7 @@ import ( "github.com/buildpacks/lifecycle/auth" "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/cmd" + "github.com/buildpacks/lifecycle/cmd/lifecycle/cli" "github.com/buildpacks/lifecycle/image" "github.com/buildpacks/lifecycle/internal/str" "github.com/buildpacks/lifecycle/platform" @@ -51,32 +52,32 @@ type createCmd struct { // DefineFlags defines the flags that are considered valid and reads their values (if provided). func (c *createCmd) DefineFlags() { - cmd.FlagAppDir(&c.appDir) - cmd.FlagBuildpacksDir(&c.buildpacksDir) - cmd.FlagCacheDir(&c.cacheDir) - cmd.FlagCacheImage(&c.cacheImageRef) - cmd.FlagGID(&c.gid) - cmd.FlagLaunchCacheDir(&c.launchCacheDir) - cmd.FlagLauncherPath(&c.launcherPath) - cmd.FlagLayersDir(&c.layersDir) - cmd.FlagOrderPath(&c.orderPath) - cmd.FlagPlatformDir(&c.platformDir) - cmd.FlagPreviousImage(&c.previousImageRef) - cmd.FlagReportPath(&c.reportPath) - cmd.FlagRunImage(&c.runImageRef) - cmd.FlagSkipRestore(&c.skipRestore) - cmd.FlagStackPath(&c.stackPath) - cmd.FlagUID(&c.uid) - cmd.FlagUseDaemon(&c.useDaemon) - cmd.FlagTags(&c.additionalTags) - cmd.FlagProjectMetadataPath(&c.projectMetadataPath) - cmd.FlagProcessType(&c.processType) + cli.FlagAppDir(&c.appDir) + cli.FlagBuildpacksDir(&c.buildpacksDir) + cli.FlagCacheDir(&c.cacheDir) + cli.FlagCacheImage(&c.cacheImageRef) + cli.FlagGID(&c.gid) + cli.FlagLaunchCacheDir(&c.launchCacheDir) + cli.FlagLauncherPath(&c.launcherPath) + cli.FlagLayersDir(&c.layersDir) + cli.FlagOrderPath(&c.orderPath) + cli.FlagPlatformDir(&c.platformDir) + cli.FlagPreviousImage(&c.previousImageRef) + cli.FlagReportPath(&c.reportPath) + cli.FlagRunImage(&c.runImageRef) + cli.FlagSkipRestore(&c.skipRestore) + cli.FlagStackPath(&c.stackPath) + cli.FlagUID(&c.uid) + cli.FlagUseDaemon(&c.useDaemon) + cli.FlagTags(&c.additionalTags) + cli.FlagProjectMetadataPath(&c.projectMetadataPath) + cli.FlagProcessType(&c.processType) } // Args validates arguments and flags, and fills in default values. func (c *createCmd) Args(nargs int, args []string) error { if nargs != 1 { - return cmd.FailErrCode(fmt.Errorf("received %d arguments, but expected 1", nargs), cmd.CodeInvalidArgs, "parse arguments") + return cmd.FailErrCode(fmt.Errorf("received %d arguments, but expected 1", nargs), platform.CodeForInvalidArgs, "parse arguments") } c.outputImageRef = args[0] @@ -94,34 +95,34 @@ func (c *createCmd) Args(nargs int, args []string) error { } if err := image.ValidateDestinationTags(c.useDaemon, append(c.additionalTags, c.outputImageRef)...); err != nil { - return cmd.FailErrCode(err, cmd.CodeInvalidArgs, "validate image tag(s)") + return cmd.FailErrCode(err, platform.CodeForInvalidArgs, "validate image tag(s)") } - if c.projectMetadataPath == cmd.PlaceholderProjectMetadataPath { - c.projectMetadataPath = cmd.DefaultProjectMetadataPath(c.platform.API().String(), c.layersDir) + if c.projectMetadataPath == platform.PlaceholderProjectMetadataPath { + c.projectMetadataPath = cli.DefaultProjectMetadataPath(c.platform.API().String(), c.layersDir) } - if c.reportPath == cmd.PlaceholderReportPath { - c.reportPath = cmd.DefaultReportPath(c.platform.API().String(), c.layersDir) + if c.reportPath == platform.PlaceholderReportPath { + c.reportPath = cli.DefaultReportPath(c.platform.API().String(), c.layersDir) } - if c.orderPath == cmd.PlaceholderOrderPath { - c.orderPath = cmd.DefaultOrderPath(c.platform.API().String(), c.layersDir) + if c.orderPath == platform.PlaceholderOrderPath { + c.orderPath = cli.DefaultOrderPath(c.platform.API().String(), c.layersDir) } var err error c.stackMD, err = readStack(c.stackPath) if err != nil { - return cmd.FailErrCode(err, cmd.CodeInvalidArgs, "parse stack metadata") + return cmd.FailErrCode(err, platform.CodeForInvalidArgs, "parse stack metadata") } c.targetRegistry, err = parseRegistry(c.outputImageRef) if err != nil { - return cmd.FailErrCode(err, cmd.CodeInvalidArgs, "parse target registry") + return cmd.FailErrCode(err, platform.CodeForInvalidArgs, "parse target registry") } if err := c.populateRunImage(); err != nil { - return cmd.FailErrCode(err, cmd.CodeInvalidArgs, "populate run image") + return cmd.FailErrCode(err, platform.CodeForInvalidArgs, "populate run image") } return nil diff --git a/cmd/lifecycle/detector.go b/cmd/lifecycle/detector.go index 9beb94883..ecfa652d3 100644 --- a/cmd/lifecycle/detector.go +++ b/cmd/lifecycle/detector.go @@ -7,8 +7,10 @@ import ( "github.com/buildpacks/lifecycle" "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/cmd" + "github.com/buildpacks/lifecycle/cmd/lifecycle/cli" "github.com/buildpacks/lifecycle/internal/encoding" "github.com/buildpacks/lifecycle/platform" + "github.com/buildpacks/lifecycle/platform/guard" "github.com/buildpacks/lifecycle/priv" ) @@ -34,31 +36,31 @@ type detectArgs struct { // DefineFlags defines the flags that are considered valid and reads their values (if provided). func (d *detectCmd) DefineFlags() { - cmd.FlagBuildpacksDir(&d.buildpacksDir) - cmd.FlagAppDir(&d.appDir) - cmd.FlagLayersDir(&d.layersDir) - cmd.FlagPlatformDir(&d.platformDir) - cmd.FlagOrderPath(&d.orderPath) - cmd.FlagGroupPath(&d.groupPath) - cmd.FlagPlanPath(&d.planPath) + cli.FlagBuildpacksDir(&d.buildpacksDir) + cli.FlagAppDir(&d.appDir) + cli.FlagLayersDir(&d.layersDir) + cli.FlagPlatformDir(&d.platformDir) + cli.FlagOrderPath(&d.orderPath) + cli.FlagGroupPath(&d.groupPath) + cli.FlagPlanPath(&d.planPath) } // Args validates arguments and flags, and fills in default values. func (d *detectCmd) Args(nargs int, args []string) error { if nargs != 0 { - return cmd.FailErrCode(errors.New("received unexpected arguments"), cmd.CodeInvalidArgs, "parse arguments") + return cmd.FailErrCode(errors.New("received unexpected arguments"), platform.CodeForInvalidArgs, "parse arguments") } - if d.groupPath == cmd.PlaceholderGroupPath { - d.groupPath = cmd.DefaultGroupPath(d.platform.API().String(), d.layersDir) + if d.groupPath == platform.PlaceholderGroupPath { + d.groupPath = cli.DefaultGroupPath(d.platform.API().String(), d.layersDir) } - if d.planPath == cmd.PlaceholderPlanPath { - d.planPath = cmd.DefaultPlanPath(d.platform.API().String(), d.layersDir) + if d.planPath == platform.PlaceholderPlanPath { + d.planPath = cli.DefaultPlanPath(d.platform.API().String(), d.layersDir) } - if d.orderPath == cmd.PlaceholderOrderPath { - d.orderPath = cmd.DefaultOrderPath(d.platform.API().String(), d.layersDir) + if d.orderPath == platform.PlaceholderOrderPath { + d.orderPath = cli.DefaultOrderPath(d.platform.API().String(), d.layersDir) } return nil @@ -131,12 +133,12 @@ func (da detectArgs) verifyBuildpackApis(order buildpack.Order) error { } for _, group := range order { for _, groupBp := range group.Group { - buildpack, err := store.Lookup(groupBp.ID, groupBp.Version) + bp, err := store.Lookup(groupBp.ID, groupBp.Version) if err != nil { return cmd.FailErr(err, fmt.Sprintf("lookup buildpack.toml for buildpack '%s'", groupBp.String())) } - if err := cmd.VerifyBuildpackAPI(groupBp.String(), buildpack.ConfigFile().API); err != nil { - return err + if err := guard.BuildpackAPI(groupBp.String(), bp.ConfigFile().API, buildpack.APIs, cmd.DefaultLogger); err != nil { + return cmd.FailErrCode(err, platform.CodeForIncompatibleBuildpackAPI, "set buildpack API") } } } diff --git a/cmd/lifecycle/exporter.go b/cmd/lifecycle/exporter.go index 8f3747776..b30e31a0c 100644 --- a/cmd/lifecycle/exporter.go +++ b/cmd/lifecycle/exporter.go @@ -21,9 +21,11 @@ import ( "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/cache" "github.com/buildpacks/lifecycle/cmd" + "github.com/buildpacks/lifecycle/cmd/lifecycle/cli" "github.com/buildpacks/lifecycle/image" "github.com/buildpacks/lifecycle/internal/encoding" "github.com/buildpacks/lifecycle/layers" + "github.com/buildpacks/lifecycle/log" "github.com/buildpacks/lifecycle/platform" "github.com/buildpacks/lifecycle/priv" ) @@ -69,30 +71,30 @@ type exportArgs struct { // DefineFlags defines the flags that are considered valid and reads their values (if provided). func (e *exportCmd) DefineFlags() { - cmd.FlagAnalyzedPath(&e.analyzedPath) - cmd.FlagAppDir(&e.appDir) - cmd.FlagCacheDir(&e.cacheDir) - cmd.FlagCacheImage(&e.cacheImageTag) - cmd.FlagGID(&e.gid) - cmd.FlagGroupPath(&e.groupPath) - cmd.FlagLaunchCacheDir(&e.launchCacheDir) - cmd.FlagLauncherPath(&e.launcherPath) - cmd.FlagLayersDir(&e.layersDir) - cmd.FlagProcessType(&e.processType) - cmd.FlagProjectMetadataPath(&e.projectMetadataPath) - cmd.FlagReportPath(&e.reportPath) - cmd.FlagRunImage(&e.runImageRef) - cmd.FlagStackPath(&e.stackPath) - cmd.FlagUID(&e.uid) - cmd.FlagUseDaemon(&e.useDaemon) - - cmd.DeprecatedFlagRunImage(&e.deprecatedRunImageRef) + cli.FlagAnalyzedPath(&e.analyzedPath) + cli.FlagAppDir(&e.appDir) + cli.FlagCacheDir(&e.cacheDir) + cli.FlagCacheImage(&e.cacheImageTag) + cli.FlagGID(&e.gid) + cli.FlagGroupPath(&e.groupPath) + cli.FlagLaunchCacheDir(&e.launchCacheDir) + cli.FlagLauncherPath(&e.launcherPath) + cli.FlagLayersDir(&e.layersDir) + cli.FlagProcessType(&e.processType) + cli.FlagProjectMetadataPath(&e.projectMetadataPath) + cli.FlagReportPath(&e.reportPath) + cli.FlagRunImage(&e.runImageRef) + cli.FlagStackPath(&e.stackPath) + cli.FlagUID(&e.uid) + cli.FlagUseDaemon(&e.useDaemon) + + cli.DeprecatedFlagRunImage(&e.deprecatedRunImageRef) } // Args validates arguments and flags, and fills in default values. func (e *exportCmd) Args(nargs int, args []string) error { if nargs == 0 { - return cmd.FailErrCode(errors.New("at least one image argument is required"), cmd.CodeInvalidArgs, "parse arguments") + return cmd.FailErrCode(errors.New("at least one image argument is required"), platform.CodeForInvalidArgs, "parse arguments") } e.imageNames = args @@ -106,27 +108,27 @@ func (e *exportCmd) Args(nargs int, args []string) error { } if err := image.ValidateDestinationTags(e.useDaemon, e.imageNames...); err != nil { - return cmd.FailErrCode(err, cmd.CodeInvalidArgs, "validate image tag(s)") + return cmd.FailErrCode(err, platform.CodeForInvalidArgs, "validate image tag(s)") } if err := e.validateRunImageInput(); err != nil { - return cmd.FailErrCode(err, cmd.CodeInvalidArgs, "validate run image input") + return cmd.FailErrCode(err, platform.CodeForInvalidArgs, "validate run image input") } - if e.analyzedPath == cmd.PlaceholderAnalyzedPath { - e.analyzedPath = cmd.DefaultAnalyzedPath(e.platform.API().String(), e.layersDir) + if e.analyzedPath == platform.PlaceholderAnalyzedPath { + e.analyzedPath = cli.DefaultAnalyzedPath(e.platform.API().String(), e.layersDir) } - if e.groupPath == cmd.PlaceholderGroupPath { - e.groupPath = cmd.DefaultGroupPath(e.platform.API().String(), e.layersDir) + if e.groupPath == platform.PlaceholderGroupPath { + e.groupPath = cli.DefaultGroupPath(e.platform.API().String(), e.layersDir) } - if e.projectMetadataPath == cmd.PlaceholderProjectMetadataPath { - e.projectMetadataPath = cmd.DefaultProjectMetadataPath(e.platform.API().String(), e.layersDir) + if e.projectMetadataPath == platform.PlaceholderProjectMetadataPath { + e.projectMetadataPath = cli.DefaultProjectMetadataPath(e.platform.API().String(), e.layersDir) } - if e.reportPath == cmd.PlaceholderReportPath { - e.reportPath = cmd.DefaultReportPath(e.platform.API().String(), e.layersDir) + if e.reportPath == platform.PlaceholderReportPath { + e.reportPath = cli.DefaultReportPath(e.platform.API().String(), e.layersDir) } if e.deprecatedRunImageRef != "" { @@ -136,21 +138,21 @@ func (e *exportCmd) Args(nargs int, args []string) error { var err error e.analyzedMD, err = parseAnalyzedMD(cmd.DefaultLogger, e.analyzedPath) if err != nil { - return cmd.FailErrCode(err, cmd.CodeInvalidArgs, "parse analyzed metadata") + return cmd.FailErrCode(err, platform.CodeForInvalidArgs, "parse analyzed metadata") } e.stackMD, err = readStack(e.stackPath) if err != nil { - return cmd.FailErrCode(err, cmd.CodeInvalidArgs, "parse stack metadata") + return cmd.FailErrCode(err, platform.CodeForInvalidArgs, "parse stack metadata") } e.targetRegistry, err = parseRegistry(e.imageNames[0]) if err != nil { - return cmd.FailErrCode(err, cmd.CodeInvalidArgs, "parse target registry") + return cmd.FailErrCode(err, platform.CodeForInvalidArgs, "parse target registry") } if err := e.populateRunImageRefIfNeeded(); err != nil { - return cmd.FailErrCode(err, cmd.CodeInvalidArgs, "populate run image") + return cmd.FailErrCode(err, platform.CodeForInvalidArgs, "populate run image") } return nil @@ -249,11 +251,11 @@ func (e *exportCmd) populateRunImageRefIfNeeded() error { func (e *exportCmd) validateRunImageInput() error { switch { - case e.supportsRunImage() && e.deprecatedRunImageRef != "" && e.runImageRef != os.Getenv(cmd.EnvRunImage): + case e.supportsRunImage() && e.deprecatedRunImageRef != "" && e.runImageRef != os.Getenv(platform.EnvRunImage): return errors.New("supply only one of -run-image or (deprecated) -image") case !e.supportsRunImage() && e.deprecatedRunImageRef != "": return errors.New("-image is unsupported") - case !e.supportsRunImage() && e.runImageRef != os.Getenv(cmd.EnvRunImage): + case !e.supportsRunImage() && e.runImageRef != os.Getenv(platform.EnvRunImage): return errors.New("-run-image is unsupported") default: return nil @@ -423,7 +425,7 @@ func launcherConfig(launcherPath string) lifecycle.LauncherConfig { } } -func parseAnalyzedMD(logger lifecycle.Logger, path string) (platform.AnalyzedMetadata, error) { +func parseAnalyzedMD(logger log.Logger, path string) (platform.AnalyzedMetadata, error) { var analyzedMD platform.AnalyzedMetadata _, err := toml.DecodeFile(path, &analyzedMD) diff --git a/cmd/lifecycle/main.go b/cmd/lifecycle/main.go index 19708ea0f..96ebf3357 100644 --- a/cmd/lifecycle/main.go +++ b/cmd/lifecycle/main.go @@ -17,41 +17,43 @@ import ( "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/cache" "github.com/buildpacks/lifecycle/cmd" + "github.com/buildpacks/lifecycle/cmd/lifecycle/cli" + "github.com/buildpacks/lifecycle/log" "github.com/buildpacks/lifecycle/platform" + "github.com/buildpacks/lifecycle/platform/guard" ) type Platform interface { API() *api.Version CodeFor(errType platform.LifecycleExitError) int - ResolveAnalyze(inputs platform.AnalyzeInputs, logger platform.Logger) (platform.AnalyzeInputs, error) + ResolveAnalyze(inputs platform.AnalyzeInputs, logger log.Logger) (platform.AnalyzeInputs, error) } func main() { - platformAPI := cmd.EnvOrDefault(cmd.EnvPlatformAPI, cmd.DefaultPlatformAPI) - if err := cmd.VerifyPlatformAPI(platformAPI); err != nil { - cmd.Exit(err) + if err := platform.GuardAPI(cli.PlatformAPI, cmd.DefaultLogger); err != nil { + cmd.Exit(cmd.FailErrCode(err, platform.CodeForIncompatiblePlatformAPI, "set platform API")) } - p := platform.NewPlatform(platformAPI) + p := platform.NewPlatform(cli.PlatformAPI) switch strings.TrimSuffix(filepath.Base(os.Args[0]), filepath.Ext(os.Args[0])) { case "detector": - cmd.Run(&detectCmd{detectArgs: detectArgs{platform: p}}, false) + cli.Run(&detectCmd{detectArgs: detectArgs{platform: p}}, false) case "analyzer": - cmd.Run(&analyzeCmd{platform: p}, false) + cli.Run(&analyzeCmd{platform: p}, false) case "restorer": - cmd.Run(&restoreCmd{restoreArgs: restoreArgs{platform: p}}, false) + cli.Run(&restoreCmd{restoreArgs: restoreArgs{platform: p}}, false) case "builder": - cmd.Run(&buildCmd{buildArgs: buildArgs{platform: p}}, false) + cli.Run(&buildCmd{buildArgs: buildArgs{platform: p}}, false) case "exporter": - cmd.Run(&exportCmd{exportArgs: exportArgs{platform: p}}, false) + cli.Run(&exportCmd{exportArgs: exportArgs{platform: p}}, false) case "rebaser": - cmd.Run(&rebaseCmd{platform: p}, false) + cli.Run(&rebaseCmd{platform: p}, false) case "creator": - cmd.Run(&createCmd{platform: p}, false) + cli.Run(&createCmd{platform: p}, false) default: if len(os.Args) < 2 { - cmd.Exit(cmd.FailCode(cmd.CodeInvalidArgs, "parse arguments")) + cmd.Exit(cmd.FailCode(platform.CodeForInvalidArgs, "parse arguments")) } if os.Args[1] == "-version" { cmd.ExitWithVersion() @@ -60,25 +62,25 @@ func main() { } } -func subcommand(platform Platform) { +func subcommand(p Platform) { phase := filepath.Base(os.Args[1]) switch phase { case "detect": - cmd.Run(&detectCmd{detectArgs: detectArgs{platform: platform}}, true) + cli.Run(&detectCmd{detectArgs: detectArgs{platform: p}}, true) case "analyze": - cmd.Run(&analyzeCmd{platform: platform}, true) + cli.Run(&analyzeCmd{platform: p}, true) case "restore": - cmd.Run(&restoreCmd{restoreArgs: restoreArgs{platform: platform}}, true) + cli.Run(&restoreCmd{restoreArgs: restoreArgs{platform: p}}, true) case "build": - cmd.Run(&buildCmd{buildArgs: buildArgs{platform: platform}}, true) + cli.Run(&buildCmd{buildArgs: buildArgs{platform: p}}, true) case "export": - cmd.Run(&exportCmd{exportArgs: exportArgs{platform: platform}}, true) + cli.Run(&exportCmd{exportArgs: exportArgs{platform: p}}, true) case "rebase": - cmd.Run(&rebaseCmd{platform: platform}, true) + cli.Run(&rebaseCmd{platform: p}, true) case "create": - cmd.Run(&createCmd{platform: platform}, true) + cli.Run(&createCmd{platform: p}, true) default: - cmd.Exit(cmd.FailCode(cmd.CodeInvalidArgs, "unknown phase:", phase)) + cmd.Exit(cmd.FailCode(platform.CodeForInvalidArgs, "unknown phase:", phase)) } } @@ -173,8 +175,8 @@ func verifyBuildpackApis(group buildpack.Group) error { // but if for some reason it isn't default to 0.2 bp.API = "0.2" } - if err := cmd.VerifyBuildpackAPI(bp.String(), bp.API); err != nil { - return err + if err := guard.BuildpackAPI(bp.String(), bp.API, buildpack.APIs, cmd.DefaultLogger); err != nil { + return cmd.FailErrCode(err, platform.CodeForIncompatibleBuildpackAPI, "set buildpack API") } } return nil diff --git a/cmd/lifecycle/rebaser.go b/cmd/lifecycle/rebaser.go index d2e0bab40..49e9c557f 100644 --- a/cmd/lifecycle/rebaser.go +++ b/cmd/lifecycle/rebaser.go @@ -14,6 +14,7 @@ import ( "github.com/buildpacks/lifecycle" "github.com/buildpacks/lifecycle/auth" "github.com/buildpacks/lifecycle/cmd" + "github.com/buildpacks/lifecycle/cmd/lifecycle/cli" "github.com/buildpacks/lifecycle/image" "github.com/buildpacks/lifecycle/internal/encoding" "github.com/buildpacks/lifecycle/platform" @@ -39,34 +40,34 @@ type rebaseCmd struct { // DefineFlags defines the flags that are considered valid and reads their values (if provided). func (r *rebaseCmd) DefineFlags() { - cmd.FlagGID(&r.gid) - cmd.FlagReportPath(&r.reportPath) - cmd.FlagRunImage(&r.runImageRef) - cmd.FlagUID(&r.uid) - cmd.FlagUseDaemon(&r.useDaemon) + cli.FlagGID(&r.gid) + cli.FlagReportPath(&r.reportPath) + cli.FlagRunImage(&r.runImageRef) + cli.FlagUID(&r.uid) + cli.FlagUseDaemon(&r.useDaemon) - cmd.DeprecatedFlagRunImage(&r.deprecatedRunImageRef) + cli.DeprecatedFlagRunImage(&r.deprecatedRunImageRef) } // Args validates arguments and flags, and fills in default values. func (r *rebaseCmd) Args(nargs int, args []string) error { if nargs == 0 { - return cmd.FailErrCode(errors.New("at least one image argument is required"), cmd.CodeInvalidArgs, "parse arguments") + return cmd.FailErrCode(errors.New("at least one image argument is required"), platform.CodeForInvalidArgs, "parse arguments") } r.imageNames = args if err := image.ValidateDestinationTags(r.useDaemon, r.imageNames...); err != nil { - return cmd.FailErrCode(err, cmd.CodeInvalidArgs, "validate image tag(s)") + return cmd.FailErrCode(err, platform.CodeForInvalidArgs, "validate image tag(s)") } if r.deprecatedRunImageRef != "" && r.runImageRef != "" { - return cmd.FailErrCode(errors.New("supply only one of -run-image or (deprecated) -image"), cmd.CodeInvalidArgs, "parse arguments") + return cmd.FailErrCode(errors.New("supply only one of -run-image or (deprecated) -image"), platform.CodeForInvalidArgs, "parse arguments") } if r.deprecatedRunImageRef != "" { r.runImageRef = r.deprecatedRunImageRef } - if r.reportPath == cmd.PlaceholderReportPath { - r.reportPath = cmd.DefaultReportPath(r.platform.API().String(), "") + if r.reportPath == platform.PlaceholderReportPath { + r.reportPath = cli.DefaultReportPath(r.platform.API().String(), "") } if err := r.setAppImage(); err != nil { @@ -175,7 +176,7 @@ func (r *rebaseCmd) setAppImage() error { if r.runImageRef == "" { if md.Stack.RunImage.Image == "" { - return cmd.FailErrCode(errors.New("-image is required when there is no stack metadata available"), cmd.CodeInvalidArgs, "parse arguments") + return cmd.FailErrCode(errors.New("-image is required when there is no stack metadata available"), platform.CodeForInvalidArgs, "parse arguments") } r.runImageRef, err = md.Stack.BestRunImageMirror(registry) if err != nil { diff --git a/cmd/lifecycle/restorer.go b/cmd/lifecycle/restorer.go index 61a640b04..4f7b5e80e 100644 --- a/cmd/lifecycle/restorer.go +++ b/cmd/lifecycle/restorer.go @@ -11,6 +11,7 @@ import ( "github.com/buildpacks/lifecycle/auth" "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/cmd" + "github.com/buildpacks/lifecycle/cmd/lifecycle/cli" "github.com/buildpacks/lifecycle/internal/layer" "github.com/buildpacks/lifecycle/platform" "github.com/buildpacks/lifecycle/priv" @@ -38,33 +39,33 @@ type restoreArgs struct { // DefineFlags defines the flags that are considered valid and reads their values (if provided). func (r *restoreCmd) DefineFlags() { - cmd.FlagCacheDir(&r.cacheDir) - cmd.FlagCacheImage(&r.cacheImageTag) - cmd.FlagGroupPath(&r.groupPath) - cmd.FlagLayersDir(&r.layersDir) - cmd.FlagUID(&r.uid) - cmd.FlagGID(&r.gid) + cli.FlagCacheDir(&r.cacheDir) + cli.FlagCacheImage(&r.cacheImageTag) + cli.FlagGroupPath(&r.groupPath) + cli.FlagLayersDir(&r.layersDir) + cli.FlagUID(&r.uid) + cli.FlagGID(&r.gid) if r.restoresLayerMetadata() { - cmd.FlagAnalyzedPath(&r.analyzedPath) - cmd.FlagSkipLayers(&r.skipLayers) + cli.FlagAnalyzedPath(&r.analyzedPath) + cli.FlagSkipLayers(&r.skipLayers) } } // Args validates arguments and flags, and fills in default values. func (r *restoreCmd) Args(nargs int, args []string) error { if nargs > 0 { - return cmd.FailErrCode(errors.New("received unexpected Args"), cmd.CodeInvalidArgs, "parse arguments") + return cmd.FailErrCode(errors.New("received unexpected Args"), platform.CodeForInvalidArgs, "parse arguments") } if r.cacheImageTag == "" && r.cacheDir == "" { cmd.DefaultLogger.Warn("Not restoring cached layer data, no cache flag specified.") } - if r.groupPath == cmd.PlaceholderGroupPath { - r.groupPath = cmd.DefaultGroupPath(r.platform.API().String(), r.layersDir) + if r.groupPath == platform.PlaceholderGroupPath { + r.groupPath = cli.DefaultGroupPath(r.platform.API().String(), r.layersDir) } - if r.analyzedPath == cmd.PlaceholderAnalyzedPath { - r.analyzedPath = cmd.DefaultAnalyzedPath(r.platform.API().String(), r.layersDir) + if r.analyzedPath == platform.PlaceholderAnalyzedPath { + r.analyzedPath = cli.DefaultAnalyzedPath(r.platform.API().String(), r.layersDir) } return nil diff --git a/cmd/logs.go b/cmd/logs.go index f090448b2..ce705be51 100644 --- a/cmd/logs.go +++ b/cmd/logs.go @@ -46,7 +46,7 @@ func SetLogLevel(level string) *ErrorFail { var err error DefaultLogger.Level, err = log.ParseLevel(level) if err != nil { - return FailErrCode(err, CodeInvalidArgs, "parse log level") + return FailErrCode(err, CodeForInvalidArgs, "parse log level") } return nil diff --git a/cmd/version.go b/cmd/version.go index a17dcb702..3e6a4142e 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -3,8 +3,6 @@ package cmd import ( "fmt" "strings" - - "github.com/buildpacks/lifecycle/api" ) // The following variables are injected at compile time. @@ -15,14 +13,6 @@ var ( SCMCommit = "" // SCMRepository is the source repository. SCMRepository = "" - - DeprecationMode = EnvOrDefault(EnvDeprecationMode, DefaultDeprecationMode) -) - -const ( - DeprecationModeQuiet = "quiet" - DeprecationModeWarn = "warn" - DeprecationModeError = "error" ) // buildVersion is a display format of the version and build metadata in compliance with semver. @@ -34,73 +24,3 @@ func buildVersion() string { return fmt.Sprintf("%s+%s", Version, SCMCommit) } - -func VerifyPlatformAPI(requested string) error { - requestedAPI, err := api.NewVersion(requested) - if err != nil { - return FailErrCode( - fmt.Errorf("parse platform API '%s'", requested), - CodeIncompatiblePlatformAPI, - ) - } - if api.Platform.IsSupported(requestedAPI) { - if api.Platform.IsDeprecated(requestedAPI) { - switch DeprecationMode { - case DeprecationModeQuiet: - break - case DeprecationModeError: - DefaultLogger.Errorf("Platform requested deprecated API '%s'", requested) - DefaultLogger.Errorf("Deprecated APIs are disable by %s=%s", EnvDeprecationMode, DeprecationModeError) - return platformAPIError(requested) - case DeprecationModeWarn: - DefaultLogger.Warnf("Platform requested deprecated API '%s'", requested) - default: - DefaultLogger.Warnf("Platform requested deprecated API '%s'", requested) - } - } - return nil - } - return platformAPIError(requested) -} - -func VerifyBuildpackAPI(bp string, requested string) error { - requestedAPI, err := api.NewVersion(requested) - if err != nil { - return FailErrCode( - fmt.Errorf("parse buildpack API '%s' for buildpack '%s'", requestedAPI, bp), - CodeIncompatibleBuildpackAPI, - ) - } - if api.Buildpack.IsSupported(requestedAPI) { - if api.Buildpack.IsDeprecated(requestedAPI) { - switch DeprecationMode { - case DeprecationModeQuiet: - break - case DeprecationModeError: - DefaultLogger.Errorf("Buildpack '%s' requests deprecated API '%s'", bp, requested) - DefaultLogger.Errorf("Deprecated APIs are disable by %s=%s", EnvDeprecationMode, DeprecationModeError) - return buildpackAPIError(bp, requested) - case DeprecationModeWarn: - DefaultLogger.Warnf("Buildpack '%s' requests deprecated API '%s'", bp, requested) - default: - DefaultLogger.Warnf("Buildpack '%s' requests deprecated API '%s'", bp, requested) - } - } - return nil - } - return buildpackAPIError(bp, requested) -} - -func platformAPIError(requested string) error { - return FailErrCode( - fmt.Errorf("set platform API: platform API version '%s' is incompatible with the lifecycle", requested), - CodeIncompatiblePlatformAPI, - ) -} - -func buildpackAPIError(bp string, requested string) error { - return FailErrCode( - fmt.Errorf("set API for buildpack '%s': buildpack API version '%s' is incompatible with the lifecycle", bp, requested), - CodeIncompatibleBuildpackAPI, - ) -} diff --git a/cmd/version_test.go b/cmd/version_test.go deleted file mode 100644 index 653f8bb2d..000000000 --- a/cmd/version_test.go +++ /dev/null @@ -1,157 +0,0 @@ -package cmd_test - -import ( - "testing" - - "github.com/apex/log" - "github.com/apex/log/handlers/memory" - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" - - "github.com/buildpacks/lifecycle/api" - "github.com/buildpacks/lifecycle/cmd" - h "github.com/buildpacks/lifecycle/testhelpers" -) - -func TestPlatformAPI(t *testing.T) { - spec.Run(t, "PlatformAPI", testPlatformAPI, spec.Sequential(), spec.Report(report.Terminal{})) -} - -func testPlatformAPI(t *testing.T, when spec.G, it spec.S) { - var ( - logHandler *memory.Handler - ) - - it.Before(func() { - logHandler = memory.New() - cmd.DefaultLogger = &cmd.Logger{Logger: &log.Logger{Handler: logHandler}} - }) - - when("VerifyPlatformAPI", func() { - it.Before(func() { - var err error - api.Platform, err = api.NewAPIs([]string{"1.2", "2.1"}, []string{"1"}) - h.AssertNil(t, err) - }) - - when("is invalid", func() { - it("error with exit code 11", func() { - err := cmd.VerifyPlatformAPI("bad-api") - failErr, ok := err.(*cmd.ErrorFail) - if !ok { - t.Fatalf("expected an error of type cmd.ErrorFail") - } - h.AssertEq(t, failErr.Code, 11) - }) - }) - - when("is unsupported", func() { - it("error with exit code 11", func() { - err := cmd.VerifyPlatformAPI("2.2") - failErr, ok := err.(*cmd.ErrorFail) - if !ok { - t.Fatalf("expected an error of type cmd.ErrorFail") - } - h.AssertEq(t, failErr.Code, 11) - }) - }) - - when("is deprecated", func() { - when("CNB_DEPRECATION_MODE=warn", func() { - it("should warn", func() { - cmd.DeprecationMode = cmd.DeprecationModeWarn - err := cmd.VerifyPlatformAPI("1.1") - h.AssertNil(t, err) - h.AssertEq(t, len(logHandler.Entries), 1) - h.AssertEq(t, logHandler.Entries[0].Level, log.WarnLevel) - h.AssertEq(t, logHandler.Entries[0].Message, "Platform requested deprecated API '1.1'") - }) - }) - - when("CNB_DEPRECATION_MODE=quiet", func() { - it("should succeed silently", func() { - cmd.DeprecationMode = cmd.DeprecationModeQuiet - err := cmd.VerifyPlatformAPI("1.1") - h.AssertNil(t, err) - h.AssertEq(t, len(logHandler.Entries), 0) - }) - }) - - when("CNB_DEPRECATION_MODE=error", func() { - it("error with exit code 11", func() { - cmd.DeprecationMode = cmd.DeprecationModeError - err := cmd.VerifyPlatformAPI("1.1") - failErr, ok := err.(*cmd.ErrorFail) - if !ok { - t.Fatalf("expected an error of type cmd.ErrorFail") - } - h.AssertEq(t, failErr.Code, 11) - }) - }) - }) - }) - - when("VerifyBuildpackAPIs", func() { - it.Before(func() { - var err error - api.Buildpack, err = api.NewAPIs([]string{"1.2", "2.1"}, []string{"1"}) - h.AssertNil(t, err) - }) - - when("is invalid", func() { - it("error with exit code 12", func() { - err := cmd.VerifyBuildpackAPI("some-buildpack", "bad-api") - failErr, ok := err.(*cmd.ErrorFail) - if !ok { - t.Fatalf("expected an error of type cmd.ErrorFail") - } - h.AssertEq(t, failErr.Code, 12) - }) - }) - - when("is unsupported", func() { - it("error with exit code 11", func() { - err := cmd.VerifyBuildpackAPI("some-buildpack", "2.2") - failErr, ok := err.(*cmd.ErrorFail) - if !ok { - t.Fatalf("expected an error of type cmd.ErrorFail") - } - h.AssertEq(t, failErr.Code, 12) - }) - }) - - when("is deprecated", func() { - when("CNB_DEPRECATION_MODE=warn", func() { - it("should warn", func() { - cmd.DeprecationMode = cmd.DeprecationModeWarn - err := cmd.VerifyBuildpackAPI("some-buildpack", "1.1") - h.AssertNil(t, err) - h.AssertEq(t, len(logHandler.Entries), 1) - h.AssertEq(t, logHandler.Entries[0].Level, log.WarnLevel) - h.AssertEq(t, logHandler.Entries[0].Message, "Buildpack 'some-buildpack' requests deprecated API '1.1'") - }) - }) - - when("CNB_DEPRECATION_MODE=quiet", func() { - it("should succeed silently", func() { - cmd.DeprecationMode = cmd.DeprecationModeQuiet - err := cmd.VerifyBuildpackAPI("some-buildpack", "1.1") - h.AssertNil(t, err) - h.AssertEq(t, len(logHandler.Entries), 0) - }) - }) - - when("CNB_DEPRECATION_MODE=error", func() { - it("error with exit code 11", func() { - cmd.DeprecationMode = cmd.DeprecationModeError - err := cmd.VerifyBuildpackAPI("some-buildpack", "1.1") - failErr, ok := err.(*cmd.ErrorFail) - if !ok { - t.Fatalf("expected an error of type cmd.ErrorFail") - } - h.AssertEq(t, failErr.Code, 12) - }) - }) - }) - }) -} diff --git a/detector.go b/detector.go index 7bc41b3f3..58876f431 100644 --- a/detector.go +++ b/detector.go @@ -9,6 +9,7 @@ import ( "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/env" + "github.com/buildpacks/lifecycle/log" "github.com/buildpacks/lifecycle/platform" ) @@ -144,7 +145,7 @@ func hasID(bps []buildpack.GroupBuildpack, id string) bool { } type DefaultResolver struct { - Logger Logger + Logger log.Logger } // Resolve aggregates the detect output for a group of buildpacks and tries to resolve a build plan for the group. diff --git a/detector_test.go b/detector_test.go index 57cb686d9..8b5bec721 100644 --- a/detector_test.go +++ b/detector_test.go @@ -15,7 +15,6 @@ import ( "github.com/sclevine/spec/report" "github.com/buildpacks/lifecycle" - "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/platform" h "github.com/buildpacks/lifecycle/testhelpers" @@ -49,7 +48,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) { buildpackStore = testmock.NewMockBuildpackStore(mockCtrl) detector.Store = buildpackStore - detector.Platform = platform.NewPlatform(api.Platform.Latest().String()) + detector.Platform = platform.NewPlatform(platform.APIs.Latest().String()) }) it.After(func() { diff --git a/exporter.go b/exporter.go index 759ab2a19..d6a1d3d6c 100644 --- a/exporter.go +++ b/exporter.go @@ -14,10 +14,12 @@ import ( "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/buildpack" - "github.com/buildpacks/lifecycle/cmd" "github.com/buildpacks/lifecycle/launch" "github.com/buildpacks/lifecycle/layers" + "github.com/buildpacks/lifecycle/log" "github.com/buildpacks/lifecycle/platform" + "github.com/buildpacks/lifecycle/platform/guard" + launchenv "github.com/buildpacks/lifecycle/platform/launch" ) type Cache interface { @@ -34,7 +36,7 @@ type Cache interface { type Exporter struct { Buildpacks []buildpack.GroupBuildpack LayerFactory LayerFactory - Logger Logger + Logger log.Logger PlatformAPI *api.Version } @@ -327,24 +329,24 @@ func (e *Exporter) setLabels(opts ExportOptions, meta platform.LayersMetadata, b } func (e *Exporter) setEnv(opts ExportOptions, launchMD launch.Metadata) error { - e.Logger.Debugf("Setting %s=%s", cmd.EnvLayersDir, opts.LayersDir) - if err := opts.WorkingImage.SetEnv(cmd.EnvLayersDir, opts.LayersDir); err != nil { - return errors.Wrapf(err, "set app image env %s", cmd.EnvLayersDir) + e.Logger.Debugf("Setting %s=%s", platform.EnvLayersDir, opts.LayersDir) + if err := opts.WorkingImage.SetEnv(platform.EnvLayersDir, opts.LayersDir); err != nil { + return errors.Wrapf(err, "set app image env %s", platform.EnvLayersDir) } - e.Logger.Debugf("Setting %s=%s", cmd.EnvAppDir, opts.AppDir) - if err := opts.WorkingImage.SetEnv(cmd.EnvAppDir, opts.AppDir); err != nil { - return errors.Wrapf(err, "set app image env %s", cmd.EnvAppDir) + e.Logger.Debugf("Setting %s=%s", platform.EnvAppDir, opts.AppDir) + if err := opts.WorkingImage.SetEnv(platform.EnvAppDir, opts.AppDir); err != nil { + return errors.Wrapf(err, "set app image env %s", platform.EnvAppDir) } - e.Logger.Debugf("Setting %s=%s", cmd.EnvPlatformAPI, e.PlatformAPI.String()) - if err := opts.WorkingImage.SetEnv(cmd.EnvPlatformAPI, e.PlatformAPI.String()); err != nil { - return errors.Wrapf(err, "set app image env %s", cmd.EnvAppDir) + e.Logger.Debugf("Setting %s=%s", platform.EnvPlatformAPI, e.PlatformAPI.String()) + if err := opts.WorkingImage.SetEnv(platform.EnvPlatformAPI, e.PlatformAPI.String()); err != nil { + return errors.Wrapf(err, "set app image env %s", platform.EnvAppDir) } - e.Logger.Debugf("Setting %s=%s", cmd.EnvDeprecationMode, cmd.DeprecationModeQuiet) - if err := opts.WorkingImage.SetEnv(cmd.EnvDeprecationMode, cmd.DeprecationModeQuiet); err != nil { - return errors.Wrapf(err, "set app image env %s", cmd.EnvAppDir) + e.Logger.Debugf("Setting %s=%s", guard.EnvDeprecationMode, guard.ModeQuiet) + if err := opts.WorkingImage.SetEnv(guard.EnvDeprecationMode, guard.ModeQuiet); err != nil { + return errors.Wrapf(err, "set app image env %s", platform.EnvAppDir) } if e.supportsMulticallLauncher() { @@ -361,9 +363,9 @@ func (e *Exporter) setEnv(opts ExportOptions, launchMD launch.Metadata) error { if _, ok := launchMD.FindProcessType(opts.DefaultProcessType); !ok { return processTypeError(launchMD, opts.DefaultProcessType) } - e.Logger.Debugf("Setting %s=%s", cmd.EnvProcessType, opts.DefaultProcessType) - if err := opts.WorkingImage.SetEnv(cmd.EnvProcessType, opts.DefaultProcessType); err != nil { - return errors.Wrapf(err, "set app image env %s", cmd.EnvProcessType) + e.Logger.Debugf("Setting %s=%s", launchenv.EnvProcessType, opts.DefaultProcessType) + if err := opts.WorkingImage.SetEnv(launchenv.EnvProcessType, opts.DefaultProcessType); err != nil { + return errors.Wrapf(err, "set app image env %s", launchenv.EnvProcessType) } } return nil diff --git a/exporter_test.go b/exporter_test.go index 087f93c73..a6e265494 100644 --- a/exporter_test.go +++ b/exporter_test.go @@ -136,12 +136,12 @@ func testExporter(t *testing.T, when spec.G, it spec.S) { exporter = &lifecycle.Exporter{ Buildpacks: []buildpack.GroupBuildpack{ - {ID: "buildpack.id", Version: "1.2.3", API: api.Buildpack.Latest().String()}, - {ID: "other.buildpack.id", Version: "4.5.6", API: api.Buildpack.Latest().String(), Optional: false}, + {ID: "buildpack.id", Version: "1.2.3", API: buildpack.APIs.Latest().String()}, + {ID: "other.buildpack.id", Version: "4.5.6", API: buildpack.APIs.Latest().String(), Optional: false}, }, LayerFactory: layerFactory, Logger: &log.Logger{Handler: logHandler}, - PlatformAPI: api.Platform.Latest(), + PlatformAPI: platform.APIs.Latest(), } }) @@ -290,7 +290,7 @@ func testExporter(t *testing.T, when spec.G, it spec.S) { when("the launch flag is in the top level table", func() { it.Before(func() { - exporter.Buildpacks = []buildpack.GroupBuildpack{{ID: "bad.buildpack.id", API: api.Buildpack.Latest().String()}} + exporter.Buildpacks = []buildpack.GroupBuildpack{{ID: "bad.buildpack.id", API: buildpack.APIs.Latest().String()}} fakeAppImage.AddPreviousLayer("bad-layer", "") opts.OrigMetadata = platform.LayersMetadata{ Buildpacks: []buildpack.LayersMetadata{{ @@ -637,7 +637,7 @@ version = "4.5.6" val, err := opts.WorkingImage.Env("CNB_PLATFORM_API") h.AssertNil(t, err) - h.AssertEq(t, val, api.Platform.Latest().String()) + h.AssertEq(t, val, platform.APIs.Latest().String()) }) it("sets CNB_DEPRECATION_MODE=quiet", func() { @@ -1396,7 +1396,7 @@ version = "4.5.6" when("buildpack requires an escaped id", func() { it.Before(func() { - exporter.Buildpacks = []buildpack.GroupBuildpack{{ID: "some/escaped/bp/id", API: api.Buildpack.Latest().String()}} + exporter.Buildpacks = []buildpack.GroupBuildpack{{ID: "some/escaped/bp/id", API: buildpack.APIs.Latest().String()}} h.RecursiveCopy(t, filepath.Join("testdata", "exporter", "escaped-bpid", "layers"), opts.LayersDir) }) diff --git a/internal/layer/metadata_restorer.go b/internal/layer/metadata_restorer.go index 488f99cd5..3d7a6a27f 100644 --- a/internal/layer/metadata_restorer.go +++ b/internal/layer/metadata_restorer.go @@ -9,6 +9,7 @@ import ( "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/internal/encoding" "github.com/buildpacks/lifecycle/launch" + "github.com/buildpacks/lifecycle/log" "github.com/buildpacks/lifecycle/platform" ) @@ -17,7 +18,7 @@ type MetadataRestorer interface { Restore(buildpacks []buildpack.GroupBuildpack, appMeta platform.LayersMetadata, cacheMeta platform.CacheMetadata, layerSHAStore SHAStore) error } -func NewDefaultMetadataRestorer(layersDir string, skipLayers bool, logger Logger) *DefaultMetadataRestorer { +func NewDefaultMetadataRestorer(layersDir string, skipLayers bool, logger log.Logger) *DefaultMetadataRestorer { return &DefaultMetadataRestorer{ Logger: logger, LayersDir: layersDir, @@ -28,7 +29,7 @@ func NewDefaultMetadataRestorer(layersDir string, skipLayers bool, logger Logger type DefaultMetadataRestorer struct { LayersDir string SkipLayers bool - Logger Logger + Logger log.Logger } func (r *DefaultMetadataRestorer) Restore(buildpacks []buildpack.GroupBuildpack, appMeta platform.LayersMetadata, cacheMeta platform.CacheMetadata, layerSHAStore SHAStore) error { diff --git a/internal/layer/metadata_restorer_test.go b/internal/layer/metadata_restorer_test.go index 883ddf95a..8084d2de5 100644 --- a/internal/layer/metadata_restorer_test.go +++ b/internal/layer/metadata_restorer_test.go @@ -13,7 +13,6 @@ import ( "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/internal/layer" "github.com/buildpacks/lifecycle/platform" @@ -55,9 +54,9 @@ func testLayerMetadataRestorer(t *testing.T, when spec.G, it spec.S) { when("#Restore", func() { it.Before(func() { buildpacks = []buildpack.GroupBuildpack{ - {ID: "metadata.buildpack", API: api.Buildpack.Latest().String()}, - {ID: "no.cache.buildpack", API: api.Buildpack.Latest().String()}, - {ID: "escaped/buildpack/id", API: api.Buildpack.Latest().String()}, + {ID: "metadata.buildpack", API: buildpack.APIs.Latest().String()}, + {ID: "no.cache.buildpack", API: buildpack.APIs.Latest().String()}, + {ID: "escaped/buildpack/id", API: buildpack.APIs.Latest().String()}, } }) @@ -168,8 +167,8 @@ func testLayerMetadataRestorer(t *testing.T, when spec.G, it spec.S) { when("buildpack api >= 0.6", func() { it("restores layer metadata without the launch, build and cache flags", func() { buildpacks = []buildpack.GroupBuildpack{ - {ID: "metadata.buildpack", API: api.Buildpack.Latest().String()}, - {ID: "no.cache.buildpack", API: api.Buildpack.Latest().String()}, + {ID: "metadata.buildpack", API: buildpack.APIs.Latest().String()}, + {ID: "no.cache.buildpack", API: buildpack.APIs.Latest().String()}, } err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore) @@ -337,7 +336,7 @@ func testLayerMetadataRestorer(t *testing.T, when spec.G, it spec.S) { when("subset of buildpacks are detected", func() { it.Before(func() { - buildpacks = []buildpack.GroupBuildpack{{ID: "no.cache.buildpack", API: api.Buildpack.Latest().String()}} + buildpacks = []buildpack.GroupBuildpack{{ID: "no.cache.buildpack", API: buildpack.APIs.Latest().String()}} }) it("restores layers for detected buildpacks", func() { diff --git a/internal/layer/sbom_restorer.go b/internal/layer/sbom_restorer.go index 444acf54c..e6a1e67b8 100644 --- a/internal/layer/sbom_restorer.go +++ b/internal/layer/sbom_restorer.go @@ -17,6 +17,7 @@ import ( "github.com/buildpacks/lifecycle/internal/fsutil" "github.com/buildpacks/lifecycle/launch" "github.com/buildpacks/lifecycle/layers" + "github.com/buildpacks/lifecycle/log" ) //go:generate mockgen -package testmock -destination ../../testmock/sbom_restorer.go github.com/buildpacks/lifecycle/internal/layer SBOMRestorer @@ -32,7 +33,7 @@ type Cache interface { type SBOMRestorerOpts struct { LayersDir string - Logger Logger + Logger log.Logger Nop bool } @@ -48,7 +49,7 @@ func NewSBOMRestorer(opts SBOMRestorerOpts, platformAPI *api.Version) SBOMRestor type DefaultSBOMRestorer struct { LayersDir string - Logger Logger + Logger log.Logger } func (r *DefaultSBOMRestorer) RestoreFromPrevious(image imgutil.Image, layerDigest string) error { diff --git a/internal/layer/sbom_restorer_test.go b/internal/layer/sbom_restorer_test.go index 10aa843e2..1525eec8c 100644 --- a/internal/layer/sbom_restorer_test.go +++ b/internal/layer/sbom_restorer_test.go @@ -43,7 +43,7 @@ func testSBOMRestorer(t *testing.T, when spec.G, it spec.S) { sbomRestorer = layer.NewSBOMRestorer(layer.SBOMRestorerOpts{ LayersDir: layersDir, Logger: &log.Logger{Handler: &discard.Handler{}}, - }, api.Platform.Latest()) + }, platform.APIs.Latest()) }) it.After(func() { @@ -55,7 +55,7 @@ func testSBOMRestorer(t *testing.T, when spec.G, it spec.S) { it("returns a NopSBOMRestorer", func() { r := layer.NewSBOMRestorer(layer.SBOMRestorerOpts{ Nop: true, - }, api.Platform.Latest()) + }, platform.APIs.Latest()) _, ok := r.(*layer.NopSBOMRestorer) h.AssertEq(t, ok, true) }) @@ -74,7 +74,7 @@ func testSBOMRestorer(t *testing.T, when spec.G, it spec.S) { r := layer.NewSBOMRestorer(layer.SBOMRestorerOpts{ LayersDir: "some-dir", Logger: &log.Logger{Handler: &discard.Handler{}}, - }, api.Platform.Latest()) + }, platform.APIs.Latest()) _, ok := r.(*layer.DefaultSBOMRestorer) h.AssertEq(t, ok, true) }) @@ -196,7 +196,7 @@ func testSBOMRestorer(t *testing.T, when spec.G, it spec.S) { filepath.Join("testdata", "sbom"), filepath.Join(layersDir, "sbom")) - buildpackAPI := api.Buildpack.Latest().String() + buildpackAPI := buildpack.APIs.Latest().String() detectedBps = []buildpack.GroupBuildpack{ {ID: "buildpack.id", API: buildpackAPI}, {ID: "escaped/buildpack/id", API: buildpackAPI}, diff --git a/layers/factory.go b/layers/factory.go index ae9cf5ee2..b9234a7fd 100644 --- a/layers/factory.go +++ b/layers/factory.go @@ -6,12 +6,13 @@ import ( "strings" "github.com/buildpacks/lifecycle/archive" + "github.com/buildpacks/lifecycle/log" ) type Factory struct { ArtifactsDir string // ArtifactsDir is the directory where layer files are written UID, GID int // UID and GID are used to normalize layer entries - Logger Logger + Logger log.Logger tarHashes map[string]string // tarHases Stores hashes of layer tarballs for reuse between the export and cache steps. } @@ -22,20 +23,6 @@ type Layer struct { Digest string } -type Logger interface { - Debug(msg string) - Debugf(fmt string, v ...interface{}) - - Info(msg string) - Infof(fmt string, v ...interface{}) - - Warn(msg string) - Warnf(fmt string, v ...interface{}) - - Error(msg string) - Errorf(fmt string, v ...interface{}) -} - func (f *Factory) writeLayer(id string, addEntries func(tw *archive.NormalizingTarWriter) error) (layer Layer, err error) { tarPath := filepath.Join(f.ArtifactsDir, escape(id)+".tar") if f.tarHashes == nil { diff --git a/internal/layer/logger.go b/log/logger.go similarity index 94% rename from internal/layer/logger.go rename to log/logger.go index dbcf31b13..4104e32f0 100644 --- a/internal/layer/logger.go +++ b/log/logger.go @@ -1,4 +1,4 @@ -package layer +package log type Logger interface { Debug(msg string) diff --git a/logger.go b/logger.go deleted file mode 100644 index 6daaf0190..000000000 --- a/logger.go +++ /dev/null @@ -1,15 +0,0 @@ -package lifecycle - -type Logger interface { - Debug(msg string) - Debugf(fmt string, v ...interface{}) - - Info(msg string) - Infof(fmt string, v ...interface{}) - - Warn(msg string) - Warnf(fmt string, v ...interface{}) - - Error(msg string) - Errorf(fmt string, v ...interface{}) -} diff --git a/platform/analyze_inputs.go b/platform/analyze_inputs.go index b4bfe02bb..2bee403a1 100644 --- a/platform/analyze_inputs.go +++ b/platform/analyze_inputs.go @@ -5,6 +5,7 @@ import ( "github.com/buildpacks/lifecycle/image" "github.com/buildpacks/lifecycle/internal/str" + "github.com/buildpacks/lifecycle/log" ) // AnalyzeInputs holds the values of command-line flags and args. @@ -40,7 +41,7 @@ func (a AnalyzeInputs) RegistryImages() []string { // ResolveAnalyze accepts an AnalyzeInputs and returns a new AnalyzeInputs with default values filled in, // or an error if the provided inputs are not valid. -func (r *InputsResolver) ResolveAnalyze(inputs AnalyzeInputs, logger Logger) (AnalyzeInputs, error) { +func (r *InputsResolver) ResolveAnalyze(inputs AnalyzeInputs, logger log.Logger) (AnalyzeInputs, error) { resolvedInputs := inputs if err := r.fillDefaults(&resolvedInputs, logger); err != nil { @@ -53,7 +54,7 @@ func (r *InputsResolver) ResolveAnalyze(inputs AnalyzeInputs, logger Logger) (An return resolvedInputs, nil } -func (r *InputsResolver) fillDefaults(inputs *AnalyzeInputs, logger Logger) error { +func (r *InputsResolver) fillDefaults(inputs *AnalyzeInputs, logger log.Logger) error { if inputs.AnalyzedPath == PlaceholderAnalyzedPath { inputs.AnalyzedPath = defaultPath(PlaceholderAnalyzedPath, inputs.LayersDir, r.platformAPI) } @@ -69,7 +70,7 @@ func (r *InputsResolver) fillDefaults(inputs *AnalyzeInputs, logger Logger) erro return r.fillRunImage(inputs, logger) } -func (r *InputsResolver) fillRunImage(inputs *AnalyzeInputs, logger Logger) error { +func (r *InputsResolver) fillRunImage(inputs *AnalyzeInputs, logger log.Logger) error { if r.platformAPI.LessThan("0.7") || inputs.RunImageRef != "" { return nil } @@ -91,7 +92,7 @@ func (r *InputsResolver) fillRunImage(inputs *AnalyzeInputs, logger Logger) erro return nil } -func (r *InputsResolver) validate(inputs AnalyzeInputs, logger Logger) error { +func (r *InputsResolver) validate(inputs AnalyzeInputs, logger log.Logger) error { if inputs.OutputImageRef == "" { return errors.New("image argument is required") } diff --git a/platform/analyze_inputs_test.go b/platform/analyze_inputs_test.go index 014501013..3d6d2c67f 100644 --- a/platform/analyze_inputs_test.go +++ b/platform/analyze_inputs_test.go @@ -11,12 +11,13 @@ import ( "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/internal/str" + llog "github.com/buildpacks/lifecycle/log" "github.com/buildpacks/lifecycle/platform" h "github.com/buildpacks/lifecycle/testhelpers" ) func TestAnalyzeInputs(t *testing.T) { - for _, api := range api.Platform.Supported { + for _, api := range platform.APIs.Supported { spec.Run(t, "unit-analyzer/"+api.String(), testAnalyzeInputs(api.String()), spec.Parallel(), spec.Report(report.Terminal{})) } } @@ -26,7 +27,7 @@ func testAnalyzeInputs(platformAPI string) func(t *testing.T, when spec.G, it sp var ( resolver *platform.InputsResolver logHandler *memory.Handler - logger platform.Logger + logger llog.Logger ) it.Before(func() { diff --git a/platform/apis.go b/platform/apis.go new file mode 100644 index 000000000..fd5edcbb2 --- /dev/null +++ b/platform/apis.go @@ -0,0 +1,15 @@ +package platform + +import ( + "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/log" + "github.com/buildpacks/lifecycle/platform/guard" +) + +var ( + APIs = api.NewAPIsMustParse([]string{"0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9"}, nil) +) + +func GuardAPI(api string, logger log.Logger) error { + return guard.PlatformAPI(api, APIs, logger) +} diff --git a/platform/default_paths.go b/platform/default_paths.go deleted file mode 100644 index 791009052..000000000 --- a/platform/default_paths.go +++ /dev/null @@ -1,28 +0,0 @@ -package platform - -import ( - "path/filepath" - - "github.com/buildpacks/lifecycle/api" -) - -const ( - DefaultAnalyzedFile = "analyzed.toml" - DefaultGroupFile = "group.toml" - // TODO: future work should move order, plan, project metadata, and report to this file -) - -var ( - PlaceholderAnalyzedPath = filepath.Join("", DefaultAnalyzedFile) - PlaceholderGroupPath = filepath.Join("", DefaultGroupFile) -) - -func defaultPath(placeholderPath, layersDir string, platformAPI *api.Version) string { - filename := filepath.Base(placeholderPath) - if (platformAPI).LessThan("0.5") || (layersDir == "") { - // prior to platform api 0.5, the default directory was the working dir. - // layersDir is unset when this call comes from the rebaser - will be fixed as part of https://github.com/buildpacks/spec/issues/156 - return filepath.Join(".", filename) - } - return filepath.Join(layersDir, filename) -} diff --git a/platform/env.go b/platform/env.go new file mode 100644 index 000000000..1da3c5f0c --- /dev/null +++ b/platform/env.go @@ -0,0 +1,28 @@ +package platform + +const ( + EnvAnalyzedPath = "CNB_ANALYZED_PATH" + EnvAppDir = "CNB_APP_DIR" + EnvBuildpacksDir = "CNB_BUILDPACKS_DIR" + EnvCacheDir = "CNB_CACHE_DIR" + EnvCacheImage = "CNB_CACHE_IMAGE" + EnvGID = "CNB_GROUP_ID" + EnvGroupPath = "CNB_GROUP_PATH" + EnvLaunchCacheDir = "CNB_LAUNCH_CACHE_DIR" + EnvLayersDir = "CNB_LAYERS_DIR" + EnvLogLevel = "CNB_LOG_LEVEL" + EnvOrderPath = "CNB_ORDER_PATH" + EnvPlanPath = "CNB_PLAN_PATH" + EnvPlatformAPI = "CNB_PLATFORM_API" + EnvPlatformDir = "CNB_PLATFORM_DIR" + EnvPreviousImage = "CNB_PREVIOUS_IMAGE" + EnvProcessType = "CNB_PROCESS_TYPE" + EnvProjectMetadataPath = "CNB_PROJECT_METADATA_PATH" + EnvReportPath = "CNB_REPORT_PATH" + EnvRunImage = "CNB_RUN_IMAGE" + EnvSkipLayers = "CNB_ANALYZE_SKIP_LAYERS" // defaults to false + EnvSkipRestore = "CNB_SKIP_RESTORE" // defaults to false + EnvStackPath = "CNB_STACK_PATH" + EnvUID = "CNB_USER_ID" + EnvUseDaemon = "CNB_USE_DAEMON" // defaults to false +) diff --git a/platform/exit.go b/platform/exit.go index 705809e27..ca31839a3 100644 --- a/platform/exit.go +++ b/platform/exit.go @@ -2,7 +2,18 @@ package platform type LifecycleExitError int -const CodeFailed = 1 +const ( + CodeForFailed = 1 // CodeForFailed indicates generic lifecycle error + // 2: reserved + CodeForInvalidArgs = 3 + // 4: CodeForInvalidEnv + // 5: CodeForNotFound + // 9: CodeForFailedUpdate + + // API errors + CodeForIncompatiblePlatformAPI = 11 + CodeForIncompatibleBuildpackAPI = 12 +) const ( FailedDetect LifecycleExitError = iota @@ -91,5 +102,5 @@ func codeFor(errType LifecycleExitError, exitCodes map[LifecycleExitError]int) i if code, ok := exitCodes[errType]; ok { return code } - return CodeFailed + return CodeForFailed } diff --git a/platform/files_test.go b/platform/files_test.go index 88dc3b005..1d7500cd5 100644 --- a/platform/files_test.go +++ b/platform/files_test.go @@ -96,7 +96,7 @@ func testMetadata(t *testing.T, when spec.G, it spec.S) { }, }}, Buildpacks: buildpacks, - PlatformAPI: api.Platform.Latest(), + PlatformAPI: platform.APIs.Latest(), } }) diff --git a/platform/guard/apis.go b/platform/guard/apis.go new file mode 100644 index 000000000..5c033bd07 --- /dev/null +++ b/platform/guard/apis.go @@ -0,0 +1,119 @@ +package guard + +import ( + "fmt" + "os" + + "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/log" +) + +var ( + DeprecationMode = envOrDefault(EnvDeprecationMode, DefaultDeprecation) + ExperimentalMode = envOrDefault(EnvExperimentalMode, DefaultExperimentalAPIs) +) + +const ( + EnvDeprecationMode = "CNB_DEPRECATION_MODE" + EnvExperimentalMode = "CNB_EXPERIMENTAL_MODE" + + DefaultDeprecation = ModeWarn + DefaultExperimentalAPIs = ModeWarn + + ModeError = "error" + ModeQuiet = "quiet" + ModeWarn = "warn" +) + +func PlatformAPI(requested string, apis api.APIs, logger log.Logger) error { + requestedAPI, err := api.NewVersion(requested) + if err != nil { + return fmt.Errorf("failed to parse platform API '%s'", requested) + } + if apis.IsSupported(requestedAPI) { + if apis.IsDeprecated(requestedAPI) { + switch DeprecationMode { + case ModeQuiet: + break + case ModeError: + logger.Errorf("Platform requested deprecated API '%s'", requested) + logger.Errorf("Deprecated APIs are disabled by %s=%s", EnvDeprecationMode, ModeError) + return platformAPIError(requested) + case ModeWarn: + logger.Warnf("Platform requested deprecated API '%s'", requested) + default: + logger.Warnf("Platform requested deprecated API '%s'", requested) + } + } + if apis.IsPrelease(requestedAPI) { + switch ExperimentalMode { + case ModeQuiet: + break + case ModeError: + logger.Errorf("Platform requested experimental API '%s'", requested) + logger.Errorf("Experimental APIs are disabled by %s=%s", EnvExperimentalMode, ModeError) + return platformAPIError(requested) + case ModeWarn: + logger.Warnf("Platform requested experimental API '%s'", requested) + default: + logger.Warnf("Platform requested experimental API '%s'", requested) + } + } + return nil + } + return platformAPIError(requested) +} + +func platformAPIError(requested string) error { + return fmt.Errorf("platform API version '%s' is incompatible with the lifecycle", requested) +} + +func BuildpackAPI(bp string, requested string, apis api.APIs, logger log.Logger) error { + requestedAPI, err := api.NewVersion(requested) + if err != nil { + return fmt.Errorf("failed to parse buildpack API '%s' for buildpack '%s'", requestedAPI, bp) + } + if apis.IsSupported(requestedAPI) { + if apis.IsDeprecated(requestedAPI) { + switch DeprecationMode { + case ModeQuiet: + break + case ModeError: + logger.Errorf("Buildpack '%s' requests deprecated API '%s'", bp, requested) + logger.Errorf("Deprecated APIs are disabled by %s=%s", EnvDeprecationMode, ModeError) + return buildpackAPIError(bp, requested) + case ModeWarn: + logger.Warnf("Buildpack '%s' requests deprecated API '%s'", bp, requested) + default: + logger.Warnf("Buildpack '%s' requests deprecated API '%s'", bp, requested) + } + } + if apis.IsPrelease(requestedAPI) { + switch ExperimentalMode { + case ModeQuiet: + break + case ModeError: + logger.Errorf("Buildpack '%s' requests experimental API '%s'", bp, requested) + logger.Errorf("Experimental APIs are disabled by %s=%s", EnvExperimentalMode, ModeError) + return buildpackAPIError(bp, requested) + case ModeWarn: + logger.Warnf("Buildpack '%s' requests experimental API '%s'", bp, requested) + default: + logger.Warnf("Buildpack '%s' requests experimental API '%s'", bp, requested) + } + } + return nil + } + return buildpackAPIError(bp, requested) +} + +func buildpackAPIError(bp string, requested string) error { + return fmt.Errorf("buildpack '%s' requests buildpack API version '%s' which is incompatible with the lifecycle", bp, requested) +} + +func envOrDefault(key string, defaultVal string) string { + if envVal := os.Getenv(key); envVal != "" { + return envVal + } + return defaultVal +} diff --git a/platform/guard/apis_test.go b/platform/guard/apis_test.go new file mode 100644 index 000000000..45d6ea142 --- /dev/null +++ b/platform/guard/apis_test.go @@ -0,0 +1,205 @@ +package guard_test + +import ( + "testing" + + "github.com/apex/log" + "github.com/apex/log/handlers/memory" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/cmd" + llog "github.com/buildpacks/lifecycle/log" + "github.com/buildpacks/lifecycle/platform/guard" + h "github.com/buildpacks/lifecycle/testhelpers" +) + +func TestGuardAPIs(t *testing.T) { + spec.Run(t, "GuardAPIs", testGuardAPIs, spec.Sequential(), spec.Report(report.Terminal{})) +} + +func testGuardAPIs(t *testing.T, when spec.G, it spec.S) { + var ( + logger llog.Logger + logHandler *memory.Handler + supported api.APIs + ) + + it.Before(func() { + logHandler = memory.New() + logger = &cmd.Logger{Logger: &log.Logger{Handler: logHandler}} + }) + + when("PlatformAPI", func() { + it.Before(func() { + var err error + supported, err = api.NewAPIs([]string{"1.2", "2.1"}, []string{"1"}) + h.AssertNil(t, err) + }) + + when("is invalid", func() { + it("errors", func() { + err := guard.PlatformAPI("bad-api", supported, logger) + h.AssertNotNil(t, err) + }) + }) + + when("is unsupported", func() { + it("errors", func() { + err := guard.PlatformAPI("2.2", supported, logger) + h.AssertNotNil(t, err) + }) + }) + + when("is deprecated", func() { + when("CNB_DEPRECATION_MODE=warn", func() { + it("warns", func() { + guard.DeprecationMode = guard.ModeWarn + err := guard.PlatformAPI("1.1", supported, logger) + h.AssertNil(t, err) + h.AssertEq(t, len(logHandler.Entries), 1) + h.AssertEq(t, logHandler.Entries[0].Level, log.WarnLevel) + h.AssertEq(t, logHandler.Entries[0].Message, "Platform requested deprecated API '1.1'") + }) + }) + + when("CNB_DEPRECATION_MODE=quiet", func() { + it("succeeds silently", func() { + guard.DeprecationMode = guard.ModeQuiet + err := guard.PlatformAPI("1.1", supported, logger) + h.AssertNil(t, err) + h.AssertEq(t, len(logHandler.Entries), 0) + }) + }) + + when("CNB_DEPRECATION_MODE=error", func() { + it("errors", func() { + guard.DeprecationMode = guard.ModeError + err := guard.PlatformAPI("1.1", supported, logger) + h.AssertNotNil(t, err) + }) + }) + }) + + when("is experimental", func() { + when("CNB_EXPERIMENTAL_MODE=warn", func() { + it("warns", func() { + guard.ExperimentalMode = guard.ModeWarn + err := guard.PlatformAPI("2.1-alpha-1", supported, logger) + h.AssertNil(t, err) + h.AssertEq(t, len(logHandler.Entries), 1) + h.AssertEq(t, logHandler.Entries[0].Level, log.WarnLevel) + h.AssertEq(t, logHandler.Entries[0].Message, "Platform requested experimental API '2.1-alpha-1'") + }) + }) + + when("CNB_EXPERIMENTAL_MODE=quiet", func() { + it("succeeds silently", func() { + guard.ExperimentalMode = guard.ModeQuiet + err := guard.PlatformAPI("2.1-alpha-1", supported, logger) + h.AssertNil(t, err) + h.AssertEq(t, len(logHandler.Entries), 0) + }) + }) + + when("CNB_EXPERIMENTAL_MODE=error", func() { + it("errors", func() { + guard.ExperimentalMode = guard.ModeError + err := guard.PlatformAPI("2.1-alpha-1", supported, logger) + h.AssertNotNil(t, err) + }) + }) + }) + }) + + when("BuildpackAPIs", func() { + it.Before(func() { + var err error + supported, err = api.NewAPIs([]string{"1.2", "2.1"}, []string{"1"}) + h.AssertNil(t, err) + }) + + when("is invalid", func() { + it("errors", func() { + err := guard.BuildpackAPI("some-buildpack", "bad-api", supported, logger) + h.AssertNotNil(t, err) + }) + }) + + when("is unsupported", func() { + it("errors", func() { + err := guard.BuildpackAPI("some-buildpack", "2.2", supported, logger) + h.AssertNotNil(t, err) + }) + }) + + when("is deprecated", func() { + when("CNB_DEPRECATION_MODE=warn", func() { + it("warns", func() { + guard.DeprecationMode = guard.ModeWarn + err := guard.BuildpackAPI("some-buildpack", "1.1", supported, logger) + h.AssertNil(t, err) + h.AssertEq(t, len(logHandler.Entries), 1) + h.AssertEq(t, logHandler.Entries[0].Level, log.WarnLevel) + h.AssertEq(t, logHandler.Entries[0].Message, "Buildpack 'some-buildpack' requests deprecated API '1.1'") + }) + }) + + when("CNB_DEPRECATION_MODE=quiet", func() { + it("succeeds silently", func() { + guard.DeprecationMode = guard.ModeQuiet + err := guard.BuildpackAPI("some-buildpack", "1.1", supported, logger) + h.AssertNil(t, err) + h.AssertEq(t, len(logHandler.Entries), 0) + }) + }) + + when("CNB_DEPRECATION_MODE=error", func() { + it("errors", func() { + guard.DeprecationMode = guard.ModeError + err := guard.BuildpackAPI("some-buildpack", "1.1", supported, logger) + h.AssertNotNil(t, err) + }) + }) + }) + + when("is experimental", func() { + when("CNB_EXPERIMENTAL_MODE=warn", func() { + it("warns", func() { + guard.ExperimentalMode = guard.ModeWarn + err := guard.BuildpackAPI("some-buildpack", "2.1-alpha-1", supported, logger) + h.AssertNil(t, err) + h.AssertEq(t, len(logHandler.Entries), 1) + h.AssertEq(t, logHandler.Entries[0].Level, log.WarnLevel) + h.AssertEq(t, logHandler.Entries[0].Message, "Buildpack 'some-buildpack' requests experimental API '2.1-alpha-1'") + }) + }) + + when("CNB_EXPERIMENTAL_MODE=quiet", func() { + it("succeeds silently", func() { + guard.ExperimentalMode = guard.ModeQuiet + err := guard.BuildpackAPI("some-buildpack", "2.1-alpha-1", supported, logger) + h.AssertNil(t, err) + h.AssertEq(t, len(logHandler.Entries), 0) + }) + }) + + when("CNB_EXPERIMENTAL_MODE=error", func() { + it("errors", func() { + guard.ExperimentalMode = guard.ModeError + err := guard.BuildpackAPI("some-buildpack", "2.1-alpha-1", supported, logger) + h.AssertNotNil(t, err) + }) + }) + }) + }) + + when("constants", func() { + it("match platform package", func() { + h.AssertEq(t, guard.ModeError, guard.ModeError) + h.AssertEq(t, guard.ModeQuiet, guard.ModeQuiet) + h.AssertEq(t, guard.ModeWarn, guard.ModeWarn) + }) + }) +} diff --git a/platform/guard/experimental_features.go b/platform/guard/experimental_features.go new file mode 100644 index 000000000..b9acf3bb7 --- /dev/null +++ b/platform/guard/experimental_features.go @@ -0,0 +1,32 @@ +package guard + +import ( + "github.com/pkg/errors" + + "github.com/buildpacks/lifecycle/log" +) + +var ( + ExperimentalFeaturesMode = envOrDefault(EnvExperimentalFeatures, DefaultExperimentalFeatures) +) + +const ( + EnvExperimentalFeatures = "CNB_PLATFORM_EXPERIMENTAL_MODE" + + DefaultExperimentalFeatures = ModeWarn +) + +func ExperimentalFeature(feature string, logger log.Logger) error { + switch ExperimentalFeaturesMode { + case ModeQuiet: + break + case ModeError: + logger.Errorf("Experimental feature '%s' requested", feature) + return errors.Errorf("Experimental features are disabled by %s=%s", EnvExperimentalFeatures, ExperimentalFeaturesMode) + case ModeWarn: + logger.Warnf("Experimental feature '%s' requested", feature) + default: + logger.Warnf("Experimental feature '%s' requested", feature) + } + return nil +} diff --git a/platform/guard/experimental_features_test.go b/platform/guard/experimental_features_test.go new file mode 100644 index 000000000..a3fae7ce8 --- /dev/null +++ b/platform/guard/experimental_features_test.go @@ -0,0 +1,61 @@ +package guard_test + +import ( + "testing" + + "github.com/apex/log" + "github.com/apex/log/handlers/memory" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + llog "github.com/buildpacks/lifecycle/log" + "github.com/buildpacks/lifecycle/platform/guard" + h "github.com/buildpacks/lifecycle/testhelpers" +) + +func TestExperimentalFeature(t *testing.T) { + spec.Run(t, "Feature", testExperimentalFeature, spec.Sequential(), spec.Report(report.Terminal{})) +} + +func testExperimentalFeature(t *testing.T, when spec.G, it spec.S) { + var ( + logger llog.Logger + logHandler *memory.Handler + ) + + it.Before(func() { + logHandler = memory.New() + logger = &log.Logger{Handler: logHandler} + }) + + when("ExperimentalFeature", func() { + when("CNB_PLATFORM_EXPERIMENTAL_MODE=warn", func() { + it("warns", func() { + guard.ExperimentalFeaturesMode = "warn" + h.AssertNil(t, guard.ExperimentalFeature("some-feature", logger)) + h.AssertEq(t, len(logHandler.Entries), 1) + h.AssertEq(t, logHandler.Entries[0].Level, log.WarnLevel) + h.AssertEq(t, logHandler.Entries[0].Message, "Experimental feature 'some-feature' requested") + }) + }) + + when("CNB_PLATFORM_EXPERIMENTAL_MODE=quiet", func() { + it("succeeds silently", func() { + guard.ExperimentalFeaturesMode = "quiet" + h.AssertNil(t, guard.ExperimentalFeature("some-feature", logger)) + h.AssertEq(t, len(logHandler.Entries), 0) + }) + }) + + when("CNB_PLATFORM_EXPERIMENTAL_MODE=error", func() { + it("errors", func() { + guard.ExperimentalFeaturesMode = "error" + err := guard.ExperimentalFeature("some-feature", logger) + h.AssertEq(t, len(logHandler.Entries), 1) + h.AssertEq(t, logHandler.Entries[0].Level, log.ErrorLevel) + h.AssertEq(t, logHandler.Entries[0].Message, "Experimental feature 'some-feature' requested") + h.AssertEq(t, err.Error(), "Experimental features are disabled by CNB_PLATFORM_EXPERIMENTAL_MODE=error") + }) + }) + }) +} diff --git a/platform/launch/apis.go b/platform/launch/apis.go new file mode 100644 index 000000000..c8b1839e8 --- /dev/null +++ b/platform/launch/apis.go @@ -0,0 +1,13 @@ +package launch + +import ( + "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/lifecycle/log" + "github.com/buildpacks/lifecycle/platform/guard" +) + +var APIs = api.NewAPIsMustParse([]string{"0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9"}, nil) + +func GuardAPI(api string, logger log.Logger) error { + return guard.PlatformAPI(api, APIs, logger) +} diff --git a/platform/launch/env.go b/platform/launch/env.go new file mode 100644 index 000000000..03ffc9b5d --- /dev/null +++ b/platform/launch/env.go @@ -0,0 +1,16 @@ +package launch + +import "path/filepath" + +const ( + EnvAppDir = "CNB_APP_DIR" + EnvLayersDir = "CNB_LAYERS_DIR" + EnvPlatformAPI = "CNB_PLATFORM_API" + EnvProcessType = "CNB_PROCESS_TYPE" +) + +var ( + DefaultAppDir = filepath.Join(rootDir, "workspace") + DefaultLayersDir = filepath.Join(rootDir, "layers") + DefaultProcessType = "web" +) diff --git a/platform/launch/env_unix.go b/platform/launch/env_unix.go new file mode 100644 index 000000000..123d0ca04 --- /dev/null +++ b/platform/launch/env_unix.go @@ -0,0 +1,8 @@ +//go:build linux || darwin +// +build linux darwin + +package launch + +const ( + rootDir = "/" +) diff --git a/platform/launch/env_windows.go b/platform/launch/env_windows.go new file mode 100644 index 000000000..a051967e8 --- /dev/null +++ b/platform/launch/env_windows.go @@ -0,0 +1,5 @@ +package launch + +const ( + rootDir = `c:\` +) diff --git a/platform/launch/exit.go b/platform/launch/exit.go index 4443ec473..2329cf301 100644 --- a/platform/launch/exit.go +++ b/platform/launch/exit.go @@ -2,7 +2,18 @@ package launch type LifecycleExitError int -const CodeFailed = 1 +const ( + CodeForFailed = 1 // CodeForFailed indicates generic lifecycle error + // 2: reserved + // 3: CodeForInvalidArgs + // 4: CodeForInvalidEnv + // 5: CodeForNotFound + // 9: CodeForFailedUpdate + + // API errors + CodeForIncompatiblePlatformAPI = 11 + CodeForIncompatibleBuildpackAPI = 12 +) const ( LaunchError LifecycleExitError = iota @@ -47,5 +58,5 @@ func codeFor(errType LifecycleExitError, exitCodes map[LifecycleExitError]int) i if code, ok := exitCodes[errType]; ok { return code } - return CodeFailed + return CodeForFailed } diff --git a/platform/launch/platform_test.go b/platform/launch/platform_test.go index cad408f8d..1424be464 100644 --- a/platform/launch/platform_test.go +++ b/platform/launch/platform_test.go @@ -7,12 +7,13 @@ import ( "github.com/sclevine/spec/report" "github.com/buildpacks/lifecycle/api" - platform "github.com/buildpacks/lifecycle/platform/launch" + "github.com/buildpacks/lifecycle/platform" + "github.com/buildpacks/lifecycle/platform/launch" h "github.com/buildpacks/lifecycle/testhelpers" ) func TestPlatform(t *testing.T) { - for _, api := range api.Platform.Supported { + for _, api := range launch.APIs.Supported { spec.Run(t, "unit-platform/"+api.String(), testPlatform(api), spec.Parallel(), spec.Report(report.Terminal{})) } } @@ -26,10 +27,10 @@ func testPlatform(platformAPI *api.Version) func(t *testing.T, when spec.G, it s }) it("configures the platform", func() { - foundPlatform := platform.NewPlatform(platformAPI.String()) + foundPlatform := launch.NewPlatform(platformAPI.String()) t.Log("with a default exiter") - _, ok := foundPlatform.Exiter.(*platform.DefaultExiter) + _, ok := foundPlatform.Exiter.(*launch.DefaultExiter) h.AssertEq(t, ok, true) t.Log("with an api") @@ -43,10 +44,10 @@ func testPlatform(platformAPI *api.Version) func(t *testing.T, when spec.G, it s }) it("configures the platform", func() { - foundPlatform := platform.NewPlatform(platformAPI.String()) + foundPlatform := launch.NewPlatform(platformAPI.String()) t.Log("with a legacy exiter") - _, ok := foundPlatform.Exiter.(*platform.LegacyExiter) + _, ok := foundPlatform.Exiter.(*launch.LegacyExiter) h.AssertEq(t, ok, true) t.Log("with an api") @@ -54,5 +55,18 @@ func testPlatform(platformAPI *api.Version) func(t *testing.T, when spec.G, it s }) }) }) + + when("constants", func() { + it("match platform package", func() { + h.AssertEq(t, launch.APIs, platform.APIs) + h.AssertEq(t, launch.EnvAppDir, platform.EnvAppDir) + h.AssertEq(t, launch.EnvLayersDir, platform.EnvLayersDir) + h.AssertEq(t, launch.EnvPlatformAPI, platform.EnvPlatformAPI) + h.AssertEq(t, launch.EnvProcessType, platform.EnvProcessType) + h.AssertEq(t, launch.CodeForFailed, platform.CodeForFailed) + h.AssertEq(t, launch.CodeForIncompatiblePlatformAPI, platform.CodeForIncompatiblePlatformAPI) + h.AssertEq(t, launch.CodeForIncompatibleBuildpackAPI, platform.CodeForIncompatibleBuildpackAPI) + }) + }) } } diff --git a/platform/logger.go b/platform/logger.go deleted file mode 100644 index 46d965751..000000000 --- a/platform/logger.go +++ /dev/null @@ -1,15 +0,0 @@ -package platform - -type Logger interface { - Debug(msg string) - Debugf(fmt string, v ...interface{}) - - Info(msg string) - Infof(fmt string, v ...interface{}) - - Warn(msg string) - Warnf(fmt string, v ...interface{}) - - Error(msg string) - Errorf(fmt string, v ...interface{}) -} diff --git a/platform/paths.go b/platform/paths.go new file mode 100644 index 000000000..e7e446fb1 --- /dev/null +++ b/platform/paths.go @@ -0,0 +1,43 @@ +package platform + +import ( + "path/filepath" + + "github.com/buildpacks/lifecycle/api" +) + +var ( + // TODO: when all phases use the "inputs resolver" pattern, these variables can be internal to the package + PlaceholderAnalyzedPath = filepath.Join("", DefaultAnalyzedFile) + PlaceholderGroupPath = filepath.Join("", DefaultGroupFile) + PlaceholderOrderPath = filepath.Join("", DefaultOrderFile) + PlaceholderPlanPath = filepath.Join("", DefaultPlanFile) + PlaceholderProjectMetadataPath = filepath.Join("", DefaultProjectMetadataFile) + PlaceholderReportPath = filepath.Join("", DefaultReportFile) + + DefaultAppDir = filepath.Join(rootDir, "workspace") + DefaultBuildpacksDir = filepath.Join(rootDir, "cnb", "buildpacks") + DefaultLauncherPath = filepath.Join(rootDir, "cnb", "lifecycle", "launcher"+execExt) + DefaultLayersDir = filepath.Join(rootDir, "layers") + DefaultPlatformDir = filepath.Join(rootDir, "platform") + DefaultStackPath = filepath.Join(rootDir, "cnb", "stack.toml") +) + +const ( + DefaultAnalyzedFile = "analyzed.toml" + DefaultGroupFile = "group.toml" + DefaultOrderFile = "order.toml" + DefaultPlanFile = "plan.toml" + DefaultProjectMetadataFile = "project-metadata.toml" + DefaultReportFile = "report.toml" +) + +func defaultPath(placeholderPath, layersDir string, platformAPI *api.Version) string { + filename := filepath.Base(placeholderPath) + if (platformAPI).LessThan("0.5") || (layersDir == "") { + // prior to platform api 0.5, the default directory was the working dir. + // layersDir is unset when this call comes from the rebaser - will be fixed as part of https://github.com/buildpacks/spec/issues/156 + return filepath.Join(".", filename) + } + return filepath.Join(layersDir, filename) +} diff --git a/cmd/flags_unix.go b/platform/paths_unix.go similarity index 84% rename from cmd/flags_unix.go rename to platform/paths_unix.go index 83d5bfdc6..9f3da3be5 100644 --- a/cmd/flags_unix.go +++ b/platform/paths_unix.go @@ -1,7 +1,7 @@ //go:build linux || darwin // +build linux darwin -package cmd +package platform const ( rootDir = "/" diff --git a/cmd/flags_windows.go b/platform/paths_windows.go similarity index 73% rename from cmd/flags_windows.go rename to platform/paths_windows.go index b426acdc1..ac245c9cc 100644 --- a/cmd/flags_windows.go +++ b/platform/paths_windows.go @@ -1,4 +1,4 @@ -package cmd +package platform const ( rootDir = `c:\` diff --git a/platform/platform_test.go b/platform/platform_test.go index 6b84f85e7..b2a0de69b 100644 --- a/platform/platform_test.go +++ b/platform/platform_test.go @@ -12,7 +12,7 @@ import ( ) func TestPlatform(t *testing.T) { - for _, api := range api.Platform.Supported { + for _, api := range platform.APIs.Supported { spec.Run(t, "unit-platform/"+api.String(), testPlatform(api), spec.Parallel(), spec.Report(report.Terminal{})) } } diff --git a/platform/utils.go b/platform/utils.go index a1d0188c4..309e9af71 100644 --- a/platform/utils.go +++ b/platform/utils.go @@ -6,6 +6,8 @@ import ( "github.com/BurntSushi/toml" "github.com/google/go-containerregistry/pkg/name" + + "github.com/buildpacks/lifecycle/log" ) func appendNotEmpty(slice []string, elems ...string) []string { @@ -43,7 +45,7 @@ func parseRegistry(providedRef string) (string, error) { return ref.Context().RegistryStr(), nil } -func readStack(stackPath string, logger Logger) (StackMetadata, error) { +func readStack(stackPath string, logger log.Logger) (StackMetadata, error) { var stackMD StackMetadata if _, err := toml.DecodeFile(stackPath, &stackMD); err != nil { if os.IsNotExist(err) { diff --git a/rebaser.go b/rebaser.go index 3afd85328..60ecc6c84 100644 --- a/rebaser.go +++ b/rebaser.go @@ -12,11 +12,12 @@ import ( "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/image" "github.com/buildpacks/lifecycle/internal/str" + "github.com/buildpacks/lifecycle/log" "github.com/buildpacks/lifecycle/platform" ) type Rebaser struct { - Logger Logger + Logger log.Logger PlatformAPI *api.Version } diff --git a/rebaser_test.go b/rebaser_test.go index f349ccdeb..964fbf474 100644 --- a/rebaser_test.go +++ b/rebaser_test.go @@ -58,7 +58,7 @@ func testRebaser(t *testing.T, when spec.G, it spec.S) { rebaser = &lifecycle.Rebaser{ Logger: &log.Logger{Handler: &discard.Handler{}}, - PlatformAPI: api.Platform.Latest(), + PlatformAPI: platform.APIs.Latest(), } }) diff --git a/restorer.go b/restorer.go index c7704b7f9..f33f1b49a 100644 --- a/restorer.go +++ b/restorer.go @@ -10,12 +10,13 @@ import ( "github.com/buildpacks/lifecycle/buildpack" "github.com/buildpacks/lifecycle/internal/layer" "github.com/buildpacks/lifecycle/layers" + "github.com/buildpacks/lifecycle/log" "github.com/buildpacks/lifecycle/platform" ) type Restorer struct { LayersDir string - Logger Logger + Logger log.Logger Buildpacks []buildpack.GroupBuildpack LayerMetadataRestorer layer.MetadataRestorer // Platform API >= 0.7 diff --git a/restorer_test.go b/restorer_test.go index ee9f98748..5d60574c9 100644 --- a/restorer_test.go +++ b/restorer_test.go @@ -27,8 +27,8 @@ import ( ) func TestRestorer(t *testing.T) { - for _, buildpackAPIStr := range []string{"0.5", api.Buildpack.Latest().String()} { - for _, platformAPI := range api.Platform.Supported { + for _, buildpackAPIStr := range []string{"0.5", buildpack.APIs.Latest().String()} { + for _, platformAPI := range platform.APIs.Supported { platformAPIStr := platformAPI.String() spec.Run( t, diff --git a/save.go b/save.go index 787a41d35..409fa5857 100644 --- a/save.go +++ b/save.go @@ -8,10 +8,11 @@ import ( "github.com/buildpacks/imgutil/remote" "github.com/pkg/errors" + "github.com/buildpacks/lifecycle/log" "github.com/buildpacks/lifecycle/platform" ) -func saveImage(image imgutil.Image, additionalNames []string, logger Logger) (platform.ImageReport, error) { +func saveImage(image imgutil.Image, additionalNames []string, logger log.Logger) (platform.ImageReport, error) { var saveErr error imageReport := platform.ImageReport{} logger.Infof("Saving %s...\n", image.Name()) diff --git a/tools/packager/main.go b/tools/packager/main.go index 1798eb364..bba877a4f 100644 --- a/tools/packager/main.go +++ b/tools/packager/main.go @@ -16,8 +16,9 @@ import ( "github.com/pkg/errors" - "github.com/buildpacks/lifecycle/api" "github.com/buildpacks/lifecycle/archive" + "github.com/buildpacks/lifecycle/buildpack" + "github.com/buildpacks/lifecycle/platform" ) var ( @@ -68,10 +69,10 @@ func doPackage() error { descriptorContents, err := fillTemplate(templateContents, map[string]interface{}{ "lifecycle_version": version, - "apis_buildpack_supported": api.Buildpack.Supported.String(), - "apis_buildpack_deprecated": api.Buildpack.Deprecated.String(), - "apis_platform_supported": api.Platform.Supported.String(), - "apis_platform_deprecated": api.Platform.Deprecated.String(), + "apis_buildpack_supported": buildpack.APIs.Supported.String(), + "apis_buildpack_deprecated": buildpack.APIs.Deprecated.String(), + "apis_platform_supported": platform.APIs.Supported.String(), + "apis_platform_deprecated": platform.APIs.Deprecated.String(), }) if err != nil { return errors.Wrap(err, "Failed to fill template")