diff --git a/Makefile b/Makefile index a4f0a52c6..23a620804 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ all: clean verify test build build: @echo "> Building..." mkdir -p ./out - $(GOENV) $(GOCMD) build -mod=vendor -ldflags "-X 'main.Version=${PACK_VERSION}'" -o ./out/$(PACK_BIN) -a ./cmd/pack + $(GOENV) $(GOCMD) build -mod=vendor -ldflags "-X 'github.com/buildpack/pack/cmd.Version=${PACK_VERSION}'" -o ./out/$(PACK_BIN) -a ./cmd/pack package: tar czf ./out/$(ARCHIVE_NAME).tgz -C out/ pack diff --git a/build.go b/build.go index f9fe3f665..2436c7928 100644 --- a/build.go +++ b/build.go @@ -15,8 +15,10 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/pkg/errors" + "github.com/buildpack/pack/api" "github.com/buildpack/pack/build" "github.com/buildpack/pack/builder" + "github.com/buildpack/pack/cmd" "github.com/buildpack/pack/internal/archive" "github.com/buildpack/pack/internal/paths" "github.com/buildpack/pack/style" @@ -69,14 +71,14 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { return errors.Wrapf(err, "failed to fetch builder image '%s'", builderRef.Name()) } - builderImage, err := c.processBuilderImage(rawBuilderImage) + bldr, err := c.processBuilderImage(rawBuilderImage) if err != nil { return errors.Wrapf(err, "invalid builder '%s'", opts.Builder) } - runImage := c.resolveRunImage(opts.RunImage, imageRef.Context().RegistryStr(), builderImage.GetStackInfo(), opts.AdditionalMirrors) + runImage := c.resolveRunImage(opts.RunImage, imageRef.Context().RegistryStr(), bldr.GetStackInfo(), opts.AdditionalMirrors) - if _, err := c.validateRunImage(ctx, runImage, opts.NoPull, opts.Publish, builderImage.StackID); err != nil { + if _, err := c.validateRunImage(ctx, runImage, opts.NoPull, opts.Publish, bldr.StackID); err != nil { return errors.Wrapf(err, "invalid run-image '%s'", runImage) } @@ -91,6 +93,30 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { } defer c.docker.ImageRemove(context.Background(), ephemeralBuilder.Name(), types.ImageRemoveOptions{Force: true}) + descriptor := ephemeralBuilder.GetLifecycleDescriptor() + lifecycleVersion := descriptor.Info.Version + if lifecycleVersion == nil { + c.logger.Warnf("lifecycle version unknown, assuming %s", style.Symbol(builder.AssumedLifecycleVersion)) + lifecycleVersion = builder.VersionMustParse(builder.AssumedLifecycleVersion) + } else { + c.logger.Debugf("Executing lifecycle version %s", style.Symbol(lifecycleVersion.String())) + } + + lcPlatformAPIVersion := api.MustParse(builder.AssumedPlatformAPIVersion) + if descriptor.API.PlatformVersion != nil { + lcPlatformAPIVersion = descriptor.API.PlatformVersion + } + + if !api.MustParse(build.PlatformAPIVersion).SupportsVersion(lcPlatformAPIVersion) { + return errors.Errorf( + "pack %s (Platform API version %s) is incompatible with builder %s (Platform API version %s)", + cmd.Version, + build.PlatformAPIVersion, + style.Symbol(opts.Builder), + lcPlatformAPIVersion, + ) + } + return c.lifecycle.Execute(ctx, build.LifecycleOptions{ AppPath: appPath, Image: imageRef, diff --git a/build/build.go b/build/build.go index 6a0d73f9d..5f46a6cca 100644 --- a/build/build.go +++ b/build/build.go @@ -16,6 +16,9 @@ import ( "github.com/buildpack/pack/style" ) +// PlatformAPIVersion is the current Platform API Version supported by this version of pack. +const PlatformAPIVersion = "0.1" + type Lifecycle struct { builder *builder.Builder logger logging.Logger @@ -69,14 +72,6 @@ func (l *Lifecycle) Execute(ctx context.Context, opts LifecycleOptions) error { l.logger.Debugf("Build cache %s cleared", style.Symbol(buildCache.Name())) } - lifecycleVersion := l.builder.GetLifecycleDescriptor().Info.Version - if lifecycleVersion == nil { - l.logger.Warnf("lifecycle version unknown, assuming %s", style.Symbol(builder.AssumedLifecycleVersion)) - lifecycleVersion = builder.VersionMustParse(builder.AssumedLifecycleVersion) - } else { - l.logger.Debugf("Executing lifecycle version %s", style.Symbol(lifecycleVersion.String())) - } - l.logger.Debug(style.Step("DETECTING")) if err := l.Detect(ctx); err != nil { return err diff --git a/build_test.go b/build_test.go index d729f3daf..9f6f52081 100644 --- a/build_test.go +++ b/build_test.go @@ -25,8 +25,11 @@ import ( "github.com/buildpack/pack/api" "github.com/buildpack/pack/blob" + "github.com/buildpack/pack/build" "github.com/buildpack/pack/builder" + "github.com/buildpack/pack/cmd" ifakes "github.com/buildpack/pack/internal/fakes" + "github.com/buildpack/pack/style" h "github.com/buildpack/pack/testhelpers" ) @@ -90,7 +93,7 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }, API: builder.LifecycleAPI{ BuildpackVersion: api.MustParse("0.3"), - PlatformVersion: api.MustParse("0.2"), + PlatformVersion: api.MustParse(build.PlatformAPIVersion), }, }, }, @@ -336,6 +339,11 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { Image: "some/run", }, }, + Lifecycle: builder.LifecycleMetadata{ + API: builder.LifecycleAPI{ + PlatformVersion: api.MustParse(build.PlatformAPIVersion), + }, + }, }) fakeImageFetcher.LocalImages[customBuilderImage.Name()] = customBuilderImage @@ -911,6 +919,80 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }) }) }) + + when("Lifecycle option", func() { + when("Platform API", func() { + when("lifecycle platform API is compatible", func() { + it("should succeed", func() { + err := subject.Build(context.TODO(), BuildOptions{ + Image: "some/app", + Builder: builderName, + }) + + h.AssertNil(t, err) + }) + }) + + when("lifecycle platform API is not compatible", func() { + var incompatibleBuilderImage *fakes.Image + it.Before(func() { + incompatibleBuilderImage = ifakes.NewFakeBuilderImage(t, + "incompatible-"+builderName, + defaultBuilderStackID, + "1234", + "5678", + builder.Metadata{ + Stack: builder.StackMetadata{ + RunImage: builder.RunImageMetadata{ + Image: "default/run", + Mirrors: []string{ + "registry1.example.com/run/mirror", + "registry2.example.com/run/mirror", + }, + }, + }, + Lifecycle: builder.LifecycleMetadata{ + LifecycleInfo: builder.LifecycleInfo{ + Version: &builder.Version{ + Version: *semver.MustParse("0.3.0"), + }, + }, + API: builder.LifecycleAPI{ + BuildpackVersion: api.MustParse("0.3"), + PlatformVersion: api.MustParse("0.9"), + }, + }, + }, + ) + + fakeImageFetcher.LocalImages[incompatibleBuilderImage.Name()] = incompatibleBuilderImage + }) + + it.After(func() { + incompatibleBuilderImage.Cleanup() + }) + + it("should error", func() { + builderName := incompatibleBuilderImage.Name() + + err := subject.Build(context.TODO(), BuildOptions{ + Image: "some/app", + Builder: builderName, + }) + + h.AssertError(t, + err, + fmt.Sprintf( + "pack %s (Platform API version %s) is incompatible with builder %s (Platform API version %s)", + cmd.Version, + build.PlatformAPIVersion, + style.Symbol(builderName), + "0.9", + )) + }) + }) + }) + }) }) } diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100644 index 000000000..3a347a3cf --- /dev/null +++ b/cmd/cmd.go @@ -0,0 +1,6 @@ +package cmd + +var ( + // Version is the version of `pack`. It is injected at compile time. + Version = "0.0.0" +) diff --git a/cmd/pack/main.go b/cmd/pack/main.go index 3e7fc6621..067aeb5e7 100644 --- a/cmd/pack/main.go +++ b/cmd/pack/main.go @@ -8,16 +8,14 @@ import ( "github.com/spf13/cobra" "github.com/buildpack/pack" + "github.com/buildpack/pack/cmd" "github.com/buildpack/pack/commands" "github.com/buildpack/pack/config" clilogger "github.com/buildpack/pack/internal/logging" "github.com/buildpack/pack/logging" ) -var ( - Version = "0.0.0" - packClient pack.Client -) +var packClient pack.Client func main() { // create logger with defaults @@ -64,7 +62,7 @@ func main() { rootCmd.AddCommand(commands.SuggestBuilders(logger, &packClient)) rootCmd.AddCommand(commands.SuggestStacks(logger)) - rootCmd.AddCommand(commands.Version(logger, Version)) + rootCmd.AddCommand(commands.Version(logger, cmd.Version)) rootCmd.AddCommand(commands.CompletionCommand(logger))