Hardcode platform/buildpack APIs

Signed-off-by: Emily Casey <ecasey@vmware.com>
This commit is contained in:
Emily Casey 2020-07-28 12:06:53 -04:00
parent cddc9b9a43
commit 36db452da0
20 changed files with 205 additions and 293 deletions

View File

@ -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..."

View File

@ -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)

View File

@ -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"

View File

@ -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)

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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()),

View File

@ -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 {

View File

@ -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...),

View File

@ -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")

View File

@ -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 == "" {

View File

@ -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)
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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")
}

View File

@ -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),

89
cmd/version_test.go Normal file
View File

@ -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)
})
})
})
})
}

View File

@ -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")
}
})

View File

@ -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] <path-to-lifecycle-descriptor-file>")
os.Exit(1)
}