From 36db452da07269eb70155d04e7a89ae8071995fa Mon Sep 17 00:00:00 2001 From: Emily Casey Date: Tue, 28 Jul 2020 12:06:53 -0400 Subject: [PATCH] Hardcode platform/buildpack APIs Signed-off-by: Emily Casey --- Makefile | 40 ++++++++------ acceptance/acceptance_test.go | 87 +---------------------------- acceptance/testdata/lifecycle.toml | 10 ---- analyzer_test.go | 2 +- api/apis.go | 23 ++++++++ cmd/exit.go | 4 +- cmd/flags.go | 30 +++++----- cmd/launcher/main.go | 22 +++----- cmd/lifecycle/analyzer.go | 4 +- cmd/lifecycle/creator.go | 14 ++--- cmd/lifecycle/detector.go | 8 +-- cmd/lifecycle/exporter.go | 22 ++++---- cmd/lifecycle/main.go | 3 +- cmd/lifecycle/rebaser.go | 2 +- cmd/lifecycle/restorer.go | 4 +- cmd/logs.go | 9 ++- cmd/version.go | 36 +++--------- cmd/version_test.go | 89 ++++++++++++++++++++++++++++++ restorer_test.go | 2 +- tools/descriptor/main.go | 87 ----------------------------- 20 files changed, 205 insertions(+), 293 deletions(-) delete mode 100644 acceptance/testdata/lifecycle.toml create mode 100644 cmd/version_test.go delete mode 100644 tools/descriptor/main.go diff --git a/Makefile b/Makefile index 9d3a2958..b6b16949 100644 --- a/Makefile +++ b/Makefile @@ -4,19 +4,20 @@ PWD?=$(subst /,\,${CURDIR}) LDFLAGS=-s -w BLANK:= /:=\$(BLANK) +LIFECYCLE_VERSION?=$(shell type VERSION) else /:=/ +LIFECYCLE_VERSION?=$(shell cat VERSION) endif GOCMD?=go GOARCH?=amd64 GOENV=GOARCH=$(GOARCH) CGO_ENABLED=0 LIFECYCLE_DESCRIPTOR_PATH?=lifecycle.toml -LIFECYCLE_VERSION:=$(shell $(GOCMD) run tools/descriptor/main.go version $(LIFECYCLE_DESCRIPTOR_PATH)) LDFLAGS=-s -w LDFLAGS+=-X 'github.com/buildpacks/lifecycle/cmd.SCMRepository=$(SCM_REPO)' LDFLAGS+=-X 'github.com/buildpacks/lifecycle/cmd.SCMCommit=$(SCM_COMMIT)' -LDFLAGS+=$(shell $(GOCMD) run tools/descriptor/main.go build-args $(LIFECYCLE_DESCRIPTOR_PATH)) +LDFLAGS+=-X 'github.com/buildpacks/lifecycle/cmd.Version=$(LIFECYCLE_VERSION)' GOBUILD:=go build $(GOFLAGS) -ldflags "$(LDFLAGS)" GOTEST=$(GOCMD) test $(GOFLAGS) SCM_REPO?=github.com/buildpacks/lifecycle @@ -54,7 +55,6 @@ $(BUILD_DIR)/linux/lifecycle/lifecycle: @echo "> Building lifecycle/lifecycle for linux..." mkdir -p $(OUT_DIR) $(DOCKER_RUN) sh -c 'apk add build-base && $(GOENV) $(GOBUILD) -o /out/lifecycle -a ./cmd/lifecycle' -$(BUILD_DIR)/linux/lifecycle/lifecycle: $(GOFILES) build-linux-launcher: $(BUILD_DIR)/linux/lifecycle/launcher @@ -89,20 +89,19 @@ $(BUILD_DIR)/windows/lifecycle/lifecycle.toml: $(BUILD_DIR)/windows/lifecycle/lifecycle.exe: export GOOS:=windows $(BUILD_DIR)/windows/lifecycle/lifecycle.exe: OUT_DIR?=$(BUILD_DIR)$/$(GOOS)$/lifecycle -$(BUILD_DIR)/windows/lifecycle/lifecycle.exe: descriptor-windows +$(BUILD_DIR)/windows/lifecycle/lifecycle.exe: $(GOFILES) $(BUILD_DIR)/windows/lifecycle/lifecycle.exe: @echo "> Building lifecycle/lifecycle for Windows..." $(GOBUILD) -o $(OUT_DIR)$/lifecycle.exe -a ./cmd/lifecycle -$(BUILD_DIR)/windows/lifecycle/lifecycle.exe: $(GOFILES) build-windows-launcher: $(BUILD_DIR)/windows/lifecycle/launcher.exe $(BUILD_DIR)/windows/lifecycle/launcher.exe: export GOOS:=windows $(BUILD_DIR)/windows/lifecycle/launcher.exe: OUT_DIR?=$(BUILD_DIR)$/$(GOOS)$/lifecycle +$(BUILD_DIR)/windows/lifecycle/launcher.exe: $(GOFILES) $(BUILD_DIR)/windows/lifecycle/launcher.exe: @echo "> Building lifecycle/launcher for Windows..." $(GOBUILD) -o $(OUT_DIR)$/launcher.exe -a ./cmd/launcher -$(BUILD_DIR)/windows/lifecycle/launcher.exe: $(GOFILES) build-windows-symlinks: export GOOS:=windows build-windows-symlinks: OUT_DIR?=$(BUILD_DIR)$/$(GOOS)$/lifecycle @@ -136,23 +135,32 @@ $(BUILD_DIR)/darwin/lifecycle/lifecycle.toml: mkdir -p $(BUILD_DIR)/darwin/lifecycle cp $(LIFECYCLE_DESCRIPTOR_PATH) $(BUILD_DIR)/darwin/lifecycle/lifecycle.toml -build-darwin: $(BUILD_DIR)/darwin/lifecycle -$(BUILD_DIR)/darwin/lifecycle: export GOOS:=darwin -$(BUILD_DIR)/darwin/lifecycle: OUT_DIR:=$(BUILD_DIR)/$(GOOS)/lifecycle -$(BUILD_DIR)/darwin/lifecycle: - @echo "> Building for macos..." - mkdir -p $(OUT_DIR) - $(GOENV) $(GOBUILD) -o $(OUT_DIR)/launcher -a ./cmd/launcher - test $$(du -m $(OUT_DIR)/launcher|cut -f 1) -le 4 +build-darwin: descriptor-darwin build-darwin-lifecycle build-darwin-launcher + +build-darwin-lifecycle: $(BUILD_DIR)/darwin/lifecycle/lifecycle +$(BUILD_DIR)/darwin/lifecycle/lifecycle: export GOOS:=darwin +$(BUILD_DIR)/darwin/lifecycle/lifecycle: OUT_DIR:=$(BUILD_DIR)/$(GOOS)/lifecycle +$(BUILD_DIR)/darwin/lifecycle/lifecycle: $(GOFILES) +$(BUILD_DIR)/darwin/lifecycle/lifecycle: + @echo "> Building lifecycle for macos..." $(GOENV) $(GOBUILD) -o $(OUT_DIR)/lifecycle -a ./cmd/lifecycle + @echo "> Creating lifecycle symlinks for macos..." ln -sf lifecycle $(OUT_DIR)/detector ln -sf lifecycle $(OUT_DIR)/analyzer ln -sf lifecycle $(OUT_DIR)/restorer ln -sf lifecycle $(OUT_DIR)/builder ln -sf lifecycle $(OUT_DIR)/exporter ln -sf lifecycle $(OUT_DIR)/rebaser -$(BUILD_DIR)/darwin/lifecycle: $(GOFILES) -$(BUILD_DIR)/darwin/lifecycle: descriptor-darwin + +build-darwin-launcher: $(BUILD_DIR)/darwin/lifecycle/launcher +$(BUILD_DIR)/darwin/lifecycle/launcher: export GOOS:=darwin +$(BUILD_DIR)/darwin/lifecycle/launcher: OUT_DIR:=$(BUILD_DIR)/$(GOOS)/lifecycle +$(BUILD_DIR)/darwin/lifecycle/launcher: $(GOFILES) +$(BUILD_DIR)/darwin/lifecycle/launcher: + @echo "> Building launcher for macos..." + mkdir -p $(OUT_DIR) + $(GOENV) $(GOBUILD) -o $(OUT_DIR)/launcher -a ./cmd/launcher + test $$(du -m $(OUT_DIR)/launcher|cut -f 1) -le 4 install-goimports: @echo "> Installing goimports..." diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index bbac3461..2f5bef08 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -1,7 +1,6 @@ package acceptance import ( - "fmt" "io/ioutil" "os" "os/exec" @@ -32,13 +31,11 @@ func TestVersion(t *testing.T) { outDir := filepath.Join(buildDir, runtime.GOOS, "lifecycle") h.AssertNil(t, os.MkdirAll(outDir, 0755)) - descriptorPath, err := filepath.Abs(filepath.Join("testdata/lifecycle.toml")) - h.AssertNil(t, err) h.MakeAndCopyLifecycle(t, runtime.GOOS, outDir, - "LIFECYCLE_DESCRIPTOR_PATH="+descriptorPath, + "LIFECYCLE_VERSION=some-version", "SCM_COMMIT="+expectedCommit, ) spec.Run(t, "acceptance", testVersion, spec.Parallel(), spec.Report(report.Terminal{})) @@ -53,87 +50,6 @@ type testCase struct { func testVersion(t *testing.T, when spec.G, it spec.S) { when("All", func() { - when("CNB_PLATFORM_API", func() { - for _, phase := range []string{ - "analyzer", - "builder", - "detector", - "exporter", - "restorer", - "rebaser", - "lifecycle", - } { - phase := phase - when("is unsupported", func() { - it(phase+"/should fail with error message and exit code 11", func() { - cmd := lifecycleCmd(phase) - cmd.Env = append(os.Environ(), "CNB_PLATFORM_API=1.4") - - _, exitCode, err := h.RunE(cmd) - h.AssertError(t, err, fmt.Sprintf("platform API version '1.4' is incompatible with the lifecycle")) - h.AssertEq(t, exitCode, 11) - }) - }) - - when("is deprecated", func() { - when("CNB_DEPRECATION_MODE is unset", func() { - it(phase+"/should warn", func() { - cmd := lifecycleCmd(phase, "-version") - cmd.Env = []string{ - "CNB_PLATFORM_API=1.3", - } - - out, _, err := h.RunE(cmd) - h.AssertNil(t, err) - h.AssertStringContains(t, out, "Platform API '1.3' is deprecated") - }) - }) - - when("CNB_DEPRECATION_MODE=warn", func() { - it(phase+"/should warn", func() { - cmd := lifecycleCmd(phase, "-version") - cmd.Env = []string{ - "CNB_PLATFORM_API=1.3", - "CNB_DEPRECATION_MODE=warn", - } - - out, _, err := h.RunE(cmd) - h.AssertNil(t, err) - h.AssertStringContains(t, out, "Platform API '1.3' is deprecated") - }) - }) - - when("CNB_DEPRECATION_MODE=quiet", func() { - it(phase+"/should not warn", func() { - cmd := lifecycleCmd(phase, "-version") - cmd.Env = []string{ - "CNB_PLATFORM_API=1.3", - "CNB_DEPRECATION_MODE=quiet", - } - - out, _, err := h.RunE(cmd) - h.AssertNil(t, err) - h.AssertStringDoesNotContain(t, out, "deprecated") - }) - }) - - when("CNB_DEPRECATION_MODE=error", func() { - it(phase+"/should error", func() { - cmd := lifecycleCmd(phase, "-version") - cmd.Env = []string{ - "CNB_PLATFORM_API=1.3", - "CNB_DEPRECATION_MODE=error", - } - - _, exitCode, err := h.RunE(cmd) - h.AssertError(t, err, fmt.Sprintf("platform API version '1.3' is incompatible with the lifecycle")) - h.AssertEq(t, exitCode, 11) - }) - }) - }) - } - }) - when("version flag is set", func() { for _, tc := range []testCase{ { @@ -220,7 +136,6 @@ func testVersion(t *testing.T, when spec.G, it spec.S) { w(tc.description, func() { it("only prints the version", func() { cmd := lifecycleCmd(tc.command, tc.args...) - cmd.Env = []string{"CNB_PLATFORM_API=2.0"} output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("failed to run %v\n OUTPUT: %s\n ERROR: %s\n", cmd.Args, output, err) diff --git a/acceptance/testdata/lifecycle.toml b/acceptance/testdata/lifecycle.toml deleted file mode 100644 index 5c28c4ac..00000000 --- a/acceptance/testdata/lifecycle.toml +++ /dev/null @@ -1,10 +0,0 @@ -[apis] -[apis.buildpack] - deprecated = ["0.9"] - supported = ["0.9", "1.2"] -[apis.platform] - deprecated = ["1"] - supported = ["1.3","2.4"] - -[lifecycle] - version = "some-version" diff --git a/analyzer_test.go b/analyzer_test.go index 8e1360bc..cf3b3f04 100644 --- a/analyzer_test.go +++ b/analyzer_test.go @@ -58,7 +58,7 @@ func testAnalyzer(t *testing.T, when spec.G, it spec.S) { Logger: &log.Logger{Handler: &discard.Handler{}}, } if testing.Verbose() { - analyzer.Logger = cmd.Logger + analyzer.Logger = cmd.DefaultLogger cmd.SetLogLevel("debug") } mockCtrl = gomock.NewController(t) diff --git a/api/apis.go b/api/apis.go index 78903503..dbdba51d 100644 --- a/api/apis.go +++ b/api/apis.go @@ -6,11 +6,32 @@ import ( "github.com/pkg/errors" ) +var ( + Platform = newApisMustParse([]string{"0.3", "0.4"}, nil) + Buildpack = newApisMustParse([]string{"0.3", "0.4"}, nil) +) + type APIs struct { Supported []*Version Deprecated []*Version } +// newApisMustParse calls NewApis and panics on error +func newApisMustParse(supported []string, deprecated []string) APIs { + apis, err := NewAPIs(supported, deprecated) + if err != nil { + panic(err) + } + return apis +} + +// NewApis constructs an instance of APIs +// supported must be a superset of deprecated +// deprecated APIs greater than 1.0 should should not include minor versions +// supported APIs should always include minor versions +// Examples: +// deprecated API 1 implies all 1.x APIs are deprecated +// supported API 1 implies only 1.0 is supported func NewAPIs(supported []string, deprecated []string) (APIs, error) { apis := APIs{} for _, api := range supported { @@ -40,6 +61,7 @@ func validateDeprecated(apis APIs, d string) error { return nil } +// IsSupported returns true or false depending on whether the target API is supported func (a APIs) IsSupported(target string) bool { tAPI := MustParse(target) for _, sAPI := range a.Supported { @@ -50,6 +72,7 @@ func (a APIs) IsSupported(target string) bool { return false } +// IsDeprecated returns true or false depending on whether the target API is deprecated func (a APIs) IsDeprecated(target string) bool { tAPI := MustParse(target) for _, dAPI := range a.Deprecated { diff --git a/cmd/exit.go b/cmd/exit.go index 7f8aa047..1b90385c 100644 --- a/cmd/exit.go +++ b/cmd/exit.go @@ -55,7 +55,7 @@ func Exit(err error) { if err == nil { os.Exit(0) } - Logger.Errorf("%s\n", err) + DefaultLogger.Errorf("%s\n", err) if err, ok := err.(*ErrorFail); ok { os.Exit(err.Code) } @@ -63,6 +63,6 @@ func Exit(err error) { } func ExitWithVersion() { - Logger.Infof(buildVersion()) + DefaultLogger.Infof(buildVersion()) os.Exit(0) } diff --git a/cmd/flags.go b/cmd/flags.go index e23b790f..fd05d9f0 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -12,12 +12,14 @@ var ( DefaultAnalyzedPath = filepath.Join(".", "analyzed.toml") DefaultAppDir = filepath.Join(rootDir, "workspace") DefaultBuildpacksDir = filepath.Join(rootDir, "cnb", "buildpacks") + DefaultDeprecationMode = DeprecationModeWarn DefaultGroupPath = filepath.Join(".", "group.toml") DefaultLauncherPath = filepath.Join(rootDir, "cnb", "lifecycle", "launcher"+execExt) DefaultLayersDir = filepath.Join(rootDir, "layers") DefaultLogLevel = "info" DefaultOrderPath = filepath.Join(rootDir, "cnb", "order.toml") DefaultPlanPath = filepath.Join(".", "plan.toml") + DefaultPlatformAPI = "0.3" DefaultPlatformDir = filepath.Join(rootDir, "platform") DefaultProcessType = "web" DefaultProjectMetadataPath = filepath.Join(".", "project-metadata.toml") @@ -31,6 +33,7 @@ const ( 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" @@ -39,6 +42,7 @@ const ( 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" @@ -56,15 +60,15 @@ const ( var flagSet = flag.NewFlagSet("lifecycle", flag.ExitOnError) func FlagAnalyzedPath(dir *string) { - flagSet.StringVar(dir, "analyzed", envOrDefault(EnvAnalyzedPath, DefaultAnalyzedPath), "path to analyzed.toml") + flagSet.StringVar(dir, "analyzed", EnvOrDefault(EnvAnalyzedPath, DefaultAnalyzedPath), "path to analyzed.toml") } func FlagAppDir(dir *string) { - flagSet.StringVar(dir, "app", envOrDefault(EnvAppDir, DefaultAppDir), "path to app directory") + flagSet.StringVar(dir, "app", EnvOrDefault(EnvAppDir, DefaultAppDir), "path to app directory") } func FlagBuildpacksDir(dir *string) { - flagSet.StringVar(dir, "buildpacks", envOrDefault(EnvBuildpacksDir, DefaultBuildpacksDir), "path to buildpacks directory") + flagSet.StringVar(dir, "buildpacks", EnvOrDefault(EnvBuildpacksDir, DefaultBuildpacksDir), "path to buildpacks directory") } func FlagCacheDir(dir *string) { @@ -80,7 +84,7 @@ func FlagGID(gid *int) { } func FlagGroupPath(path *string) { - flagSet.StringVar(path, "group", envOrDefault(EnvGroupPath, DefaultGroupPath), "path to group.toml") + flagSet.StringVar(path, "group", EnvOrDefault(EnvGroupPath, DefaultGroupPath), "path to group.toml") } func FlagLaunchCacheDir(dir *string) { @@ -92,7 +96,7 @@ func FlagLauncherPath(path *string) { } func FlagLayersDir(dir *string) { - flagSet.StringVar(dir, "layers", envOrDefault(EnvLayersDir, DefaultLayersDir), "path to layers directory") + flagSet.StringVar(dir, "layers", EnvOrDefault(EnvLayersDir, DefaultLayersDir), "path to layers directory") } func FlagNoColor(skip *bool) { @@ -100,15 +104,15 @@ func FlagNoColor(skip *bool) { } func FlagOrderPath(path *string) { - flagSet.StringVar(path, "order", envOrDefault(EnvOrderPath, DefaultOrderPath), "path to order.toml") + flagSet.StringVar(path, "order", EnvOrDefault(EnvOrderPath, DefaultOrderPath), "path to order.toml") } func FlagPlanPath(path *string) { - flagSet.StringVar(path, "plan", envOrDefault(EnvPlanPath, DefaultPlanPath), "path to plan.toml") + flagSet.StringVar(path, "plan", EnvOrDefault(EnvPlanPath, DefaultPlanPath), "path to plan.toml") } func FlagPlatformDir(dir *string) { - flagSet.StringVar(dir, "platform", envOrDefault(EnvPlatformDir, DefaultPlatformDir), "path to platform directory") + flagSet.StringVar(dir, "platform", EnvOrDefault(EnvPlatformDir, DefaultPlatformDir), "path to platform directory") } func FlagPreviousImage(image *string) { @@ -116,7 +120,7 @@ func FlagPreviousImage(image *string) { } func FlagReportPath(path *string) { - flagSet.StringVar(path, "report", envOrDefault(EnvReportPath, DefaultReportPath), "path to report.toml") + flagSet.StringVar(path, "report", EnvOrDefault(EnvReportPath, DefaultReportPath), "path to report.toml") } func FlagRunImage(image *string) { @@ -132,7 +136,7 @@ func FlagSkipRestore(skip *bool) { } func FlagStackPath(path *string) { - flagSet.StringVar(path, "stack", envOrDefault(EnvStackPath, DefaultStackPath), "path to stack.toml") + flagSet.StringVar(path, "stack", EnvOrDefault(EnvStackPath, DefaultStackPath), "path to stack.toml") } func FlagTags(tags *StringSlice) { @@ -152,11 +156,11 @@ func FlagVersion(version *bool) { } func FlagLogLevel(level *string) { - flagSet.StringVar(level, "log-level", envOrDefault(EnvLogLevel, DefaultLogLevel), "logging level") + flagSet.StringVar(level, "log-level", EnvOrDefault(EnvLogLevel, DefaultLogLevel), "logging level") } func FlagProjectMetadataPath(projectMetadataPath *string) { - flagSet.StringVar(projectMetadataPath, "project-metadata", envOrDefault(EnvProjectMetadataPath, DefaultProjectMetadataPath), "path to project-metadata.toml") + flagSet.StringVar(projectMetadataPath, "project-metadata", EnvOrDefault(EnvProjectMetadataPath, DefaultProjectMetadataPath), "path to project-metadata.toml") } func FlagProcessType(processType *string) { @@ -196,7 +200,7 @@ func boolEnv(k string) bool { return b } -func envOrDefault(key string, defaultVal string) string { +func EnvOrDefault(key string, defaultVal string) string { if envVal := os.Getenv(key); envVal != "" { return envVal } diff --git a/cmd/launcher/main.go b/cmd/launcher/main.go index fd7ff156..d7c22309 100644 --- a/cmd/launcher/main.go +++ b/cmd/launcher/main.go @@ -15,28 +15,20 @@ func main() { } func runLaunch() error { - defaultProcessType := cmd.DefaultProcessType - if v := os.Getenv(cmd.EnvProcessType); v != "" { - defaultProcessType = v - } - layersDir := cmd.DefaultLayersDir - if v := os.Getenv(cmd.EnvLayersDir); v != "" { - layersDir = v - } - appDir := cmd.DefaultAppDir - if v := os.Getenv(cmd.EnvAppDir); v != "" { - appDir = v + platformAPI := cmd.EnvOrDefault(cmd.EnvPlatformAPI, cmd.DefaultPlatformAPI) + if err := cmd.VerifyPlatformAPI(platformAPI); err != nil { + cmd.Exit(err) } var md launch.Metadata - if _, err := toml.DecodeFile(launch.GetMetadataFilePath(layersDir), &md); err != nil { + if _, err := toml.DecodeFile(launch.GetMetadataFilePath(cmd.EnvOrDefault(cmd.EnvLayersDir, cmd.DefaultLayersDir)), &md); err != nil { return cmd.FailErr(err, "read metadata") } launcher := &launch.Launcher{ - DefaultProcessType: defaultProcessType, - LayersDir: layersDir, - AppDir: appDir, + DefaultProcessType: cmd.EnvOrDefault(cmd.EnvProcessType, cmd.DefaultProcessType), + LayersDir: cmd.EnvOrDefault(cmd.EnvLayersDir, cmd.DefaultLayersDir), + AppDir: cmd.EnvOrDefault(cmd.EnvAppDir, cmd.DefaultAppDir), Processes: md.Processes, Buildpacks: md.Buildpacks, Env: env.NewLaunchEnv(os.Environ()), diff --git a/cmd/lifecycle/analyzer.go b/cmd/lifecycle/analyzer.go index e79a6a6b..a6165d8f 100644 --- a/cmd/lifecycle/analyzer.go +++ b/cmd/lifecycle/analyzer.go @@ -58,7 +58,7 @@ func (a *analyzeCmd) Args(nargs int, args []string) error { return cmd.FailErrCode(errors.New("image argument is required"), cmd.CodeInvalidArgs, "parse arguments") } if a.cacheImageTag == "" && a.cacheDir == "" { - cmd.Logger.Warn("Not restoring cached layer metadata, no cache flag specified.") + cmd.DefaultLogger.Warn("Not restoring cached layer metadata, no cache flag specified.") } a.imageName = args[0] return nil @@ -129,7 +129,7 @@ func (aa analyzeArgs) analyze(group lifecycle.BuildpackGroup, cacheStore lifecyc analyzedMD, err := (&lifecycle.Analyzer{ Buildpacks: group.Group, LayersDir: aa.layersDir, - Logger: cmd.Logger, + Logger: cmd.DefaultLogger, SkipLayers: aa.skipLayers, }).Analyze(img, cacheStore) if err != nil { diff --git a/cmd/lifecycle/creator.go b/cmd/lifecycle/creator.go index b40afac0..17120c05 100644 --- a/cmd/lifecycle/creator.go +++ b/cmd/lifecycle/creator.go @@ -65,12 +65,12 @@ func (c *createCmd) Args(nargs int, args []string) error { c.imageName = args[0] if c.launchCacheDir != "" && !c.useDaemon { - cmd.Logger.Warn("Ignoring -launch-cache, only intended for use with -daemon") + cmd.DefaultLogger.Warn("Ignoring -launch-cache, only intended for use with -daemon") c.launchCacheDir = "" } if c.cacheImageTag == "" && c.cacheDir == "" { - cmd.Logger.Warn("Not restoring or caching layer data, no cache flag specified.") + cmd.DefaultLogger.Warn("Not restoring or caching layer data, no cache flag specified.") } if c.previousImage == "" { @@ -110,7 +110,7 @@ func (c *createCmd) Exec() error { return err } - cmd.Logger.Phase("DETECTING") + cmd.DefaultLogger.Phase("DETECTING") group, plan, err := detectArgs{ buildpacksDir: c.buildpacksDir, appDir: c.appDir, @@ -121,7 +121,7 @@ func (c *createCmd) Exec() error { return cmd.FailErrCode(err, cmd.CodeFailed, "detect") } - cmd.Logger.Phase("ANALYZING") + cmd.DefaultLogger.Phase("ANALYZING") analyzedMD, err := analyzeArgs{ imageName: c.previousImage, layersDir: c.layersDir, @@ -134,13 +134,13 @@ func (c *createCmd) Exec() error { } if !c.skipRestore { - cmd.Logger.Phase("RESTORING") + cmd.DefaultLogger.Phase("RESTORING") if err := restore(c.layersDir, group, cacheStore); err != nil { return err } } - cmd.Logger.Phase("BUILDING") + cmd.DefaultLogger.Phase("BUILDING") err = buildArgs{ buildpacksDir: c.buildpacksDir, layersDir: c.layersDir, @@ -151,7 +151,7 @@ func (c *createCmd) Exec() error { return err } - cmd.Logger.Phase("EXPORTING") + cmd.DefaultLogger.Phase("EXPORTING") return exportArgs{ stackPath: c.stackPath, imageNames: append([]string{c.imageName}, c.additionalTags...), diff --git a/cmd/lifecycle/detector.go b/cmd/lifecycle/detector.go index 86a1ae03..110aeae8 100644 --- a/cmd/lifecycle/detector.go +++ b/cmd/lifecycle/detector.go @@ -76,16 +76,16 @@ func (da detectArgs) detect() (lifecycle.BuildpackGroup, lifecycle.BuildPlan, er AppDir: da.appDir, PlatformDir: da.platformDir, BuildpacksDir: da.buildpacksDir, - Logger: cmd.Logger, + Logger: cmd.DefaultLogger, }) if err != nil { switch err { case lifecycle.ErrFailedDetection: - cmd.Logger.Error("No buildpack groups passed detection.") - cmd.Logger.Error("Please check that you are running against the correct path.") + cmd.DefaultLogger.Error("No buildpack groups passed detection.") + cmd.DefaultLogger.Error("Please check that you are running against the correct path.") return lifecycle.BuildpackGroup{}, lifecycle.BuildPlan{}, cmd.FailErrCode(err, cmd.CodeFailedDetect, "detect") case lifecycle.ErrBuildpack: - cmd.Logger.Error("No buildpack groups passed detection.") + cmd.DefaultLogger.Error("No buildpack groups passed detection.") return lifecycle.BuildpackGroup{}, lifecycle.BuildPlan{}, cmd.FailErrCode(err, cmd.CodeFailedDetectWithErrors, "detect") default: return lifecycle.BuildpackGroup{}, lifecycle.BuildPlan{}, cmd.FailErrCode(err, 1, "detect") diff --git a/cmd/lifecycle/exporter.go b/cmd/lifecycle/exporter.go index c3cc38ab..b6777d22 100644 --- a/cmd/lifecycle/exporter.go +++ b/cmd/lifecycle/exporter.go @@ -81,12 +81,12 @@ func (e *exportCmd) Args(nargs int, args []string) error { e.imageNames = args if e.launchCacheDir != "" && !e.useDaemon { - cmd.Logger.Warn("Ignoring -launch-cache, only intended for use with -daemon") + cmd.DefaultLogger.Warn("Ignoring -launch-cache, only intended for use with -daemon") e.launchCacheDir = "" } if e.cacheImageTag == "" && e.cacheDir == "" { - cmd.Logger.Warn("Will not cache data, no cache flag specified.") + cmd.DefaultLogger.Warn("Will not cache data, no cache flag specified.") } if err := image.ValidateDestinationTags(e.useDaemon, e.imageNames...); err != nil { @@ -127,14 +127,14 @@ func (e *exportCmd) Exec() error { return cmd.FailErr(err, "read buildpack group") } - analyzedMD, err := parseOptionalAnalyzedMD(cmd.Logger, e.analyzedPath) + analyzedMD, err := parseOptionalAnalyzedMD(cmd.DefaultLogger, e.analyzedPath) if err != nil { return cmd.FailErrCode(err, cmd.CodeInvalidArgs, "parse analyzed metadata") } cacheStore, err := initCache(e.cacheImageTag, e.cacheDir) if err != nil { - cmd.Logger.Infof("no stack metadata found at path '%s', stack metadata will not be exported\n", e.stackPath) + cmd.DefaultLogger.Infof("no stack metadata found at path '%s', stack metadata will not be exported\n", e.stackPath) } return e.export(group, cacheStore, analyzedMD) @@ -164,17 +164,17 @@ func (ea exportArgs) export(group lifecycle.BuildpackGroup, cacheStore lifecycle if !os.IsNotExist(err) { return err } - cmd.Logger.Debugf("no project metadata found at path '%s', project metadata will not be exported\n", ea.projectMetadataPath) + cmd.DefaultLogger.Debugf("no project metadata found at path '%s', project metadata will not be exported\n", ea.projectMetadataPath) } exporter := &lifecycle.Exporter{ Buildpacks: group.Group, - Logger: cmd.Logger, + Logger: cmd.DefaultLogger, LayerFactory: &layers.Factory{ ArtifactsDir: artifactsDir, UID: ea.uid, GID: ea.uid, - Logger: cmd.Logger, + Logger: cmd.DefaultLogger, }, } @@ -224,7 +224,7 @@ func (ea exportArgs) export(group lifecycle.BuildpackGroup, cacheStore lifecycle if cacheStore != nil { if cacheErr := exporter.Cache(ea.layersDir, cacheStore); cacheErr != nil { - cmd.Logger.Warnf("Failed to export cache: %v\n", cacheErr) + cmd.DefaultLogger.Warnf("Failed to export cache: %v\n", cacheErr) } } return nil @@ -236,7 +236,7 @@ func initDaemonImage(imagName string, runImageRef string, analyzedMD lifecycle.A } if analyzedMD.Image != nil { - cmd.Logger.Debugf("Reusing layers from image with id '%s'", analyzedMD.Image.Reference) + cmd.DefaultLogger.Debugf("Reusing layers from image with id '%s'", analyzedMD.Image.Reference) opts = append(opts, local.WithPreviousImage(analyzedMD.Image.Reference)) } @@ -270,7 +270,7 @@ func initRemoteImage(imageName string, runImageRef string, analyzedMD lifecycle. } if analyzedMD.Image != nil { - cmd.Logger.Infof("Reusing layers from image '%s'", analyzedMD.Image.Reference) + cmd.DefaultLogger.Infof("Reusing layers from image '%s'", analyzedMD.Image.Reference) ref, err := name.ParseReference(analyzedMD.Image.Reference, name.WeakValidation) if err != nil { return nil, "", cmd.FailErr(err, "parse analyzed registry") @@ -337,7 +337,7 @@ func resolveStack(stackPath, runImageRef, registry string) (lifecycle.StackMetad var stackMD lifecycle.StackMetadata _, err := toml.DecodeFile(stackPath, &stackMD) if err != nil { - cmd.Logger.Infof("no stack metadata found at path '%s', stack metadata will not be exported\n", stackPath) + cmd.DefaultLogger.Infof("no stack metadata found at path '%s', stack metadata will not be exported\n", stackPath) } if runImageRef == "" { if stackMD.RunImage.Image == "" { diff --git a/cmd/lifecycle/main.go b/cmd/lifecycle/main.go index 12ca4b45..679dd9c0 100644 --- a/cmd/lifecycle/main.go +++ b/cmd/lifecycle/main.go @@ -12,7 +12,8 @@ import ( ) func main() { - if err := cmd.VerifyCompatibility(); err != nil { + platformAPI := cmd.EnvOrDefault(cmd.EnvPlatformAPI, cmd.DefaultPlatformAPI) + if err := cmd.VerifyPlatformAPI(platformAPI); err != nil { cmd.Exit(err) } diff --git a/cmd/lifecycle/rebaser.go b/cmd/lifecycle/rebaser.go index 808a6dc7..c32dee14 100644 --- a/cmd/lifecycle/rebaser.go +++ b/cmd/lifecycle/rebaser.go @@ -131,7 +131,7 @@ func (r *rebaseCmd) Exec() error { } rebaser := &lifecycle.Rebaser{ - Logger: cmd.Logger, + Logger: cmd.DefaultLogger, } report, err := rebaser.Rebase(appImage, newBaseImage, r.imageNames[1:]) if err != nil { diff --git a/cmd/lifecycle/restorer.go b/cmd/lifecycle/restorer.go index 865faa79..4215d55f 100644 --- a/cmd/lifecycle/restorer.go +++ b/cmd/lifecycle/restorer.go @@ -32,7 +32,7 @@ func (r *restoreCmd) Args(nargs int, args []string) error { return cmd.FailErrCode(errors.New("received unexpected Args"), cmd.CodeInvalidArgs, "parse arguments") } if r.cacheImageTag == "" && r.cacheDir == "" { - cmd.Logger.Warn("Not restoring cached layer data, no cache flag specified.") + cmd.DefaultLogger.Warn("Not restoring cached layer data, no cache flag specified.") } return nil } @@ -63,7 +63,7 @@ func restore(layersDir string, group lifecycle.BuildpackGroup, cacheStore lifecy restorer := &lifecycle.Restorer{ LayersDir: layersDir, Buildpacks: group.Group, - Logger: cmd.Logger, + Logger: cmd.DefaultLogger, } if err := restorer.Restore(cacheStore); err != nil { diff --git a/cmd/logs.go b/cmd/logs.go index 63c51791..775afaf3 100644 --- a/cmd/logs.go +++ b/cmd/logs.go @@ -19,9 +19,8 @@ func init() { //color.Disable(!terminal.IsTerminal(int(os.Stdout.Fd()))) } -// Default logger var ( - Logger = &logger{ + DefaultLogger = &Logger{ &log.Logger{ Handler: &handler{ writer: os.Stdout, @@ -33,17 +32,17 @@ var ( phaseStyle = color.New(color.FgCyan).SprintfFunc() ) -type logger struct { +type Logger struct { *log.Logger } -func (l *logger) Phase(name string) { +func (l *Logger) Phase(name string) { l.Infof(phaseStyle("===> %s", name)) } func SetLogLevel(level string) *ErrorFail { var err error - Logger.Level, err = log.ParseLevel(level) + DefaultLogger.Level, err = log.ParseLevel(level) if err != nil { return FailErrCode(err, CodeInvalidArgs, "parse log level") } diff --git a/cmd/version.go b/cmd/version.go index 803b83a1..9e006141 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -2,7 +2,6 @@ package cmd import ( "fmt" - "strings" "github.com/buildpacks/lifecycle/api" ) @@ -16,17 +15,7 @@ var ( // SCMRepository is the source repository. SCMRepository = "" - // SupportedPlatformAPIs contains a comma separated list of supported platforms APIs - SupportedPlatformAPIs string - // DepreactedPlatformAPIs contains a comma separated list of depreacted platforms APIs - DeprecatedPlatformAPIs string - - EnvPlatformAPI = "CNB_PLATFORM_API" - EnvDeprecationMode = "CNB_DEPRECATION_MODE" - - // DefaultPlatformAPI specifies platform API to provide when "CNB_PLATFORM_API" is unset - DefaultPlatformAPI = "0.3" - DefaultDeprecationMode = "warn" + DeprecationMode = EnvOrDefault(EnvDeprecationMode, DefaultDeprecationMode) ) const ( @@ -45,21 +34,18 @@ func buildVersion() string { return fmt.Sprintf("%s+%s", Version, SCMCommit) } -func VerifyCompatibility() error { - apis, _ := api.NewAPIs(splitApis(SupportedPlatformAPIs), splitApis(DeprecatedPlatformAPIs)) - - requestedAPI := envOrDefault(EnvPlatformAPI, DefaultPlatformAPI) - if apis.IsSupported(requestedAPI) { - if apis.IsDeprecated(requestedAPI) { - switch envOrDefault(EnvDeprecationMode, DeprecationModeWarn) { +func VerifyPlatformAPI(requestedAPI string) error { + if api.Platform.IsSupported(requestedAPI) { + if api.Platform.IsDeprecated(requestedAPI) { + switch DeprecationMode { case DeprecationModeQuiet: break case DeprecationModeError: return platformAPIError(requestedAPI) case DeprecationModeWarn: - Logger.Warnf("Platform API '%s' is deprecated", requestedAPI) + DefaultLogger.Warnf("Platform API '%s' is deprecated", requestedAPI) default: - Logger.Warnf("Platform API '%s' is deprecated", requestedAPI) + DefaultLogger.Warnf("Platform API '%s' is deprecated", requestedAPI) } } return nil @@ -67,14 +53,6 @@ func VerifyCompatibility() error { return platformAPIError(requestedAPI) } -func splitApis(joined string) []string { - supported := strings.Split(joined, `,`) - if len(supported) == 1 && supported[0] == "" { - supported = nil - } - return supported -} - func platformAPIError(requestedAPI string) error { return FailErrCode( fmt.Errorf("set platform API: platform API version '%s' is incompatible with the lifecycle", requestedAPI), diff --git a/cmd/version_test.go b/cmd/version_test.go new file mode 100644 index 00000000..30d6d17f --- /dev/null +++ b/cmd/version_test.go @@ -0,0 +1,89 @@ +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) { + when("VerifyPlatformAPI", func() { + var ( + logHandler *memory.Handler + ) + + it.Before(func() { + var err error + api.Platform, err = api.NewAPIs([]string{"1.2", "2.1"}, []string{"1"}) + h.AssertNil(t, err) + logHandler = memory.New() + cmd.DefaultLogger = &cmd.Logger{Logger: &log.Logger{Handler: logHandler}} + }) + + 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("default deprecation mode", func() { + it("should warn", func() { + 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 API '1.1' is deprecated") + }) + }) + + 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 API '1.1' is deprecated") + }) + }) + + 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) + }) + }) + }) + }) +} diff --git a/restorer_test.go b/restorer_test.go index 73927087..b1d59afd 100644 --- a/restorer_test.go +++ b/restorer_test.go @@ -54,7 +54,7 @@ func testRestorer(t *testing.T, when spec.G, it spec.S) { Logger: &log.Logger{Handler: &discard.Handler{}}, } if testing.Verbose() { - restorer.Logger = cmd.Logger + restorer.Logger = cmd.DefaultLogger cmd.SetLogLevel("debug") } }) diff --git a/tools/descriptor/main.go b/tools/descriptor/main.go deleted file mode 100644 index 579ed972..00000000 --- a/tools/descriptor/main.go +++ /dev/null @@ -1,87 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strings" - - "github.com/BurntSushi/toml" -) - -type Descriptor struct { - APIs `toml:"apis"` - Lifecycle Lifecycle `toml:"lifecycle"` -} - -type Lifecycle struct { - Version string `toml:"version"` -} - -type APIs struct { - Buildpack APISet `toml:"buildpack"` - Platform APISet `toml:"platform"` -} - -type APISet struct { - Deprecated []string - Supported []string -} - -const ( - gitRepository = "github.com/buildpacks/lifecycle" -) - -// build-args generates ldflags from descriptor -// version parses and print version from descriptor -func main() { - if len(os.Args) != 3 { - usageAndExit() - } - descriptorPath := os.Args[2] - descriptor := Descriptor{} - _, err := toml.DecodeFile(descriptorPath, &descriptor) - if err != nil { - fmt.Printf("Failed to decode '%s': %s", descriptorPath, err) - os.Exit(2) - } - - switch os.Args[1] { //just print the version - case "version": - fmt.Print(descriptor.Lifecycle.Version) - case "build-args": - fmt.Print(buildArgs(descriptor)) - default: - usageAndExit() - } -} - -func buildArgs(descriptor Descriptor) string { - flags := []string{ - fmt.Sprintf( - "-X 'github.com/buildpacks/lifecycle/cmd.DeprecatedBuildpackAPIs=%s'", - strings.Join(descriptor.Buildpack.Deprecated, `,`), - ), - fmt.Sprintf( - "-X 'github.com/buildpacks/lifecycle/cmd.SupportedBuildpackAPIs=%s'", - strings.Join(descriptor.Buildpack.Supported, `,`), - ), - fmt.Sprintf( - "-X 'github.com/buildpacks/lifecycle/cmd.DeprecatedPlatformAPIs=%s'", - strings.Join(descriptor.Platform.Deprecated, `,`), - ), - fmt.Sprintf( - "-X 'github.com/buildpacks/lifecycle/cmd.SupportedPlatformAPIs=%s'", - strings.Join(descriptor.Platform.Supported, `,`), - ), - fmt.Sprintf( - "-X 'github.com/buildpacks/lifecycle/cmd.Version=%s'", - descriptor.Lifecycle.Version, - ), - } - return strings.Join(flags, " ") -} - -func usageAndExit() { - fmt.Println("USAGE: tools/descriptor/main.go [build-args, version] ") - os.Exit(1) -}