diff --git a/build.go b/build.go index b6dceb396c..8b3f272887 100644 --- a/build.go +++ b/build.go @@ -6,6 +6,7 @@ import ( "context" "crypto/md5" "fmt" + "github.com/buildpack/lifecycle/image" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" "io" @@ -18,28 +19,26 @@ import ( "strings" "time" - "github.com/BurntSushi/toml" - "github.com/buildpack/lifecycle" "github.com/buildpack/pack/config" "github.com/buildpack/pack/docker" "github.com/buildpack/pack/fs" - "github.com/buildpack/pack/image" + + "github.com/BurntSushi/toml" + "github.com/buildpack/lifecycle" dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" - dockercli "github.com/docker/docker/client" - "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/uuid" "github.com/pkg/errors" ) type BuildFactory struct { - Cli Docker - Stdout io.Writer - Stderr io.Writer - Log *log.Logger - FS FS - Config *config.Config - Images Images + Cli Docker + Stdout io.Writer + Stderr io.Writer + Log *log.Logger + FS FS + Config *config.Config + ImageFactory ImageFactory } type BuildFlags struct { @@ -69,7 +68,6 @@ type BuildConfig struct { Log *log.Logger FS FS Config *config.Config - Images Images // Above are copied from BuildFactory WorkspaceVolume string CacheVolume string @@ -91,7 +89,6 @@ func DefaultBuildFactory() (*BuildFactory, error) { Stderr: os.Stderr, Log: log.New(os.Stdout, "", log.LstdFlags), FS: &fs.FS{}, - Images: &image.Client{}, } var err error @@ -105,6 +102,11 @@ func DefaultBuildFactory() (*BuildFactory, error) { return nil, err } + f.ImageFactory, err = image.DefaultFactory() + if err != nil { + return nil, err + } + return f, nil } @@ -138,7 +140,6 @@ func (bf *BuildFactory) BuildConfigFromFlags(f *BuildFlags) (*BuildConfig, error Log: bf.Log, FS: bf.FS, Config: bf.Config, - Images: bf.Images, WorkspaceVolume: fmt.Sprintf("pack-workspace-%x", uuid.New().String()), CacheVolume: fmt.Sprintf("pack-cache-%x", md5.Sum([]byte(appDir))), } @@ -159,12 +160,14 @@ func (bf *BuildFactory) BuildConfigFromFlags(f *BuildFlags) (*BuildConfig, error } if !f.NoPull { bf.Log.Printf("Pulling builder image '%s' (use --no-pull flag to skip this step)", b.Builder) - if err := bf.Cli.PullImage(b.Builder); err != nil { - return nil, err - } } - builderStackID, err := b.imageLabel(b.Builder, "io.buildpacks.stack.id", true) + builderImage, err := bf.ImageFactory.NewLocal(b.Builder, !f.NoPull) + if err != nil { + return nil, err + } + + builderStackID, err := builderImage.Label("io.buildpacks.stack.id") if err != nil { return nil, fmt.Errorf(`invalid builder image "%s": %s`, b.Builder, err) } @@ -191,14 +194,23 @@ func (bf *BuildFactory) BuildConfigFromFlags(f *BuildFlags) (*BuildConfig, error b.Log.Printf("Selected run image '%s' from stack '%s'\n", b.RunImage, builderStackID) } - if !f.NoPull && !f.Publish { - bf.Log.Printf("Pulling run image '%s' (use --no-pull flag to skip this step)", b.RunImage) - if err := bf.Cli.PullImage(b.RunImage); err != nil { + var runImage image.Image + if f.Publish { + runImage, err = bf.ImageFactory.NewRemote(b.RunImage) + if err != nil { + return nil, err + } + }else { + if !f.NoPull { + bf.Log.Printf("Pulling run image '%s' (use --no-pull flag to skip this step)", b.RunImage) + } + runImage, err = bf.ImageFactory.NewLocal(b.RunImage, !f.NoPull) + if err != nil { return nil, err } } - if runStackID, err := b.imageLabel(b.RunImage, "io.buildpacks.stack.id", !f.Publish); err != nil { + if runStackID, err := runImage.Label("io.buildpacks.stack.id"); err != nil { return nil, fmt.Errorf(`invalid run image "%s": %s`, b.RunImage, err) } else if runStackID == "" { return nil, fmt.Errorf(`invalid run image "%s": missing required label "io.buildpacks.stack.id"`, b.RunImage) @@ -587,37 +599,6 @@ func (b *BuildConfig) Export() error { return nil } -func (b *BuildConfig) imageLabel(repoName, key string, useDaemon bool) (string, error) { - var labels map[string]string - if useDaemon { - i, _, err := b.Cli.ImageInspectWithRaw(context.Background(), repoName) - if dockercli.IsErrNotFound(err) { - return "", nil - } else if err != nil { - return "", errors.Wrap(err, "analyze read previous image config") - } - labels = i.Config.Labels - } else { - origImage, err := b.Images.ReadImage(repoName, false) - if err != nil || origImage == nil { - return "", err - } - config, err := origImage.ConfigFile() - if err != nil { - if remoteErr, ok := err.(*remote.Error); ok && len(remoteErr.Errors) > 0 { - switch remoteErr.Errors[0].Code { - case remote.UnauthorizedErrorCode, remote.ManifestUnknownErrorCode: - return "", nil - } - } - return "", errors.Wrapf(err, "access manifest: %s", repoName) - } - labels = config.Config.Labels - } - - return labels[key], nil -} - func (b *BuildConfig) packUidGid(builder string) (int, int, error) { i, _, err := b.Cli.ImageInspectWithRaw(context.Background(), builder) if err != nil { @@ -666,4 +647,4 @@ func (b *BuildConfig) chownDir(path string, uid, gid int) error { return err } return nil -} \ No newline at end of file +} diff --git a/build_test.go b/build_test.go index 2b0ee176b7..481f9c4e53 100644 --- a/build_test.go +++ b/build_test.go @@ -20,13 +20,11 @@ import ( "github.com/buildpack/pack/config" "github.com/buildpack/pack/docker" "github.com/buildpack/pack/fs" - "github.com/buildpack/pack/image" "github.com/buildpack/pack/mocks" h "github.com/buildpack/pack/testhelpers" dockertypes "github.com/docker/docker/api/types" dockercontainer "github.com/docker/docker/api/types/container" "github.com/golang/mock/gomock" - "github.com/google/go-containerregistry/pkg/v1" "github.com/google/uuid" "github.com/pkg/errors" "github.com/sclevine/spec" @@ -68,7 +66,6 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { Stderr: &buf, Log: log.New(&buf, "", log.LstdFlags|log.Lshortfile), FS: &fs.FS{}, - Images: &image.Client{}, } log.SetOutput(ioutil.Discard) dockerCli, err = docker.New() @@ -85,17 +82,17 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { var ( factory *pack.BuildFactory mockController *gomock.Controller - mockImages *mocks.MockImages + mockImageFactory *mocks.MockImageFactory mockDocker *mocks.MockDocker ) it.Before(func() { mockController = gomock.NewController(t) - mockImages = mocks.NewMockImages(mockController) + mockImageFactory = mocks.NewMockImageFactory(mockController) mockDocker = mocks.NewMockDocker(mockController) factory = &pack.BuildFactory{ - Images: mockImages, + ImageFactory: mockImageFactory, Config: &config.Config{ DefaultBuilder: "some/builder", Stacks: []config.Stack{ @@ -115,18 +112,14 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }) it("defaults to daemon, default-builder, pulls builder and run images, selects run-image using builder's stack", func() { - mockDocker.EXPECT().PullImage("some/builder") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "some/builder").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{"io.buildpacks.stack.id": "some.stack.id"}, - }, - }, nil, nil) - mockDocker.EXPECT().PullImage("some/run") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "some/run").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{"io.buildpacks.stack.id": "some.stack.id"}, - }, - }, nil, nil) + mockBuilderImage := mocks.NewMockImage(mockController) + mockBuilderImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("some/builder", true).Return(mockBuilderImage, nil) + + mockRunImage := mocks.NewMockImage(mockController) + mockRunImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("some/run", true).Return(mockRunImage, nil) + config, err := factory.BuildConfigFromFlags(&pack.BuildFlags{ RepoName: "some/app", @@ -134,21 +127,18 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }) h.AssertNil(t, err) h.AssertEq(t, config.RunImage, "some/run") + h.AssertEq(t, config.Builder, "some/builder") }) it("respects builder from flags", func() { - mockDocker.EXPECT().PullImage("custom/builder") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "custom/builder").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{"io.buildpacks.stack.id": "some.stack.id"}, - }, - }, nil, nil) - mockDocker.EXPECT().PullImage("some/run") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "some/run").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{"io.buildpacks.stack.id": "some.stack.id"}, - }, - }, nil, nil) + mockBuilderImage := mocks.NewMockImage(mockController) + mockBuilderImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("custom/builder", true).Return(mockBuilderImage, nil) + + mockRunImage := mocks.NewMockImage(mockController) + mockRunImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("some/run", true).Return(mockRunImage, nil) + config, err := factory.BuildConfigFromFlags(&pack.BuildFlags{ RepoName: "some/app", @@ -156,21 +146,38 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }) h.AssertNil(t, err) h.AssertEq(t, config.RunImage, "some/run") + h.AssertEq(t, config.Builder, "custom/builder") + }) + + it("doesn't pull builder or run images when --no-pull is passed", func() { + mockBuilderImage := mocks.NewMockImage(mockController) + mockBuilderImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("custom/builder", false).Return(mockBuilderImage, nil) + + mockRunImage := mocks.NewMockImage(mockController) + mockRunImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("some/run", false).Return(mockRunImage, nil) + + + config, err := factory.BuildConfigFromFlags(&pack.BuildFlags{ + NoPull: true, + RepoName: "some/app", + Builder: "custom/builder", + }) + h.AssertNil(t, err) + h.AssertEq(t, config.RunImage, "some/run") + h.AssertEq(t, config.Builder, "custom/builder") }) it("selects run images with matching registry", func() { - mockDocker.EXPECT().PullImage("some/builder") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "some/builder").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{"io.buildpacks.stack.id": "some.stack.id"}, - }, - }, nil, nil) - mockDocker.EXPECT().PullImage("registry.com/some/run") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "registry.com/some/run").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{"io.buildpacks.stack.id": "some.stack.id"}, - }, - }, nil, nil) + mockBuilderImage := mocks.NewMockImage(mockController) + mockBuilderImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("some/builder", true).Return(mockBuilderImage, nil) + + mockRunImage := mocks.NewMockImage(mockController) + mockRunImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("registry.com/some/run", true).Return(mockRunImage, nil) + config, err := factory.BuildConfigFromFlags(&pack.BuildFlags{ RepoName: "registry.com/some/app", @@ -178,24 +185,17 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }) h.AssertNil(t, err) h.AssertEq(t, config.RunImage, "registry.com/some/run") + h.AssertEq(t, config.Builder, "some/builder") }) - it("doesn't pull run images when --publish is passed", func() { - mockDocker.EXPECT().PullImage("some/builder") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "some/builder").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{"io.buildpacks.stack.id": "some.stack.id"}, - }, - }, nil, nil) - mockRunImage := mocks.NewMockV1Image(mockController) - mockImages.EXPECT().ReadImage("some/run", false).Return(mockRunImage, nil) - mockRunImage.EXPECT().ConfigFile().Return(&v1.ConfigFile{ - Config: v1.Config{ - Labels: map[string]string{ - "io.buildpacks.stack.id": "some.stack.id", - }, - }, - }, nil) + it("uses a remote run image when --publish is passed", func() { + mockBuilderImage := mocks.NewMockImage(mockController) + mockBuilderImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("some/builder", true).Return(mockBuilderImage, nil) + + mockRunImage := mocks.NewMockImage(mockController) + mockRunImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewRemote("some/run").Return(mockRunImage, nil) config, err := factory.BuildConfigFromFlags(&pack.BuildFlags{ RepoName: "some/app", @@ -204,24 +204,17 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }) h.AssertNil(t, err) h.AssertEq(t, config.RunImage, "some/run") + h.AssertEq(t, config.Builder, "some/builder") }) it("allows run-image from flags if the stacks match", func() { - mockDocker.EXPECT().PullImage("some/builder") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "some/builder").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{"io.buildpacks.stack.id": "some.stack.id"}, - }, - }, nil, nil) - mockRunImage := mocks.NewMockV1Image(mockController) - mockImages.EXPECT().ReadImage("override/run", false).Return(mockRunImage, nil) - mockRunImage.EXPECT().ConfigFile().Return(&v1.ConfigFile{ - Config: v1.Config{ - Labels: map[string]string{ - "io.buildpacks.stack.id": "some.stack.id", - }, - }, - }, nil) + mockBuilderImage := mocks.NewMockImage(mockController) + mockBuilderImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("some/builder", true).Return(mockBuilderImage, nil) + + mockRunImage := mocks.NewMockImage(mockController) + mockRunImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewRemote("override/run").Return(mockRunImage, nil) config, err := factory.BuildConfigFromFlags(&pack.BuildFlags{ RepoName: "some/app", @@ -231,24 +224,17 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }) h.AssertNil(t, err) h.AssertEq(t, config.RunImage, "override/run") + h.AssertEq(t, config.Builder, "some/builder") }) it("doesn't allows run-image from flags if the stacks are difference", func() { - mockDocker.EXPECT().PullImage("some/builder") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "some/builder").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{"io.buildpacks.stack.id": "some.stack.id"}, - }, - }, nil, nil) - mockRunImage := mocks.NewMockV1Image(mockController) - mockImages.EXPECT().ReadImage("override/run", false).Return(mockRunImage, nil) - mockRunImage.EXPECT().ConfigFile().Return(&v1.ConfigFile{ - Config: v1.Config{ - Labels: map[string]string{ - "io.buildpacks.stack.id": "other.stack.id", - }, - }, - }, nil) + mockBuilderImage := mocks.NewMockImage(mockController) + mockBuilderImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("some/builder", true).Return(mockBuilderImage, nil) + + mockRunImage := mocks.NewMockImage(mockController) + mockRunImage.EXPECT().Label("io.buildpacks.stack.id").Return("other.stack.id", nil) + mockImageFactory.EXPECT().NewRemote("override/run").Return(mockRunImage, nil) _, err := factory.BuildConfigFromFlags(&pack.BuildFlags{ RepoName: "some/app", @@ -260,41 +246,30 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }) it("uses working dir if appDir is set to placeholder value", func() { - mockDocker.EXPECT().PullImage("some/builder") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "some/builder").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{"io.buildpacks.stack.id": "some.stack.id"}, - }, - }, nil, nil) - mockRunImage := mocks.NewMockV1Image(mockController) - mockImages.EXPECT().ReadImage("override/run", false).Return(mockRunImage, nil) - mockRunImage.EXPECT().ConfigFile().Return(&v1.ConfigFile{ - Config: v1.Config{ - Labels: map[string]string{ - "io.buildpacks.stack.id": "some.stack.id", - }, - }, - }, nil) + mockBuilderImage := mocks.NewMockImage(mockController) + mockBuilderImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("some/builder", true).Return(mockBuilderImage, nil) + + mockRunImage := mocks.NewMockImage(mockController) + mockRunImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewRemote("some/run").Return(mockRunImage, nil) config, err := factory.BuildConfigFromFlags(&pack.BuildFlags{ RepoName: "some/app", Builder: "some/builder", - RunImage: "override/run", Publish: true, AppDir: "current working directory", }) h.AssertNil(t, err) - h.AssertEq(t, config.RunImage, "override/run") + h.AssertEq(t, config.RunImage, "some/run") + h.AssertEq(t, config.Builder, "some/builder") h.AssertEq(t, config.AppDir, os.Getenv("PWD")) }) it("returns an errors when the builder stack label is missing", func() { - mockDocker.EXPECT().PullImage("some/builder") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "some/builder").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{}, - }, - }, nil, nil) + mockBuilderImage := mocks.NewMockImage(mockController) + mockBuilderImage.EXPECT().Label("io.buildpacks.stack.id").Return("", nil) + mockImageFactory.EXPECT().NewLocal("some/builder", true).Return(mockBuilderImage, nil) _, err := factory.BuildConfigFromFlags(&pack.BuildFlags{ RepoName: "some/app", @@ -304,18 +279,13 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { }) it("sets EnvFile", func() { - mockDocker.EXPECT().PullImage("some/builder") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "some/builder").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{"io.buildpacks.stack.id": "some.stack.id"}, - }, - }, nil, nil) - mockDocker.EXPECT().PullImage("some/run") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "some/run").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{"io.buildpacks.stack.id": "some.stack.id"}, - }, - }, nil, nil) + mockBuilderImage := mocks.NewMockImage(mockController) + mockBuilderImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("some/builder", true).Return(mockBuilderImage, nil) + + mockRunImage := mocks.NewMockImage(mockController) + mockRunImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("some/run", true).Return(mockRunImage, nil) envFile, err := ioutil.TempFile("", "pack.build.enfile") h.AssertNil(t, err) diff --git a/cmd/pack/main.go b/cmd/pack/main.go index 57f8ae2b5a..033d9d1d3d 100644 --- a/cmd/pack/main.go +++ b/cmd/pack/main.go @@ -12,7 +12,8 @@ import ( "github.com/buildpack/pack" "github.com/buildpack/pack/config" "github.com/buildpack/pack/fs" - "github.com/buildpack/pack/image" + + "github.com/buildpack/lifecycle/image" "github.com/spf13/cobra" ) diff --git a/create_builder.go b/create_builder.go index e686f31343..db881d5854 100644 --- a/create_builder.go +++ b/create_builder.go @@ -14,10 +14,10 @@ import ( "github.com/BurntSushi/toml" "github.com/buildpack/lifecycle" + "github.com/buildpack/lifecycle/image" "github.com/pkg/errors" "github.com/buildpack/pack/config" - "github.com/buildpack/pack/image" ) type BuilderTOML struct { @@ -91,7 +91,7 @@ func (f *BuilderFactory) resolveBuildpackURI(builderDir string, b Buildpack) (Bu return Buildpack{}, err } switch asurl.Scheme { - case "", // This is the only way to support relative filepaths + case "", // This is the only way to support relative filepaths "file": // URIs with file:// protocol force the use of absolute paths. Host=localhost may be implied with file:/// path := asurl.Path diff --git a/create_builder_test.go b/create_builder_test.go index 063e1dcefe..6834e4b113 100644 --- a/create_builder_test.go +++ b/create_builder_test.go @@ -33,9 +33,6 @@ func TestCreateBuilder(t *testing.T) { spec.Run(t, "create-builder", testCreateBuilder, spec.Sequential(), spec.Report(report.Terminal{})) } -//go:generate mockgen -package mocks -destination mocks/img.go -mock_names Image=MockV1Image github.com/google/go-containerregistry/pkg/v1 Image -//go:generate mockgen -package mocks -destination mocks/store.go github.com/buildpack/lifecycle/img Store - func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { when("#BuilderFactory", func() { var ( diff --git a/go.mod b/go.mod index 483272963e..ed71cc08c7 100644 --- a/go.mod +++ b/go.mod @@ -2,12 +2,12 @@ module github.com/buildpack/pack require ( github.com/BurntSushi/toml v0.3.1 - github.com/buildpack/lifecycle v0.0.0-20181116214402-fa66d9071434 + github.com/buildpack/lifecycle v0.0.0-20181211162153-177afa69a3ea github.com/buildpack/packs v0.0.0-20180824001031-aa30a412923763df37e83f14a6e4e0fe07e11f25 github.com/dgodd/dockerdial v1.0.1 github.com/docker/docker v0.7.3-0.20181027010111-b8e87cfdad8d github.com/docker/go-connections v0.4.0 - github.com/golang/mock v1.1.1 + github.com/golang/mock v1.2.0 github.com/google/go-cmp v0.2.0 github.com/google/go-containerregistry v0.0.0-20181023232207-eb57122f1bf9 github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d diff --git a/go.sum b/go.sum index 01ad6a6277..4a351085a2 100644 --- a/go.sum +++ b/go.sum @@ -19,11 +19,16 @@ github.com/buildpack/lifecycle v0.0.0-20181025145258-a42887a7d29f h1:6+0q7PRrM7E github.com/buildpack/lifecycle v0.0.0-20181025145258-a42887a7d29f/go.mod h1:rmQIWPE7ORJsSn/gHoT5fQAbsK7zV0X9yttd0cuGv5M= github.com/buildpack/lifecycle v0.0.0-20181116214402-fa66d9071434 h1:VQflgDluvbcgpyyzjdNRBwSMuDcDjw1l/h+ePIzzsFA= github.com/buildpack/lifecycle v0.0.0-20181116214402-fa66d9071434/go.mod h1:Vk2KGQyLuqXrfyY1siCrHTqtvciRQIgzTbrv5iILoKU= +github.com/buildpack/lifecycle v0.0.0-20181204191349-898e1dd05a0a h1:Gk7xHMNFRMLKVIK/6VjZh41IKHGuCaiuSgRb53ft3n8= +github.com/buildpack/lifecycle v0.0.0-20181211162153-177afa69a3ea h1:Eer+LeRdEQgIZXk+ix34VAvcVEzklAEG+8ZLUy4T2ZU= +github.com/buildpack/lifecycle v0.0.0-20181211162153-177afa69a3ea/go.mod h1:3XZXGTIcoU8nr3mhC0/n6VRJvSVL8e5+6GOk01git7U= github.com/buildpack/packs v0.0.0-20180824001031-aa30a412923763df37e83f14a6e4e0fe07e11f25 h1:gaFgBRK9mKLhfH26GLqMYmriWawmcUvYYgta73tHVJ4= github.com/buildpack/packs v0.0.0-20180824001031-aa30a412923763df37e83f14a6e4e0fe07e11f25/go.mod h1:jcquCT5a2gbyJw8WMkfgq36g7yc5VDSABVnCctIUKs8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/containerd/continuity v0.0.0-20181027224239-bea7585dbfac h1:PThQaO4yCvJzJBUW1XoFQxLotWRhvX2fgljJX8yrhFI= github.com/containerd/continuity v0.0.0-20181027224239-bea7585dbfac/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M= +github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/cpuguy83/go-md2man v1.0.8/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -57,6 +62,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -90,6 +96,7 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs= github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -113,6 +120,8 @@ github.com/sclevine/spec v1.0.0 h1:ILQ08A/CHCz8GGqivOvI54Hy1U40wwcpkf7WtB1MQfY= github.com/sclevine/spec v1.0.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sirupsen/logrus v1.1.1 h1:VzGj7lhU7KEB9e9gMpAV/v5XT2NVSvLJhJLCWbnkgXg= github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/spf13/afero v1.1.1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -126,6 +135,8 @@ github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= +github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50/go.mod h1:1pdIZTAHUz+HDKDVZ++5xg/duPlhKAIzw9qy42CWYp4= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -138,6 +149,8 @@ golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3 h1:czFLhve3vsQetD6JOJ8NZZvGQ golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519 h1:x6rhz8Y9CjbgQkccRGmELH6K+LJj7tOoh3XWeC1yaQM= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -148,6 +161,8 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUk golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181025063200-d989b31c8746 h1:zTiiIq2XH/ldZGPA59ILL7NbDlz/btn3iJvO7H57mY8= golang.org/x/sys v0.0.0-20181025063200-d989b31c8746/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU= +golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= @@ -168,6 +183,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.1.0+incompatible h1:5USw7CrJBYKqjg9R7QlA6jzqZKEAtvW82aNmsxxGPxw= gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.0.0-20180628040859-072894a440bd/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= k8s.io/apimachinery v0.0.0-20180621070125-103fd098999d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= diff --git a/image/factory.go b/image/factory.go deleted file mode 100644 index ea53b2dbaa..0000000000 --- a/image/factory.go +++ /dev/null @@ -1,94 +0,0 @@ -package image - -import ( - "context" - "io" - "log" - "os" - - "github.com/buildpack/lifecycle/img" - "github.com/buildpack/pack/docker" - "github.com/buildpack/pack/fs" - "github.com/buildpack/packs" - "github.com/docker/docker/api/types" - "github.com/google/go-containerregistry/pkg/v1" -) - -type Image interface { - Label(string) (string, error) - Rename(name string) - Name() string - Digest() (string, error) - Rebase(string, Image) error - SetLabel(string, string) error - TopLayer() (string, error) - AddLayer(path string) error - ReuseLayer(sha string) error - Save() (string, error) -} - -type Docker interface { - PullImage(ref string) error - ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) - ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) - ImageRemove(ctx context.Context, ref string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) - ImageLoad(ctx context.Context, r io.Reader, quiet bool) (types.ImageLoadResponse, error) - ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) -} - -type Factory struct { - Docker Docker - Log *log.Logger - Stdout io.Writer - FS *fs.FS -} - -func DefaultFactory() (*Factory, error) { - f := &Factory{ - Stdout: os.Stdout, - Log: log.New(os.Stdout, "", log.LstdFlags), - FS: &fs.FS{}, - } - - var err error - f.Docker, err = docker.New() - if err != nil { - return nil, err - } - - return f, nil -} - -type Client struct{} - -func (c *Client) ReadImage(repoName string, useDaemon bool) (v1.Image, error) { - repoStore, err := c.RepoStore(repoName, useDaemon) - if err != nil { - return nil, err - } - - origImage, err := repoStore.Image() - if err != nil { - // Assume error is due to non-existent image - return nil, nil - } - if _, err := origImage.RawManifest(); err != nil { - // Assume error is due to non-existent image - // This is necessary for registries - return nil, nil - } - - return origImage, nil -} - -func (c *Client) RepoStore(repoName string, useDaemon bool) (img.Store, error) { - newRepoStore := img.NewRegistry - if useDaemon { - newRepoStore = img.NewDaemon - } - repoStore, err := newRepoStore(repoName) - if err != nil { - return nil, packs.FailErr(err, "access", repoName) - } - return repoStore, nil -} diff --git a/image/local.go b/image/local.go deleted file mode 100644 index 6e6899b0c2..0000000000 --- a/image/local.go +++ /dev/null @@ -1,408 +0,0 @@ -package image - -import ( - "archive/tar" - "context" - "crypto/sha256" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "math/rand" - "os" - "path/filepath" - "strings" - "sync" - "time" - - "github.com/buildpack/pack/fs" - "github.com/docker/docker/api/types" - dockercli "github.com/docker/docker/client" - "github.com/google/go-containerregistry/pkg/name" - "github.com/pkg/errors" -) - -type local struct { - RepoName string - Docker Docker - Inspect types.ImageInspect - layerPaths []string - Stdout io.Writer - FS *fs.FS - currentTempImage string - prevDir string - prevMap map[string]string - prevOnce *sync.Once - easyAddLayers []string -} - -func (f *Factory) NewLocal(repoName string, pull bool) (Image, error) { - if pull { - f.Log.Printf("Pulling image '%s'\n", repoName) - if err := f.Docker.PullImage(repoName); err != nil { - return nil, fmt.Errorf("failed to pull image '%s' : %s", repoName, err) - } - } - - inspect, _, err := f.Docker.ImageInspectWithRaw(context.Background(), repoName) - if err != nil && !dockercli.IsErrNotFound(err) { - return nil, errors.Wrap(err, "analyze read previous image config") - } - - return &local{ - Docker: f.Docker, - RepoName: repoName, - Inspect: inspect, - layerPaths: make([]string, len(inspect.RootFS.Layers)), - Stdout: f.Stdout, - FS: f.FS, - prevOnce: &sync.Once{}, - }, nil -} - -func (l *local) Label(key string) (string, error) { - if l.Inspect.Config == nil { - return "", fmt.Errorf("failed to get label, image '%s' does not exist", l.RepoName) - } - labels := l.Inspect.Config.Labels - return labels[key], nil -} - -func (l *local) Rename(name string) { - l.easyAddLayers = nil - if inspect, _, err := l.Docker.ImageInspectWithRaw(context.TODO(), name); err == nil { - if len(inspect.RootFS.Layers) > len(l.Inspect.RootFS.Layers) { - l.easyAddLayers = inspect.RootFS.Layers[len(l.Inspect.RootFS.Layers):] - } - } - - l.RepoName = name -} - -func (l *local) Name() string { - return l.RepoName -} - -func (l *local) Digest() (string, error) { - if l.Inspect.Config == nil { - return "", fmt.Errorf("failed to get digest, image '%s' does not exist", l.RepoName) - } - if len(l.Inspect.RepoDigests) == 0 { - return "", nil - } - parts := strings.Split(l.Inspect.RepoDigests[0], "@") - if len(parts) != 2 { - return "", fmt.Errorf("failed to get digest, image '%s' has malformed digest '%s'", l.RepoName, l.Inspect.RepoDigests[0]) - } - return parts[1], nil -} - -func (l *local) Rebase(baseTopLayer string, newBase Image) error { - ctx := context.Background() - - // FIND TOP LAYER - keepLayers := -1 - for i, diffID := range l.Inspect.RootFS.Layers { - if diffID == baseTopLayer { - keepLayers = len(l.Inspect.RootFS.Layers) - i - 1 - break - } - } - if keepLayers == -1 { - return fmt.Errorf("'%s' not found in '%s' during rebase", baseTopLayer, l.RepoName) - } - - // SWITCH BASE LAYERS - newBaseInspect, _, err := l.Docker.ImageInspectWithRaw(ctx, newBase.Name()) - if err != nil { - return errors.Wrap(err, "analyze read previous image config") - } - l.Inspect.RootFS.Layers = newBaseInspect.RootFS.Layers - l.layerPaths = make([]string, len(l.Inspect.RootFS.Layers)) - - // SAVE CURRENT IMAGE TO DISK - if err := l.prevDownload(); err != nil { - return err - } - - // READ MANIFEST.JSON - b, err := ioutil.ReadFile(filepath.Join(l.prevDir, "manifest.json")) - if err != nil { - return err - } - var manifest []struct{ Layers []string } - if err := json.Unmarshal(b, &manifest); err != nil { - return err - } - if len(manifest) != 1 { - return fmt.Errorf("expected 1 image received %d", len(manifest)) - } - - // ADD EXISTING LAYERS - for _, filename := range manifest[0].Layers[(len(manifest[0].Layers) - keepLayers):] { - if err := l.AddLayer(filepath.Join(l.prevDir, filename)); err != nil { - return err - } - } - - return nil -} - -func (l *local) SetLabel(key, val string) error { - if l.Inspect.Config == nil { - return fmt.Errorf("failed to set label, image '%s' does not exist", l.RepoName) - } - l.Inspect.Config.Labels[key] = val - return nil -} - -func (l *local) TopLayer() (string, error) { - all := l.Inspect.RootFS.Layers - topLayer := all[len(all)-1] - return topLayer, nil -} - -func (l *local) AddLayer(path string) error { - f, err := os.Open(path) - if err != nil { - return errors.Wrapf(err, "AddLayer: open layer: %s", path) - } - defer f.Close() - hasher := sha256.New() - if _, err := io.Copy(hasher, f); err != nil { - return errors.Wrapf(err, "AddLayer: calculate checksum: %s", path) - } - sha := hex.EncodeToString(hasher.Sum(make([]byte, 0, hasher.Size()))) - - l.Inspect.RootFS.Layers = append(l.Inspect.RootFS.Layers, "sha256:"+sha) - l.layerPaths = append(l.layerPaths, path) - l.easyAddLayers = nil - - return nil -} - -func (l *local) ReuseLayer(sha string) error { - if len(l.easyAddLayers) > 0 && l.easyAddLayers[0] == sha { - l.Inspect.RootFS.Layers = append(l.Inspect.RootFS.Layers, sha) - l.layerPaths = append(l.layerPaths, "") - l.easyAddLayers = l.easyAddLayers[1:] - return nil - } - - if err := l.prevDownload(); err != nil { - return err - } - - reuseLayer, ok := l.prevMap[sha] - if !ok { - return fmt.Errorf("SHA %s was not found in %s", sha, l.RepoName) - } - - return l.AddLayer(filepath.Join(l.prevDir, reuseLayer)) -} - -func (l *local) Save() (string, error) { - ctx := context.Background() - done := make(chan error) - - t, err := name.NewTag(l.RepoName, name.WeakValidation) - if err != nil { - return "", err - } - repoName := t.String() - - pr, pw := io.Pipe() - go func() { - res, err := l.Docker.ImageLoad(ctx, pr, true) - if err == nil { - io.Copy(ioutil.Discard, res.Body) - res.Body.Close() - } - done <- err - }() - - tw := tar.NewWriter(pw) - - imgConfig := map[string]interface{}{ - "os": "linux", - "created": time.Now().Format(time.RFC3339), - "config": l.Inspect.Config, - "rootfs": map[string][]string{ - "diff_ids": l.Inspect.RootFS.Layers, - }, - } - formatted, err := json.Marshal(imgConfig) - if err != nil { - return "", err - } - imgID := fmt.Sprintf("%x", sha256.Sum256(formatted)) - if err := l.FS.AddTextToTar(tw, imgID+".json", formatted); err != nil { - return "", err - } - - var layerPaths []string - for _, path := range l.layerPaths { - if path == "" { - layerPaths = append(layerPaths, "") - continue - } - layerName := fmt.Sprintf("/%x.tar", sha256.Sum256([]byte(path))) - f, err := os.Open(path) - if err != nil { - return "", err - } - defer f.Close() - if err := l.FS.AddFileToTar(tw, layerName, f); err != nil { - return "", err - } - f.Close() - layerPaths = append(layerPaths, layerName) - - } - - formatted, err = json.Marshal([]map[string]interface{}{ - { - "Config": imgID + ".json", - "RepoTags": []string{repoName}, - "Layers": layerPaths, - }, - }) - if err != nil { - return "", err - } - if err := l.FS.AddTextToTar(tw, "manifest.json", formatted); err != nil { - return "", err - } - - tw.Close() - pw.Close() - err = <-done - - if l.prevDir != "" { - os.RemoveAll(l.prevDir) - l.prevDir = "" - l.prevMap = nil - l.prevOnce = &sync.Once{} - } - - return imgID, err -} - -func (l *local) prevDownload() error { - var outerErr error - l.prevOnce.Do(func() { - ctx := context.Background() - - tarFile, err := l.Docker.ImageSave(ctx, []string{l.RepoName}) - if err != nil { - outerErr = err - return - } - defer tarFile.Close() - - l.prevDir, err = ioutil.TempDir("", "packs.local.reuse-layer.") - if err != nil { - outerErr = errors.Wrap(err, "local reuse-layer create temp dir") - return - } - - err = l.FS.Untar(tarFile, l.prevDir) - if err != nil { - outerErr = err - return - } - - mf, err := os.Open(filepath.Join(l.prevDir, "manifest.json")) - if err != nil { - outerErr = err - return - } - defer mf.Close() - - var manifest []struct { - Config string - Layers []string - } - if err := json.NewDecoder(mf).Decode(&manifest); err != nil { - outerErr = err - return - } - - if len(manifest) != 1 { - outerErr = fmt.Errorf("manifest.json had unexpected number of entries: %d", len(manifest)) - return - } - - df, err := os.Open(filepath.Join(l.prevDir, manifest[0].Config)) - if err != nil { - outerErr = err - return - } - defer df.Close() - - var details struct { - RootFS struct { - DiffIDs []string `json:"diff_ids"` - } `json:"rootfs"` - } - - if err = json.NewDecoder(df).Decode(&details); err != nil { - outerErr = err - return - } - - if len(manifest[0].Layers) != len(details.RootFS.DiffIDs) { - outerErr = fmt.Errorf("layers and diff IDs do not match, there are %d layers and %d diffIDs", len(manifest[0].Layers), len(details.RootFS.DiffIDs)) - return - } - - l.prevMap = make(map[string]string, len(manifest[0].Layers)) - for i, diffID := range details.RootFS.DiffIDs { - layerID := manifest[0].Layers[i] - l.prevMap[diffID] = layerID - } - }) - return outerErr -} - -// TODO copied from exporter.go -func parseImageBuildBody(r io.Reader, out io.Writer) (string, error) { - jr := json.NewDecoder(r) - var id string - var streamError error - var obj struct { - Stream string `json:"stream"` - Error string `json:"error"` - Aux struct { - ID string `json:"ID"` - } `json:"aux"` - } - for { - err := jr.Decode(&obj) - if err != nil { - if err == io.EOF { - break - } - return "", err - } - if obj.Aux.ID != "" { - id = obj.Aux.ID - } - if txt := strings.TrimSpace(obj.Stream); txt != "" { - fmt.Fprintln(out, txt) - } - if txt := strings.TrimSpace(obj.Error); txt != "" { - streamError = errors.New(txt) - } - } - return id, streamError -} - -func randString(n int) string { - b := make([]byte, n) - for i := range b { - b[i] = 'a' + byte(rand.Intn(26)) - } - return string(b) -} diff --git a/image/local_test.go b/image/local_test.go deleted file mode 100644 index 2f5964b5ed..0000000000 --- a/image/local_test.go +++ /dev/null @@ -1,458 +0,0 @@ -package image_test - -import ( - "bytes" - "context" - "fmt" - "io" - "io/ioutil" - "log" - "math/rand" - "os" - "regexp" - "strings" - "sync" - "testing" - "time" - - "github.com/buildpack/pack/docker" - "github.com/buildpack/pack/fs" - "github.com/buildpack/pack/image" - h "github.com/buildpack/pack/testhelpers" - dockertypes "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" -) - -func TestLocal(t *testing.T) { - t.Parallel() - rand.Seed(time.Now().UTC().UnixNano()) - spec.Run(t, "local", testLocal, spec.Parallel(), spec.Report(report.Terminal{})) -} - -func testLocal(t *testing.T, when spec.G, it spec.S) { - var factory image.Factory - var buf bytes.Buffer - var repoName string - var dockerCli *docker.Client - - it.Before(func() { - var err error - dockerCli, err = docker.New() - h.AssertNil(t, err) - factory = image.Factory{ - Docker: dockerCli, - Log: log.New(&buf, "", log.LstdFlags), - Stdout: &buf, - FS: &fs.FS{}, - } - repoName = "pack-image-test-" + h.RandString(10) - }) - - when("#Label", func() { - when("image exists", func() { - it.Before(func() { - h.CreateImageOnLocal(t, dockerCli, repoName, fmt.Sprintf(` - FROM scratch - LABEL repo_name_for_randomisation=%s - LABEL mykey=myvalue other=data - `, repoName)) - }) - - it.After(func() { - h.AssertNil(t, h.DockerRmi(dockerCli, repoName)) - }) - - it("returns the label value", func() { - img, err := factory.NewLocal(repoName, false) - h.AssertNil(t, err) - - label, err := img.Label("mykey") - h.AssertNil(t, err) - h.AssertEq(t, label, "myvalue") - }) - - it("returns an empty string for a missing label", func() { - img, err := factory.NewLocal(repoName, false) - h.AssertNil(t, err) - - label, err := img.Label("missing-label") - h.AssertNil(t, err) - h.AssertEq(t, label, "") - }) - }) - - when("image NOT exists", func() { - it("returns an error", func() { - img, err := factory.NewLocal(repoName, false) - h.AssertNil(t, err) - - _, err = img.Label("mykey") - h.AssertError(t, err, fmt.Sprintf("failed to get label, image '%s' does not exist", repoName)) - }) - }) - }) - - when("#Name", func() { - it("always returns the original name", func() { - img, err := factory.NewLocal(repoName, false) - h.AssertNil(t, err) - - h.AssertEq(t, img.Name(), repoName) - }) - }) - - when("#Digest", func() { - when("image exists and has a digest", func() { - var expectedDigest string - it.Before(func() { - h.AssertNil(t, dockerCli.PullImage("busybox:1.29")) - expectedDigest = "sha256:2a03a6059f21e150ae84b0973863609494aad70f0a80eaeb64bddd8d92465812" - }) - - it("returns the image digest", func() { - img, err := factory.NewLocal("busybox:1.29", true) - h.AssertNil(t, err) - digest, err := img.Digest() - h.AssertNil(t, err) - h.AssertEq(t, digest, expectedDigest) - }) - }) - - when("image exists but has no digest", func() { - it.Before(func() { - h.CreateImageOnLocal(t, dockerCli, repoName, fmt.Sprintf(` - FROM scratch - LABEL repo_name_for_randomisation=%s - LABEL key=val - `, repoName)) - }) - - it.After(func() { - h.AssertNil(t, h.DockerRmi(dockerCli, repoName)) - }) - - it("returns an empty string", func() { - img, err := factory.NewLocal(repoName, false) - h.AssertNil(t, err) - digest, err := img.Digest() - h.AssertNil(t, err) - h.AssertEq(t, digest, "") - }) - }) - }) - - when("#SetLabel", func() { - when("image exists", func() { - var ( - img image.Image - origID string - ) - it.Before(func() { - var err error - h.CreateImageOnLocal(t, dockerCli, repoName, fmt.Sprintf(` - FROM scratch - LABEL repo_name_for_randomisation=%s - LABEL some-key=some-value - `, repoName)) - img, err = factory.NewLocal(repoName, false) - h.AssertNil(t, err) - origID = h.ImageID(t, repoName) - }) - - it.After(func() { - h.AssertNil(t, h.DockerRmi(dockerCli, repoName, origID)) - }) - - it("sets label and saves label to docker daemon", func() { - h.AssertNil(t, img.SetLabel("somekey", "new-val")) - t.Log("set label") - label, err := img.Label("somekey") - h.AssertNil(t, err) - h.AssertEq(t, label, "new-val") - t.Log("save label") - _, err = img.Save() - h.AssertNil(t, err) - - inspect, _, err := dockerCli.ImageInspectWithRaw(context.TODO(), repoName) - h.AssertNil(t, err) - label = inspect.Config.Labels["somekey"] - h.AssertEq(t, strings.TrimSpace(label), "new-val") - }) - }) - }) - - when("#Rebase", func() { - when("image exists", func() { - var oldBase, oldTopLayer, newBase, origID string - var origNumLayers int - it.Before(func() { - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - newBase = "pack-newbase-test-" + h.RandString(10) - h.CreateImageOnLocal(t, dockerCli, newBase, fmt.Sprintf(` - FROM busybox - LABEL repo_name_for_randomisation=%s - RUN echo new-base > base.txt - RUN echo text-new-base > otherfile.txt - `, newBase)) - }() - - oldBase = "pack-oldbase-test-" + h.RandString(10) - h.CreateImageOnLocal(t, dockerCli, oldBase, fmt.Sprintf(` - FROM busybox - LABEL repo_name_for_randomisation=%s - RUN echo old-base > base.txt - RUN echo text-old-base > otherfile.txt - `, oldBase)) - inspect, _, err := dockerCli.ImageInspectWithRaw(context.TODO(), oldBase) - h.AssertNil(t, err) - oldTopLayer = inspect.RootFS.Layers[len(inspect.RootFS.Layers)-1] - - h.CreateImageOnLocal(t, dockerCli, repoName, fmt.Sprintf(` - FROM %s - LABEL repo_name_for_randomisation=%s - RUN echo text-from-image > myimage.txt - RUN echo text-from-image > myimage2.txt - `, oldBase, repoName)) - inspect, _, err = dockerCli.ImageInspectWithRaw(context.TODO(), repoName) - h.AssertNil(t, err) - origNumLayers = len(inspect.RootFS.Layers) - origID = inspect.ID - - wg.Wait() - }) - - it.After(func() { - h.AssertNil(t, h.DockerRmi(dockerCli, repoName, oldBase, newBase, origID)) - }) - - it("switches the base", func() { - // Before - txt, err := h.CopySingleFileFromImage(dockerCli, repoName, "base.txt") - h.AssertNil(t, err) - h.AssertEq(t, txt, "old-base\n") - - // Run rebase - img, err := factory.NewLocal(repoName, false) - h.AssertNil(t, err) - newBaseImg, err := factory.NewLocal(newBase, false) - h.AssertNil(t, err) - err = img.Rebase(oldTopLayer, newBaseImg) - h.AssertNil(t, err) - _, err = img.Save() - h.AssertNil(t, err) - - // After - expected := map[string]string{ - "base.txt": "new-base\n", - "otherfile.txt": "text-new-base\n", - "myimage.txt": "text-from-image\n", - "myimage2.txt": "text-from-image\n", - } - ctr, err := dockerCli.ContainerCreate(context.Background(), &container.Config{Image: repoName}, &container.HostConfig{}, nil, "") - defer dockerCli.ContainerRemove(context.Background(), ctr.ID, dockertypes.ContainerRemoveOptions{}) - for filename, expectedText := range expected { - actualText, err := h.CopySingleFileFromContainer(dockerCli, ctr.ID, filename) - h.AssertNil(t, err) - h.AssertEq(t, actualText, expectedText) - } - - // Final Image should have same number of layers as initial image - inspect, _, err := dockerCli.ImageInspectWithRaw(context.TODO(), repoName) - h.AssertNil(t, err) - numLayers := len(inspect.RootFS.Layers) - h.AssertEq(t, numLayers, origNumLayers) - }) - }) - }) - - when("#TopLayer", func() { - when("image exists", func() { - var expectedTopLayer string - it.Before(func() { - h.CreateImageOnLocal(t, dockerCli, repoName, fmt.Sprintf(` - FROM busybox - LABEL repo_name_for_randomisation=%s - RUN echo old-base > base.txt - RUN echo text-old-base > otherfile.txt - `, repoName)) - - inspect, _, err := dockerCli.ImageInspectWithRaw(context.TODO(), repoName) - h.AssertNil(t, err) - expectedTopLayer = inspect.RootFS.Layers[len(inspect.RootFS.Layers)-1] - }) - - it.After(func() { - h.AssertNil(t, h.DockerRmi(dockerCli, repoName)) - }) - - it("returns the digest for the top layer (useful for rebasing)", func() { - img, err := factory.NewLocal(repoName, false) - h.AssertNil(t, err) - - actualTopLayer, err := img.TopLayer() - h.AssertNil(t, err) - - h.AssertEq(t, actualTopLayer, expectedTopLayer) - }) - }) - }) - - when("#AddLayer", func() { - var ( - tarPath string - img image.Image - origID string - ) - it.Before(func() { - h.CreateImageOnLocal(t, dockerCli, repoName, fmt.Sprintf(` - FROM busybox - LABEL repo_name_for_randomisation=%s - RUN echo -n old-layer > old-layer.txt - `, repoName)) - tr, err := (&fs.FS{}).CreateSingleFileTar("/new-layer.txt", "new-layer") - h.AssertNil(t, err) - tarFile, err := ioutil.TempFile("", "add-layer-test") - h.AssertNil(t, err) - defer tarFile.Close() - _, err = io.Copy(tarFile, tr) - h.AssertNil(t, err) - tarPath = tarFile.Name() - - img, err = factory.NewLocal(repoName, false) - h.AssertNil(t, err) - origID = h.ImageID(t, repoName) - }) - - it.After(func() { - err := os.Remove(tarPath) - h.AssertNil(t, err) - h.AssertNil(t, h.DockerRmi(dockerCli, repoName, origID)) - }) - - it("appends a layer", func() { - err := img.AddLayer(tarPath) - h.AssertNil(t, err) - - _, err = img.Save() - h.AssertNil(t, err) - - output, err := h.CopySingleFileFromImage(dockerCli, repoName, "old-layer.txt") - h.AssertNil(t, err) - h.AssertEq(t, output, "old-layer") - - output, err = h.CopySingleFileFromImage(dockerCli, repoName, "new-layer.txt") - h.AssertNil(t, err) - h.AssertEq(t, output, "new-layer") - }) - }) - - when("#ReuseLayer", func() { - var ( - layer1SHA string - layer2SHA string - img image.Image - origID string - ) - it.Before(func() { - var err error - - h.CreateImageOnLocal(t, dockerCli, repoName, fmt.Sprintf(` - FROM busybox - LABEL repo_name_for_randomisation=%s - RUN echo -n old-layer-1 > layer-1.txt - RUN echo -n old-layer-2 > layer-2.txt - `, repoName)) - - inspect, _, err := dockerCli.ImageInspectWithRaw(context.TODO(), repoName) - h.AssertNil(t, err) - origID = inspect.ID - - layer1SHA = inspect.RootFS.Layers[1] - layer2SHA = inspect.RootFS.Layers[2] - - img, err = factory.NewLocal("busybox", false) - h.AssertNil(t, err) - - img.Rename(repoName) - h.AssertNil(t, err) - }) - - it.After(func() { - h.AssertNil(t, h.DockerRmi(dockerCli, repoName, origID)) - }) - - it("reuses a layer", func() { - err := img.ReuseLayer(layer2SHA) - h.AssertNil(t, err) - - _, err = img.Save() - h.AssertNil(t, err) - - output, err := h.CopySingleFileFromImage(dockerCli, repoName, "layer-2.txt") - h.AssertNil(t, err) - h.AssertEq(t, output, "old-layer-2") - - // Confirm layer-1.txt does not exist - _, err = h.CopySingleFileFromImage(dockerCli, repoName, "layer-1.txt") - h.AssertMatch(t, err.Error(), regexp.MustCompile(`Error: No such container:path: .*:layer-1.txt`)) - }) - - it("does not download the old image if layers are directly above (performance)", func() { - err := img.ReuseLayer(layer1SHA) - h.AssertNil(t, err) - - _, err = img.Save() - h.AssertNil(t, err) - - output, err := h.CopySingleFileFromImage(dockerCli, repoName, "layer-1.txt") - h.AssertNil(t, err) - h.AssertEq(t, output, "old-layer-1") - - // Confirm layer-2.txt does not exist - _, err = h.CopySingleFileFromImage(dockerCli, repoName, "layer-2.txt") - h.AssertMatch(t, err.Error(), regexp.MustCompile(`Error: No such container:path: .*:layer-2.txt`)) - }) - }) - - when("#Save", func() { - var ( - img image.Image - origID string - ) - when("image exists", func() { - it.Before(func() { - var err error - h.CreateImageOnLocal(t, dockerCli, repoName, fmt.Sprintf(` - FROM busybox - LABEL repo_name_for_randomisation=%s - LABEL mykey=oldValue - `, repoName)) - img, err = factory.NewLocal(repoName, false) - h.AssertNil(t, err) - origID = h.ImageID(t, repoName) - }) - - it.After(func() { - h.AssertNil(t, h.DockerRmi(dockerCli, repoName, origID)) - }) - - it("returns the image digest", func() { - err := img.SetLabel("mykey", "newValue") - h.AssertNil(t, err) - - imgDigest, err := img.Save() - h.AssertNil(t, err) - - inspect, _, err := dockerCli.ImageInspectWithRaw(context.TODO(), imgDigest) - h.AssertNil(t, err) - label := inspect.Config.Labels["mykey"] - h.AssertEq(t, strings.TrimSpace(label), "newValue") - }) - }) - }) -} diff --git a/image/remote.go b/image/remote.go deleted file mode 100644 index 9c89ce7522..0000000000 --- a/image/remote.go +++ /dev/null @@ -1,154 +0,0 @@ -package image - -import ( - "fmt" - - "github.com/buildpack/lifecycle/img" - "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/mutate" - "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/pkg/errors" -) - -type remote struct { - RepoName string - Image v1.Image -} - -func (f *Factory) NewRemote(repoName string) (Image, error) { - repoStore, err := img.NewRegistry(repoName) - if err != nil { - return nil, err - } - image, err := repoStore.Image() - if err != nil { - return nil, errors.New("connect to repo store") - } - - return &remote{ - RepoName: repoName, - Image: image, - }, nil -} - -func (r *remote) Label(key string) (string, error) { - cfg, err := r.Image.ConfigFile() - if err != nil || cfg == nil { - return "", fmt.Errorf("failed to get label, image '%s' does not exist", r.RepoName) - } - labels := cfg.Config.Labels - return labels[key], nil - -} - -func (r *remote) Rename(name string) { - r.RepoName = name -} - -func (r *remote) Name() string { - return r.RepoName -} - -func (r *remote) Digest() (string, error) { - hash, err := r.Image.Digest() - if err != nil { - return "", fmt.Errorf("failed to get digest for image '%s': %s", r.RepoName, err) - } - return hash.String(), nil -} - -func (r *remote) Rebase(baseTopLayer string, newBase Image) error { - newBaseRemote, ok := newBase.(*remote) - if !ok { - return errors.New("expected new base to be a remote image") - } - - oldBase := &subImage{img: r.Image, topSHA: baseTopLayer} - newImage, err := mutate.Rebase(r.Image, oldBase, newBaseRemote.Image, &mutate.RebaseOptions{}) - if err != nil { - return errors.Wrap(err, "rebase") - } - r.Image = newImage - return nil -} - -func (r *remote) SetLabel(key, val string) error { - newImage, err := img.Label(r.Image, key, val) - if err != nil { - return errors.Wrap(err, "set metadata label") - } - r.Image = newImage - return nil -} - -func (r *remote) TopLayer() (string, error) { - all, err := r.Image.Layers() - if err != nil { - return "", err - } - topLayer := all[len(all)-1] - hex, err := topLayer.DiffID() - if err != nil { - return "", err - } - return hex.String(), nil -} - -func (r *remote) AddLayer(path string) error { - newImage, _, err := img.Append(r.Image, path) - if err != nil { - return errors.Wrap(err, "add layer") - } - r.Image = newImage - return nil -} - -func (r *remote) ReuseLayer(sha string) error { - panic("Not Implemented") -} - -func (r *remote) Save() (string, error) { - repoStore, err := img.NewRegistry(r.RepoName) - if err != nil { - return "", err - } - if err := repoStore.Write(r.Image); err != nil { - return "", err - } - - hex, err := r.Image.Digest() - - return hex.String(), nil -} - -type subImage struct { - img v1.Image - topSHA string -} - -func (si *subImage) Layers() ([]v1.Layer, error) { - all, err := si.img.Layers() - if err != nil { - return nil, err - } - for i, l := range all { - d, err := l.DiffID() - if err != nil { - return nil, err - } - if d.String() == si.topSHA { - return all[:i+1], nil - } - } - return nil, errors.New("could not find base layer in image") -} -func (si *subImage) BlobSet() (map[v1.Hash]struct{}, error) { panic("Not Implemented") } -func (si *subImage) MediaType() (types.MediaType, error) { panic("Not Implemented") } -func (si *subImage) ConfigName() (v1.Hash, error) { panic("Not Implemented") } -func (si *subImage) ConfigFile() (*v1.ConfigFile, error) { panic("Not Implemented") } -func (si *subImage) RawConfigFile() ([]byte, error) { panic("Not Implemented") } -func (si *subImage) Digest() (v1.Hash, error) { panic("Not Implemented") } -func (si *subImage) Manifest() (*v1.Manifest, error) { panic("Not Implemented") } -func (si *subImage) RawManifest() ([]byte, error) { panic("Not Implemented") } -func (si *subImage) LayerByDigest(v1.Hash) (v1.Layer, error) { panic("Not Implemented") } -func (si *subImage) LayerByDiffID(v1.Hash) (v1.Layer, error) { panic("Not Implemented") } diff --git a/image/remote_test.go b/image/remote_test.go deleted file mode 100644 index 117cd2d476..0000000000 --- a/image/remote_test.go +++ /dev/null @@ -1,360 +0,0 @@ -package image_test - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "log" - "math/rand" - "net/http" - "os" - "strings" - "sync" - "testing" - "time" - - "github.com/buildpack/pack/docker" - "github.com/buildpack/pack/fs" - "github.com/buildpack/pack/image" - h "github.com/buildpack/pack/testhelpers" - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" -) - -var registryPort string - -func TestRemote(t *testing.T) { - t.Parallel() - rand.Seed(time.Now().UTC().UnixNano()) - - registryPort = h.RunRegistry(t, false) - defer h.StopRegistry(t) - - spec.Run(t, "remote", testRemote, spec.Parallel(), spec.Report(report.Terminal{})) -} - -func testRemote(t *testing.T, when spec.G, it spec.S) { - var factory image.Factory - var buf bytes.Buffer - var repoName string - var dockerCli *docker.Client - - it.Before(func() { - var err error - dockerCli, err = docker.New() - h.AssertNil(t, err) - factory = image.Factory{ - Docker: dockerCli, - Log: log.New(&buf, "", log.LstdFlags), - Stdout: &buf, - FS: &fs.FS{}, - } - repoName = "localhost:" + registryPort + "/pack-image-test-" + h.RandString(10) - }) - - when("#Label", func() { - when("image exists", func() { - var img image.Image - it.Before(func() { - h.CreateImageOnRemote(t, dockerCli, repoName, fmt.Sprintf(` - FROM scratch - LABEL repo_name_for_randomisation=%s - LABEL mykey=myvalue other=data - `, repoName)) - - var err error - img, err = factory.NewRemote(repoName) - h.AssertNil(t, err) - }) - - it("returns the label value", func() { - label, err := img.Label("mykey") - h.AssertNil(t, err) - h.AssertEq(t, label, "myvalue") - }) - - it("returns an empty string for a missing label", func() { - label, err := img.Label("missing-label") - h.AssertNil(t, err) - h.AssertEq(t, label, "") - }) - }) - - when("image NOT exists", func() { - it("returns an error", func() { - img, err := factory.NewRemote(repoName) - h.AssertNil(t, err) - - _, err = img.Label("mykey") - h.AssertError(t, err, fmt.Sprintf("failed to get label, image '%s' does not exist", repoName)) - }) - }) - }) - - when("#Name", func() { - it("always returns the original name", func() { - img, err := factory.NewRemote(repoName) - h.AssertNil(t, err) - h.AssertEq(t, img.Name(), repoName) - }) - }) - - when("#Digest", func() { - it("returns the image digest", func() { - //busybox:1.29 has digest sha256:915f390a8912e16d4beb8689720a17348f3f6d1a7b659697df850ab625ea29d5 - img, err := factory.NewRemote("busybox:1.29") - h.AssertNil(t, err) - digest, err := img.Digest() - h.AssertNil(t, err) - h.AssertEq(t, digest, "sha256:915f390a8912e16d4beb8689720a17348f3f6d1a7b659697df850ab625ea29d5") - }) - }) - - when("#SetLabel", func() { - var img image.Image - when("image exists", func() { - it.Before(func() { - h.CreateImageOnRemote(t, dockerCli, repoName, fmt.Sprintf(` - FROM scratch - LABEL repo_name_for_randomisation=%s - LABEL mykey=myvalue other=data - `, repoName)) - - var err error - img, err = factory.NewRemote(repoName) - h.AssertNil(t, err) - }) - - it("sets label on img object", func() { - h.AssertNil(t, img.SetLabel("mykey", "new-val")) - label, err := img.Label("mykey") - h.AssertNil(t, err) - h.AssertEq(t, label, "new-val") - }) - - it("saves label", func() { - h.AssertNil(t, img.SetLabel("mykey", "new-val")) - _, err := img.Save() - h.AssertNil(t, err) - - // After Pull - label := remoteLabel(t, dockerCli, repoName, "mykey") - h.AssertEq(t, "new-val", label) - }) - }) - }) - - when("#Rebase", func() { - when("image exists", func() { - var oldBase, oldTopLayer, newBase string - var oldBaseLayers, newBaseLayers, repoTopLayers []string - it.Before(func() { - var wg sync.WaitGroup - wg.Add(1) - - newBase = "localhost:" + registryPort + "/pack-newbase-test-" + h.RandString(10) - go func() { - defer wg.Done() - h.CreateImageOnRemote(t, dockerCli, newBase, fmt.Sprintf(` - FROM busybox - LABEL repo_name_for_randomisation=%s - RUN echo new-base > base.txt - RUN echo text-new-base > otherfile.txt - `, repoName)) - newBaseLayers = manifestLayers(t, newBase) - }() - - oldBase = "localhost:" + registryPort + "/pack-oldbase-test-" + h.RandString(10) - oldTopLayer = h.CreateImageOnRemote(t, dockerCli, oldBase, fmt.Sprintf(` - FROM busybox - LABEL repo_name_for_randomisation=%s - RUN echo old-base > base.txt - RUN echo text-old-base > otherfile.txt - `, oldBase)) - oldBaseLayers = manifestLayers(t, oldBase) - - h.CreateImageOnRemote(t, dockerCli, repoName, fmt.Sprintf(` - FROM %s - LABEL repo_name_for_randomisation=%s - RUN echo text-from-image-1 > myimage.txt - RUN echo text-from-image-2 > myimage2.txt - `, oldBase, repoName)) - repoTopLayers = manifestLayers(t, repoName)[len(oldBaseLayers):] - - wg.Wait() - }) - - it.After(func() { - h.AssertNil(t, h.DockerRmi(dockerCli, oldBase)) - }) - - it("switches the base", func() { - // Before - h.AssertEq(t, - manifestLayers(t, repoName), - append(oldBaseLayers, repoTopLayers...), - ) - - // Run rebase - img, err := factory.NewRemote(repoName) - h.AssertNil(t, err) - newBaseImg, err := factory.NewRemote(newBase) - h.AssertNil(t, err) - err = img.Rebase(oldTopLayer, newBaseImg) - h.AssertNil(t, err) - _, err = img.Save() - h.AssertNil(t, err) - - // After - h.AssertEq(t, - manifestLayers(t, repoName), - append(newBaseLayers, repoTopLayers...), - ) - }) - }) - }) - - when("#TopLayer", func() { - when("image exists", func() { - it("returns the digest for the top layer (useful for rebasing)", func() { - expectedTopLayer := h.CreateImageOnRemote(t, dockerCli, repoName, fmt.Sprintf(` - FROM busybox - LABEL repo_name_for_randomisation=%s - RUN echo old-base > base.txt - RUN echo text-old-base > otherfile.txt - `, repoName)) - - img, err := factory.NewRemote(repoName) - h.AssertNil(t, err) - - actualTopLayer, err := img.TopLayer() - h.AssertNil(t, err) - - h.AssertEq(t, actualTopLayer, expectedTopLayer) - }) - }) - }) - - when("#AddLayer", func() { - var ( - tarPath string - img image.Image - ) - it.Before(func() { - h.CreateImageOnRemote(t, dockerCli, repoName, fmt.Sprintf(` - FROM busybox - LABEL repo_name_for_randomisation=%s - RUN echo -n old-layer > old-layer.txt - `, repoName)) - tr, err := (&fs.FS{}).CreateSingleFileTar("/new-layer.txt", "new-layer") - h.AssertNil(t, err) - tarFile, err := ioutil.TempFile("", "add-layer-test") - h.AssertNil(t, err) - defer tarFile.Close() - _, err = io.Copy(tarFile, tr) - h.AssertNil(t, err) - tarPath = tarFile.Name() - - img, err = factory.NewRemote(repoName) - h.AssertNil(t, err) - }) - - it.After(func() { - h.AssertNil(t, os.Remove(tarPath)) - h.AssertNil(t, h.DockerRmi(dockerCli, repoName)) - }) - - it("appends a layer", func() { - err := img.AddLayer(tarPath) - h.AssertNil(t, err) - - _, err = img.Save() - h.AssertNil(t, err) - - // After Pull - h.AssertNil(t, dockerCli.PullImage(repoName)) - - output, err := h.CopySingleFileFromImage(dockerCli, repoName, "old-layer.txt") - h.AssertNil(t, err) - h.AssertEq(t, output, "old-layer") - - output, err = h.CopySingleFileFromImage(dockerCli, repoName, "new-layer.txt") - h.AssertNil(t, err) - h.AssertEq(t, output, "new-layer") - }) - }) - - when("#Save", func() { - when("image exists", func() { - it("returns the image digest", func() { - h.CreateImageOnRemote(t, dockerCli, repoName, fmt.Sprintf(` - FROM busybox - LABEL repo_name_for_randomisation=%s - LABEL mykey=oldValue - `, repoName)) - - img, err := factory.NewRemote(repoName) - h.AssertNil(t, err) - - err = img.SetLabel("mykey", "newValue") - h.AssertNil(t, err) - - imgDigest, err := img.Save() - h.AssertNil(t, err) - - // After Pull - label := remoteLabel(t, dockerCli, repoName+"@"+imgDigest, "mykey") - h.AssertEq(t, "newValue", label) - - }) - }) - }) -} - -func manifestLayers(t *testing.T, repoName string) []string { - t.Helper() - - arr := strings.SplitN(repoName, "/", 2) - if len(arr) != 2 { - t.Fatalf("expected repoName to have 1 slash (remote test registry): '%s'", repoName) - } - - url := "http://" + arr[0] + "/v2/" + arr[1] + "/manifests/latest" - req, err := http.NewRequest("GET", url, nil) - h.AssertNil(t, err) - req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json") - resp, err := http.DefaultClient.Do(req) - h.AssertNil(t, err) - defer resp.Body.Close() - if resp.StatusCode >= 300 { - t.Fatalf("HTTP Status was bad: %s => %d", url, resp.StatusCode) - } - - var manifest struct { - Layers []struct { - Digest string `json:"digest"` - } `json:"layers"` - } - json.NewDecoder(resp.Body).Decode(&manifest) - h.AssertNil(t, err) - - outSlice := make([]string, 0, len(manifest.Layers)) - for _, layer := range manifest.Layers { - outSlice = append(outSlice, layer.Digest) - } - - return outSlice -} - -func remoteLabel(t *testing.T, dockerCli *docker.Client, repoName, label string) string { - t.Helper() - - h.AssertNil(t, dockerCli.PullImage(repoName)) - defer func() { h.AssertNil(t, h.DockerRmi(dockerCli, repoName)) }() - inspect, _, err := dockerCli.ImageInspectWithRaw(context.TODO(), repoName) - h.AssertNil(t, err) - return inspect.Config.Labels[label] -} diff --git a/interfaces.go b/interfaces.go index 138d827548..dbd7cd2fa3 100644 --- a/interfaces.go +++ b/interfaces.go @@ -8,8 +8,7 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "github.com/google/go-containerregistry/pkg/v1" - - "github.com/buildpack/pack/image" + "github.com/buildpack/lifecycle/image" ) //go:generate mockgen -package mocks -destination mocks/docker.go github.com/buildpack/pack Docker @@ -25,11 +24,6 @@ type Docker interface { ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) } -//go:generate mockgen -package mocks -destination mocks/images.go github.com/buildpack/pack Images -type Images interface { - ReadImage(repoName string, useDaemon bool) (v1.Image, error) -} - //go:generate mockgen -package mocks -destination mocks/task.go github.com/buildpack/pack Task type Task interface { Run() error @@ -43,10 +37,12 @@ type FS interface { CreateSingleFileTar(path, txt string) (io.Reader, error) } +//go:generate mockgen -package mocks -destination mocks/writablestore.go github.com/buildpack/pack WritableStore type WritableStore interface { Write(image v1.Image) error } +//go:generate mockgen -package mocks -destination mocks/image_factory.go github.com/buildpack/pack ImageFactory type ImageFactory interface { NewLocal(string, bool) (image.Image, error) NewRemote(string) (image.Image, error) diff --git a/mocks/image.go b/mocks/image.go index b34aa6147a..0aededf387 100644 --- a/mocks/image.go +++ b/mocks/image.go @@ -1,11 +1,11 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/buildpack/pack/image (interfaces: Image) +// Source: github.com/buildpack/lifecycle/image (interfaces: Image) // Package mocks is a generated GoMock package. package mocks import ( - image "github.com/buildpack/pack/image" + image "github.com/buildpack/lifecycle/image" gomock "github.com/golang/mock/gomock" reflect "reflect" ) @@ -58,6 +58,19 @@ func (mr *MockImageMockRecorder) Digest() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Digest", reflect.TypeOf((*MockImage)(nil).Digest)) } +// Found mocks base method +func (m *MockImage) Found() (bool, error) { + ret := m.ctrl.Call(m, "Found") + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Found indicates an expected call of Found +func (mr *MockImageMockRecorder) Found() *gomock.Call { + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Found", reflect.TypeOf((*MockImage)(nil).Found)) +} + // Label mocks base method func (m *MockImage) Label(arg0 string) (string, error) { ret := m.ctrl.Call(m, "Label", arg0) @@ -130,6 +143,18 @@ func (mr *MockImageMockRecorder) Save() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Save", reflect.TypeOf((*MockImage)(nil).Save)) } +// SetEnv mocks base method +func (m *MockImage) SetEnv(arg0, arg1 string) error { + ret := m.ctrl.Call(m, "SetEnv", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetEnv indicates an expected call of SetEnv +func (mr *MockImageMockRecorder) SetEnv(arg0, arg1 interface{}) *gomock.Call { + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetEnv", reflect.TypeOf((*MockImage)(nil).SetEnv), arg0, arg1) +} + // SetLabel mocks base method func (m *MockImage) SetLabel(arg0, arg1 string) error { ret := m.ctrl.Call(m, "SetLabel", arg0, arg1) diff --git a/mocks/image_factory.go b/mocks/image_factory.go index ad029d3a91..22754dc368 100644 --- a/mocks/image_factory.go +++ b/mocks/image_factory.go @@ -5,7 +5,7 @@ package mocks import ( - image "github.com/buildpack/pack/image" + image "github.com/buildpack/lifecycle/image" gomock "github.com/golang/mock/gomock" reflect "reflect" ) diff --git a/mocks/images.go b/mocks/images.go deleted file mode 100644 index 826d93bff0..0000000000 --- a/mocks/images.go +++ /dev/null @@ -1,47 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/buildpack/pack (interfaces: Images) - -// Package mocks is a generated GoMock package. -package mocks - -import ( - gomock "github.com/golang/mock/gomock" - v1 "github.com/google/go-containerregistry/pkg/v1" - reflect "reflect" -) - -// MockImages is a mock of Images interface -type MockImages struct { - ctrl *gomock.Controller - recorder *MockImagesMockRecorder -} - -// MockImagesMockRecorder is the mock recorder for MockImages -type MockImagesMockRecorder struct { - mock *MockImages -} - -// NewMockImages creates a new mock instance -func NewMockImages(ctrl *gomock.Controller) *MockImages { - mock := &MockImages{ctrl: ctrl} - mock.recorder = &MockImagesMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use -func (m *MockImages) EXPECT() *MockImagesMockRecorder { - return m.recorder -} - -// ReadImage mocks base method -func (m *MockImages) ReadImage(arg0 string, arg1 bool) (v1.Image, error) { - ret := m.ctrl.Call(m, "ReadImage", arg0, arg1) - ret0, _ := ret[0].(v1.Image) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ReadImage indicates an expected call of ReadImage -func (mr *MockImagesMockRecorder) ReadImage(arg0, arg1 interface{}) *gomock.Call { - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadImage", reflect.TypeOf((*MockImages)(nil).ReadImage), arg0, arg1) -} diff --git a/rebase.go b/rebase.go index f28cc14d76..c1f9b23305 100644 --- a/rebase.go +++ b/rebase.go @@ -6,8 +6,9 @@ import ( "log" "github.com/buildpack/lifecycle" + "github.com/buildpack/lifecycle/image" + "github.com/buildpack/pack/config" - "github.com/buildpack/pack/image" ) type RebaseConfig struct { diff --git a/rebase_test.go b/rebase_test.go index d6bcd84296..ad0bc7ab87 100644 --- a/rebase_test.go +++ b/rebase_test.go @@ -20,9 +20,8 @@ func TestRebase(t *testing.T) { spec.Run(t, "rebase", testRebase, spec.Parallel(), spec.Report(report.Terminal{})) } -//go:generate mockgen -package mocks -destination mocks/writablestore.go github.com/buildpack/pack WritableStore -//go:generate mockgen -package mocks -destination mocks/image.go github.com/buildpack/pack/image Image -//go:generate mockgen -package mocks -destination mocks/image_factory.go github.com/buildpack/pack ImageFactory +//move somewhere else +//go:generate mockgen -package mocks -destination mocks/image.go github.com/buildpack/lifecycle/image Image func testRebase(t *testing.T, when spec.G, it spec.S) { when("#RebaseFactory", func() { diff --git a/run_test.go b/run_test.go index 32286844c7..94354e6d55 100644 --- a/run_test.go +++ b/run_test.go @@ -14,9 +14,7 @@ import ( "time" "github.com/docker/docker/api/types" - dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" - dockercontainer "github.com/docker/docker/api/types/container" "github.com/docker/go-connections/nat" "github.com/golang/mock/gomock" "github.com/sclevine/spec" @@ -40,14 +38,12 @@ func testRun(t *testing.T, when spec.G, it spec.S) { mockController *gomock.Controller mockBuild *mocks.MockTask mockDocker *mocks.MockDocker - mockImages *mocks.MockImages ) it.Before(func() { mockController = gomock.NewController(t) mockBuild = mocks.NewMockTask(mockController) mockDocker = mocks.NewMockDocker(mockController) - mockImages = mocks.NewMockImages(mockController) }) it.After(func() { @@ -55,16 +51,22 @@ func testRun(t *testing.T, when spec.G, it spec.S) { }) when("#RunConfigFromFlags", func() { - var factory *pack.BuildFactory + var ( + mockController *gomock.Controller + factory *pack.BuildFactory + mockImageFactory *mocks.MockImageFactory + ) it.Before(func() { + mockController = gomock.NewController(t) + mockImageFactory = mocks.NewMockImageFactory(mockController) factory = &pack.BuildFactory{ Cli: mockDocker, Stdout: &buf, Stderr: &buf, Log: log.New(&buf, "", log.LstdFlags|log.Lshortfile), FS: &fs.FS{}, - Images: mockImages, + ImageFactory: mockImageFactory, Config: &config.Config{ Stacks: []config.Stack{ { @@ -77,19 +79,18 @@ func testRun(t *testing.T, when spec.G, it spec.S) { }) + it.After(func() { + mockController.Finish() + }) + it("creates a RunConfig derived from a BuildConfig", func() { - mockDocker.EXPECT().PullImage("some/builder") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "some/builder").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{"io.buildpacks.stack.id": "some.stack.id"}, - }, - }, nil, nil) - mockDocker.EXPECT().PullImage("some/run") - mockDocker.EXPECT().ImageInspectWithRaw(gomock.Any(), "some/run").Return(dockertypes.ImageInspect{ - Config: &dockercontainer.Config{ - Labels: map[string]string{"io.buildpacks.stack.id": "some.stack.id"}, - }, - }, nil, nil) + mockBuilderImage := mocks.NewMockImage(mockController) + mockBuilderImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("some/builder", true).Return(mockBuilderImage, nil) + + mockRunImage := mocks.NewMockImage(mockController) + mockRunImage.EXPECT().Label("io.buildpacks.stack.id").Return("some.stack.id", nil) + mockImageFactory.EXPECT().NewLocal("some/run", true).Return(mockRunImage, nil) run, err := factory.RunConfigFromFlags(&pack.RunFlags{ BuildFlags: pack.BuildFlags{