diff --git a/build.go b/build.go index e0b26ab9ee..f73851110c 100644 --- a/build.go +++ b/build.go @@ -88,9 +88,9 @@ type BuildOptions struct { // built atop. RunImage string - // Address of docker daemon exposed to build container - // e.g. tcp://example.com:1234, unix:///run/user/1000/podman/podman.sock - DockerHost string + // Address of docker daemon exposed to lifecycle in the build container + // e.g. host-socket, inherit, tcp://example.com:1234, unix:///run/user/1000/podman/podman.sock + LifecycleDockerHost string // Used to determine a run-image mirror if Run Image is empty. // Used in combination with Builder metadata to determine to the the 'best' mirror. @@ -284,25 +284,25 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { } lifecycleOpts := build.LifecycleOptions{ - AppPath: appPath, - Image: imageRef, - Builder: ephemeralBuilder, - RunImage: runImageName, - ClearCache: opts.ClearCache, - Publish: opts.Publish, - DockerHost: opts.DockerHost, - UseCreator: false, - TrustBuilder: opts.TrustBuilder, - LifecycleImage: ephemeralBuilder.Name(), - HTTPProxy: proxyConfig.HTTPProxy, - HTTPSProxy: proxyConfig.HTTPSProxy, - NoProxy: proxyConfig.NoProxy, - Network: opts.ContainerConfig.Network, - AdditionalTags: opts.AdditionalTags, - Volumes: processedVolumes, - DefaultProcessType: opts.DefaultProcessType, - FileFilter: fileFilter, - CacheImage: opts.CacheImage, + AppPath: appPath, + Image: imageRef, + Builder: ephemeralBuilder, + RunImage: runImageName, + ClearCache: opts.ClearCache, + Publish: opts.Publish, + UseCreator: false, + TrustBuilder: opts.TrustBuilder, + HTTPProxy: proxyConfig.HTTPProxy, + HTTPSProxy: proxyConfig.HTTPSProxy, + NoProxy: proxyConfig.NoProxy, + Network: opts.ContainerConfig.Network, + AdditionalTags: opts.AdditionalTags, + Volumes: processedVolumes, + DefaultProcessType: opts.DefaultProcessType, + FileFilter: fileFilter, + CacheImage: opts.CacheImage, + LifecycleImage: ephemeralBuilder.Name(), + LifecycleDockerHost: opts.LifecycleDockerHost, } lifecycleVersion := ephemeralBuilder.LifecycleDescriptor().Info.Version diff --git a/internal/build/lifecycle_execution.go b/internal/build/lifecycle_execution.go index bddfea5dcb..7f3a78f5a6 100644 --- a/internal/build/lifecycle_execution.go +++ b/internal/build/lifecycle_execution.go @@ -132,7 +132,7 @@ func (l *LifecycleExecution) Run(ctx context.Context, phaseFactoryCreator PhaseF } l.logger.Info(style.Step("ANALYZING")) - if err := l.Analyze(ctx, l.opts.Image.String(), l.opts.Network, l.opts.Publish, l.opts.DockerHost, l.opts.ClearCache, buildCache, phaseFactory); err != nil { + if err := l.Analyze(ctx, l.opts.Image.String(), l.opts.Network, l.opts.Publish, l.opts.LifecycleDockerHost, l.opts.ClearCache, buildCache, phaseFactory); err != nil { return err } @@ -150,10 +150,10 @@ func (l *LifecycleExecution) Run(ctx context.Context, phaseFactoryCreator PhaseF } l.logger.Info(style.Step("EXPORTING")) - return l.Export(ctx, l.opts.Image.String(), l.opts.RunImage, l.opts.Publish, l.opts.DockerHost, l.opts.Network, buildCache, launchCache, l.opts.AdditionalTags, phaseFactory) + return l.Export(ctx, l.opts.Image.String(), l.opts.RunImage, l.opts.Publish, l.opts.LifecycleDockerHost, l.opts.Network, buildCache, launchCache, l.opts.AdditionalTags, phaseFactory) } - return l.Create(ctx, l.opts.Publish, l.opts.DockerHost, l.opts.ClearCache, l.opts.RunImage, l.opts.Image.String(), l.opts.Network, buildCache, launchCache, l.opts.AdditionalTags, l.opts.Volumes, phaseFactory) + return l.Create(ctx, l.opts.Publish, l.opts.LifecycleDockerHost, l.opts.ClearCache, l.opts.RunImage, l.opts.Image.String(), l.opts.Network, buildCache, launchCache, l.opts.AdditionalTags, l.opts.Volumes, phaseFactory) } func (l *LifecycleExecution) Cleanup() error { diff --git a/internal/build/lifecycle_execution_test.go b/internal/build/lifecycle_execution_test.go index ed27fba970..9542a65cb3 100644 --- a/internal/build/lifecycle_execution_test.go +++ b/internal/build/lifecycle_execution_test.go @@ -537,7 +537,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { lifecycle := newTestLifecycleExec(t, false) fakePhaseFactory := fakes.NewFakePhaseFactory() - err := lifecycle.Create(context.Background(), false, "", false, "test", "test", "test", fakeBuildCache, fakeLaunchCache, []string{}, []string{}, fakePhaseFactory) + err := lifecycle.Create(context.Background(), false, "host-socket", false, "test", "test", "test", fakeBuildCache, fakeLaunchCache, []string{}, []string{}, fakePhaseFactory) h.AssertNil(t, err) lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1 @@ -548,7 +548,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { h.AssertSliceContains(t, configProvider.HostConfig().Binds, "/var/run/docker.sock:/var/run/docker.sock") }) - it("configures the phase with daemon access with tcp docker-host", func() { + it("configures the phase with daemon access with tcp lifecycle-docker-host", func() { lifecycle := newTestLifecycleExec(t, false) fakePhaseFactory := fakes.NewFakePhaseFactory() @@ -563,7 +563,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { h.AssertSliceContains(t, configProvider.ContainerConfig().Env, "DOCKER_HOST=tcp://localhost:1234") }) - it("configures the phase with daemon access with alternative unix socket docker-host", func() { + it("configures the phase with daemon access with alternative unix socket lifecycle-docker-host", func() { lifecycle := newTestLifecycleExec(t, false) fakePhaseFactory := fakes.NewFakePhaseFactory() @@ -577,7 +577,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { h.AssertSliceContains(t, configProvider.HostConfig().Binds, "/home/user/docker.sock:/var/run/docker.sock") }) - it("configures the phase with daemon access with alternative windows pipe docker-host", func() { + it("configures the phase with daemon access with alternative windows pipe lifecycle-docker-host", func() { lifecycle := newTestLifecycleExec(t, false) fakePhaseFactory := fakes.NewFakePhaseFactory() @@ -608,7 +608,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { os.Unsetenv("DOCKER_HOST") } }) - it("configures the phase with daemon access with inherited docker-host", func() { + it("configures the phase with daemon access with inherited lifecycle-docker-host", func() { lifecycle := newTestLifecycleExec(t, false) fakePhaseFactory := fakes.NewFakePhaseFactory() @@ -623,7 +623,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { }) }) - it("configures the phase with daemon access with docker-host with unknown protocol", func() { + it("configures the phase with daemon access with lifecycle-docker-host with unknown protocol", func() { lifecycle := newTestLifecycleExec(t, false) fakePhaseFactory := fakes.NewFakePhaseFactory() err := lifecycle.Create(context.Background(), false, `withoutprotocol`, false, "test", "test", "test", fakeBuildCache, fakeLaunchCache, []string{}, []string{}, fakePhaseFactory) @@ -1030,7 +1030,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { lifecycle := newTestLifecycleExec(t, false) fakePhaseFactory := fakes.NewFakePhaseFactory() - err := lifecycle.Analyze(context.Background(), "test", "test", false, "", false, fakeCache, fakePhaseFactory) + err := lifecycle.Analyze(context.Background(), "test", "test", false, "host-socket", false, fakeCache, fakePhaseFactory) h.AssertNil(t, err) lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1 @@ -1041,7 +1041,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { h.AssertSliceContains(t, configProvider.HostConfig().Binds, "/var/run/docker.sock:/var/run/docker.sock") }) - it("configures the phase with daemon access with TCP docker-host", func() { + it("configures the phase with daemon access with TCP lifecycle-docker-host", func() { lifecycle := newTestLifecycleExec(t, false) fakePhaseFactory := fakes.NewFakePhaseFactory() @@ -1656,7 +1656,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { lifecycle := newTestLifecycleExec(t, false) fakePhaseFactory := fakes.NewFakePhaseFactory() - err := lifecycle.Export(context.Background(), "test", "test", false, "", "test", fakeBuildCache, fakeLaunchCache, []string{}, fakePhaseFactory) + err := lifecycle.Export(context.Background(), "test", "test", false, "host-socket", "test", fakeBuildCache, fakeLaunchCache, []string{}, fakePhaseFactory) h.AssertNil(t, err) lastCallIndex := len(fakePhaseFactory.NewCalledWithProvider) - 1 @@ -1667,7 +1667,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) { h.AssertSliceContains(t, configProvider.HostConfig().Binds, "/var/run/docker.sock:/var/run/docker.sock") }) - it("configures the phase with daemon access with tcp docker-host", func() { + it("configures the phase with daemon access with tcp lifecycle-docker-host", func() { lifecycle := newTestLifecycleExec(t, false) fakePhaseFactory := fakes.NewFakePhaseFactory() diff --git a/internal/build/lifecycle_executor.go b/internal/build/lifecycle_executor.go index bbd5cb7653..41fb16a420 100644 --- a/internal/build/lifecycle_executor.go +++ b/internal/build/lifecycle_executor.go @@ -49,25 +49,25 @@ func init() { } type LifecycleOptions struct { - AppPath string - Image name.Reference - Builder Builder - LifecycleImage string - RunImage string - ClearCache bool - Publish bool - TrustBuilder bool - UseCreator bool - DockerHost string - CacheImage string - HTTPProxy string - HTTPSProxy string - NoProxy string - Network string - AdditionalTags []string - Volumes []string - DefaultProcessType string - FileFilter func(string) bool + AppPath string + Image name.Reference + Builder Builder + RunImage string + ClearCache bool + Publish bool + TrustBuilder bool + UseCreator bool + CacheImage string + LifecycleImage string + LifecycleDockerHost string + HTTPProxy string + HTTPSProxy string + NoProxy string + Network string + AdditionalTags []string + Volumes []string + DefaultProcessType string + FileFilter func(string) bool } func NewLifecycleExecutor(logger logging.Logger, docker client.CommonAPIClient) *LifecycleExecutor { diff --git a/internal/build/phase_config_provider.go b/internal/build/phase_config_provider.go index d428f12bf0..426e4b9aff 100644 --- a/internal/build/phase_config_provider.go +++ b/internal/build/phase_config_provider.go @@ -130,7 +130,7 @@ func WithDaemonAccess(dockerHost string) PhaseConfigProviderOperation { dockerHost = os.Getenv("DOCKER_HOST") } var bind string - if dockerHost == "" { + if dockerHost == "host-socket" { bind = "/var/run/docker.sock:/var/run/docker.sock" if provider.os == "windows" { bind = `\\.\pipe\docker_engine:\\.\pipe\docker_engine` diff --git a/internal/build/phase_config_provider_test.go b/internal/build/phase_config_provider_test.go index 2dbfec0a24..100192eb22 100644 --- a/internal/build/phase_config_provider_test.go +++ b/internal/build/phase_config_provider_test.go @@ -133,7 +133,7 @@ func testPhaseConfigProvider(t *testing.T, when spec.G, it spec.S) { phaseConfigProvider := build.NewPhaseConfigProvider( "some-name", lifecycle, - build.WithDaemonAccess(""), + build.WithDaemonAccess("host-socket"), ) h.AssertEq(t, phaseConfigProvider.ContainerConfig().User, "root") @@ -152,7 +152,7 @@ func testPhaseConfigProvider(t *testing.T, when spec.G, it spec.S) { phaseConfigProvider := build.NewPhaseConfigProvider( "some-name", lifecycle, - build.WithDaemonAccess(""), + build.WithDaemonAccess("host-socket"), ) h.AssertEq(t, phaseConfigProvider.ContainerConfig().User, "ContainerAdministrator") diff --git a/internal/build/phase_test.go b/internal/build/phase_test.go index 44d53a5457..58ff03608d 100644 --- a/internal/build/phase_test.go +++ b/internal/build/phase_test.go @@ -265,7 +265,7 @@ func testPhase(t *testing.T, when spec.G, it spec.S) { when("#WithDaemonAccess", func() { when("with standard docker socket", func() { it("allows daemon access inside the container", func() { - configProvider := build.NewPhaseConfigProvider(phaseName, lifecycleExec, build.WithArgs("daemon"), build.WithDaemonAccess("")) + configProvider := build.NewPhaseConfigProvider(phaseName, lifecycleExec, build.WithArgs("daemon"), build.WithDaemonAccess("host-socket")) phase := phaseFactory.New(configProvider) assertRunSucceeds(t, phase, &outBuf, &errBuf) h.AssertContains(t, outBuf.String(), "daemon test") @@ -296,7 +296,7 @@ func testPhase(t *testing.T, when spec.G, it spec.S) { }) }) - when("with TCP docker-host", func() { + when("with TCP lifecycle-docker-host", func() { it.Before(func() { h.SkipIf(t, runtime.GOOS != "linux", "Skipped on non-linux") }) @@ -379,6 +379,7 @@ func testPhase(t *testing.T, when spec.G, it spec.S) { build.WithArgs("registry", repoName), build.WithRegistryAccess(authConfig), build.WithNetwork("host"), + build.WithDaemonAccess("host-socket"), ) phase := phaseFactory.New(configProvider) assertRunSucceeds(t, phase, &outBuf, &errBuf) diff --git a/internal/commands/build.go b/internal/commands/build.go index d46266de68..e244733213 100644 --- a/internal/commands/build.go +++ b/internal/commands/build.go @@ -21,25 +21,25 @@ import ( ) type BuildFlags struct { - Publish bool - ClearCache bool - TrustBuilder bool - DockerHost string - CacheImage string - AppPath string - Builder string - Registry string - RunImage string - Policy string - Network string - DescriptorPath string - DefaultProcessType string - LifecycleImage string - Env []string - EnvFiles []string - Buildpacks []string - Volumes []string - AdditionalTags []string + Publish bool + ClearCache bool + TrustBuilder bool + CacheImage string + AppPath string + Builder string + Registry string + RunImage string + Policy string + Network string + DescriptorPath string + DefaultProcessType string + LifecycleImage string + LifecycleDockerHost string + Env []string + EnvFiles []string + Buildpacks []string + Volumes []string + AdditionalTags []string } // Build an image from source code @@ -118,7 +118,6 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob Env: env, Image: imageName, Publish: flags.Publish, - DockerHost: flags.DockerHost, PullPolicy: pullPolicy, ClearCache: flags.ClearCache, TrustBuilder: trustBuilder, @@ -132,6 +131,7 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob ProjectDescriptor: descriptor, CacheImage: flags.CacheImage, LifecycleImage: lifecycleImage, + LifecycleDockerHost: flags.LifecycleDockerHost, }); err != nil { return errors.Wrap(err, "failed to build") } @@ -156,13 +156,13 @@ func buildCommandFlags(cmd *cobra.Command, buildFlags *BuildFlags, cfg config.Co cmd.Flags().StringArrayVar(&buildFlags.EnvFiles, "env-file", []string{}, "Build-time environment variables file\nOne variable per line, of the form 'VAR=VALUE' or 'VAR'\nWhen using latter value-less form, value will be taken from current\n environment at the time this command is executed\nNOTE: These are NOT available at image runtime.\"") cmd.Flags().StringVar(&buildFlags.Network, "network", "", "Connect detect and build containers to network") cmd.Flags().BoolVar(&buildFlags.Publish, "publish", false, "Publish to registry") - cmd.Flags().StringVar(&buildFlags.DockerHost, "docker-host", "", - `Address to docker daemon that will be exposed to the build container. -If not set (or set to empty string) the standard socket location will be used. -Special value 'inherit' may be used in which case DOCKER_HOST environment variable will be used. -This option may set DOCKER_HOST environment variable for the build container if needed. -`) cmd.Flags().StringVar(&buildFlags.LifecycleImage, "lifecycle-image", cfg.LifecycleImage, `Custom lifecycle image to use for analysis, restore, and export when builder is untrusted.`) + cmd.Flags().StringVar(&buildFlags.LifecycleDockerHost, "lifecycle-docker-host", "host-socket", + `Address to docker daemon to expose for lifecycle in the build container. +Default value 'host-socket' will bind-mount the daemon socket from the host daemon. +Special value 'inherit' will use the DOCKER_HOST environment variable from pack. +Otherwise, values will be passed through to the DOCKER_HOST environment variable for the build container. +`) cmd.Flags().StringVar(&buildFlags.Policy, "pull-policy", "", `Pull policy to use. Accepted values are always, never, and if-not-present. (default "always")`) cmd.Flags().StringVarP(&buildFlags.Registry, "buildpack-registry", "r", cfg.DefaultRegistryName, "Buildpack Registry by name") cmd.Flags().StringVar(&buildFlags.RunImage, "run-image", "", "Run image (defaults to default stack's run image)") @@ -185,6 +185,10 @@ func validateBuildFlags(flags *BuildFlags, cfg config.Config, packClient PackCli return errors.New("cache-image flag requires the publish flag") } + if flags.LifecycleDockerHost != "host-socket" && flags.Publish { + return errors.New("lifecycle-docker-host is not allowed when publishing to registry") + } + return nil } diff --git a/internal/commands/build_test.go b/internal/commands/build_test.go index 9fa932b636..3600293a3e 100644 --- a/internal/commands/build_test.go +++ b/internal/commands/build_test.go @@ -347,6 +347,36 @@ func testBuildCommand(t *testing.T, when spec.G, it spec.S) { }) }) + when("a lifecycle-docker-host is given", func() { + when("--publish is used", func() { + it("errors", func() { + command.SetArgs([]string{"--lifecycle-docker-host", "unix://foo", "--publish", "--builder", "my-builder", "image"}) + err := command.Execute() + h.AssertError(t, err, "lifecycle-docker-host is not allowed when publishing to registry") + }) + }) + when("--publish is not used", func() { + it("sets the host", func() { + mockClient.EXPECT(). + Build(gomock.Any(), EqBuildOptionsWithLifecycleDockerHost("unix://foo")). + Return(nil) + + command.SetArgs([]string{"--lifecycle-docker-host", "unix://foo", "--builder", "my-builder", "image"}) + h.AssertNil(t, command.Execute()) + }) + }) + when("unset", func() { + it("sets the default host option", func() { + mockClient.EXPECT(). + Build(gomock.Any(), EqBuildOptionsWithLifecycleDockerHost("host-socket")). + Return(nil) + + command.SetArgs([]string{"--builder", "my-builder", "image"}) + h.AssertNil(t, command.Execute()) + }) + }) + }) + when("a valid lifecycle-image is provided", func() { when("only the image repo is provided", func() { it("uses the provided lifecycle-image and parses it correctly", func() { @@ -749,6 +779,15 @@ func EqBuildOptionsWithEnv(env map[string]string) gomock.Matcher { } } +func EqBuildOptionsWithLifecycleDockerHost(lifecycleDockerHost string) gomock.Matcher { + return buildOptionsMatcher{ + description: fmt.Sprintf("LifecycleDockerHost=%s", lifecycleDockerHost), + equals: func(o pack.BuildOptions) bool { + return reflect.DeepEqual(o.LifecycleDockerHost, lifecycleDockerHost) + }, + } +} + type buildOptionsMatcher struct { equals func(pack.BuildOptions) bool description string