浏览代码

Add support of ssh authentications defined in compose file or via cli flags

Signed-off-by: Guillaume Lours <[email protected]>
Guillaume Lours 3 年之前
父节点
当前提交
ff73827a6f
共有 9 个文件被更改,包括 93 次插入18 次删除
  1. 33 9
      cmd/compose/build.go
  2. 1 0
      docs/reference/compose_build.md
  3. 10 0
      docs/reference/docker_compose_build.yaml
  4. 1 1
      go.mod
  5. 2 2
      go.sum
  6. 2 0
      pkg/api/api.go
  7. 20 0
      pkg/compose/build.go
  8. 15 0
      pkg/e2e/compose_build_test.go
  9. 9 6
      pkg/e2e/framework.go

+ 33 - 9
cmd/compose/build.go

@@ -23,6 +23,7 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/compose-spec/compose-go/cli"
 	"github.com/compose-spec/compose-go/cli"
+	"github.com/compose-spec/compose-go/loader"
 	"github.com/compose-spec/compose-go/types"
 	"github.com/compose-spec/compose-go/types"
 	buildx "github.com/docker/buildx/util/progress"
 	buildx "github.com/docker/buildx/util/progress"
 	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/compose/v2/pkg/utils"
@@ -40,6 +41,28 @@ type buildOptions struct {
 	args     []string
 	args     []string
 	noCache  bool
 	noCache  bool
 	memory   string
 	memory   string
+	ssh      string
+}
+
+func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
+	var SSHKeys []types.SSHKey
+	var err error
+	if opts.ssh != "" {
+		SSHKeys, err = loader.ParseShortSSHSyntax(opts.ssh)
+		if err != nil {
+			return api.BuildOptions{}, err
+		}
+	}
+
+	return api.BuildOptions{
+		Pull:     opts.pull,
+		Progress: opts.progress,
+		Args:     types.NewMappingWithEquals(opts.args),
+		NoCache:  opts.noCache,
+		Quiet:    opts.quiet,
+		Services: services,
+		SSHs:     SSHKeys,
+	}, nil
 }
 }
 
 
 var printerModes = []string{
 var printerModes = []string{
@@ -73,7 +96,10 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
 			}
 			}
 			return nil
 			return nil
 		}),
 		}),
-		RunE: Adapt(func(ctx context.Context, args []string) error {
+		RunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
+			if cmd.Flags().Changed("ssh") && opts.ssh == "" {
+				opts.ssh = "default"
+			}
 			return runBuild(ctx, backend, opts, args)
 			return runBuild(ctx, backend, opts, args)
 		}),
 		}),
 		ValidArgsFunction: serviceCompletion(p),
 		ValidArgsFunction: serviceCompletion(p),
@@ -82,6 +108,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
 	cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
 	cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
 	cmd.Flags().StringVar(&opts.progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
 	cmd.Flags().StringVar(&opts.progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
 	cmd.Flags().StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
 	cmd.Flags().StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
+	cmd.Flags().StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using you default SSH Agent)")
 	cmd.Flags().Bool("parallel", true, "Build images in parallel. DEPRECATED")
 	cmd.Flags().Bool("parallel", true, "Build images in parallel. DEPRECATED")
 	cmd.Flags().MarkHidden("parallel") //nolint:errcheck
 	cmd.Flags().MarkHidden("parallel") //nolint:errcheck
 	cmd.Flags().Bool("compress", true, "Compress the build context using gzip. DEPRECATED")
 	cmd.Flags().Bool("compress", true, "Compress the build context using gzip. DEPRECATED")
@@ -103,12 +130,9 @@ func runBuild(ctx context.Context, backend api.Service, opts buildOptions, servi
 		return err
 		return err
 	}
 	}
 
 
-	return backend.Build(ctx, project, api.BuildOptions{
-		Pull:     opts.pull,
-		Progress: opts.progress,
-		Args:     types.NewMappingWithEquals(opts.args),
-		NoCache:  opts.noCache,
-		Quiet:    opts.quiet,
-		Services: services,
-	})
+	apiBuildOptions, err := opts.toAPIBuildOptions(services)
+	if err != nil {
+		return err
+	}
+	return backend.Build(ctx, project, apiBuildOptions)
 }
 }

+ 1 - 0
docs/reference/compose_build.md

@@ -12,6 +12,7 @@ Build or rebuild services
 | `--progress` | `string` | `auto` | Set type of progress output (auto, tty, plain, quiet) |
 | `--progress` | `string` | `auto` | Set type of progress output (auto, tty, plain, quiet) |
 | `--pull` |  |  | Always attempt to pull a newer version of the image. |
 | `--pull` |  |  | Always attempt to pull a newer version of the image. |
 | `-q`, `--quiet` |  |  | Don't print anything to STDOUT |
 | `-q`, `--quiet` |  |  | Don't print anything to STDOUT |
+| `--ssh` | `string` |  | Set SSH authentications used when building service images. (use 'default' for using you default SSH Agent) |
 
 
 
 
 <!---MARKER_GEN_END-->
 <!---MARKER_GEN_END-->

+ 10 - 0
docs/reference/docker_compose_build.yaml

@@ -117,6 +117,16 @@ options:
   experimentalcli: false
   experimentalcli: false
   kubernetes: false
   kubernetes: false
   swarm: false
   swarm: false
+- option: ssh
+  value_type: string
+  description: |
+    Set SSH authentications used when building service images. (use 'default' for using you default SSH Agent)
+  deprecated: false
+  hidden: false
+  experimental: false
+  experimentalcli: false
+  kubernetes: false
+  swarm: false
 deprecated: false
 deprecated: false
 experimental: false
 experimental: false
 experimentalcli: false
 experimentalcli: false

+ 1 - 1
go.mod

@@ -6,7 +6,7 @@ require (
 	github.com/AlecAivazis/survey/v2 v2.3.2
 	github.com/AlecAivazis/survey/v2 v2.3.2
 	github.com/buger/goterm v1.0.4
 	github.com/buger/goterm v1.0.4
 	github.com/cnabio/cnab-to-oci v0.3.1-beta1
 	github.com/cnabio/cnab-to-oci v0.3.1-beta1
-	github.com/compose-spec/compose-go v1.2.1
+	github.com/compose-spec/compose-go v1.2.2
 	github.com/containerd/console v1.0.3
 	github.com/containerd/console v1.0.3
 	github.com/containerd/containerd v1.6.1
 	github.com/containerd/containerd v1.6.1
 	github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e
 	github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e

+ 2 - 2
go.sum

@@ -302,8 +302,8 @@ github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoC
 github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
 github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
 github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/compose-spec/compose-go v1.0.8/go.mod h1:REnCbBugoIdHB7S1sfkN/aJ7AJpNApGNjNiVjA9L8x4=
 github.com/compose-spec/compose-go v1.0.8/go.mod h1:REnCbBugoIdHB7S1sfkN/aJ7AJpNApGNjNiVjA9L8x4=
-github.com/compose-spec/compose-go v1.2.1 h1:8+DAP7Mt/Ohl5y6YbZdilLMvIhMxvuSZcNZyywjQmJE=
-github.com/compose-spec/compose-go v1.2.1/go.mod h1:pAy7Mikpeft4pxkFU565/DRHEbDfR84G6AQuiL+Hdg8=
+github.com/compose-spec/compose-go v1.2.2 h1:y1dwl3KUTBnWPVur6EZno9zUIum6Q87/F5keljnGQB4=
+github.com/compose-spec/compose-go v1.2.2/go.mod h1:pAy7Mikpeft4pxkFU565/DRHEbDfR84G6AQuiL+Hdg8=
 github.com/compose-spec/godotenv v1.1.1/go.mod h1:zF/3BOa18Z24tts5qnO/E9YURQanJTBUf7nlcCTNsyc=
 github.com/compose-spec/godotenv v1.1.1/go.mod h1:zF/3BOa18Z24tts5qnO/E9YURQanJTBUf7nlcCTNsyc=
 github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
 github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
 github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
 github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=

+ 2 - 0
pkg/api/api.go

@@ -91,6 +91,8 @@ type BuildOptions struct {
 	Quiet bool
 	Quiet bool
 	// Services passed in the command line to be built
 	// Services passed in the command line to be built
 	Services []string
 	Services []string
+	// Ssh authentications passed in the command line
+	SSHs []types.SSHKey
 }
 }
 
 
 // CreateOptions group options of the Create API
 // CreateOptions group options of the Create API

+ 20 - 0
pkg/compose/build.go

@@ -31,6 +31,7 @@ import (
 	bclient "github.com/moby/buildkit/client"
 	bclient "github.com/moby/buildkit/client"
 	"github.com/moby/buildkit/session"
 	"github.com/moby/buildkit/session"
 	"github.com/moby/buildkit/session/auth/authprovider"
 	"github.com/moby/buildkit/session/auth/authprovider"
+	"github.com/moby/buildkit/session/sshforward/sshprovider"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 
 
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/docker/compose/v2/pkg/api"
@@ -81,6 +82,14 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
 				})
 				})
 			}
 			}
 
 
+			if len(options.SSHs) > 0 || len(service.Build.SSH) > 0 {
+				sshAgentProvider, err := sshAgentProvider(append(service.Build.SSH, options.SSHs...))
+				if err != nil {
+					return err
+				}
+				buildOptions.Session = append(buildOptions.Session, sshAgentProvider)
+			}
+
 			opts[imageName] = buildOptions
 			opts[imageName] = buildOptions
 		}
 		}
 	}
 	}
@@ -296,3 +305,14 @@ func dockerFilePath(context string, dockerfile string) string {
 	}
 	}
 	return filepath.Join(context, dockerfile)
 	return filepath.Join(context, dockerfile)
 }
 }
+
+func sshAgentProvider(sshKeys types.SSHConfig) (session.Attachable, error) {
+	sshConfig := make([]sshprovider.AgentConfig, 0, len(sshKeys))
+	for _, sshKey := range sshKeys {
+		sshConfig = append(sshConfig, sshprovider.AgentConfig{
+			ID:    sshKey.ID,
+			Paths: []string{sshKey.Path},
+		})
+	}
+	return sshprovider.NewSSHAgentProvider(sshConfig)
+}

+ 15 - 0
pkg/e2e/compose_build_test.go

@@ -18,6 +18,7 @@ package e2e
 
 
 import (
 import (
 	"net/http"
 	"net/http"
+	"os"
 	"strings"
 	"strings"
 	"testing"
 	"testing"
 	"time"
 	"time"
@@ -79,6 +80,20 @@ func TestLocalComposeBuild(t *testing.T) {
 		res.Assert(t, icmd.Expected{Out: `"RESULT": "SUCCESS"`})
 		res.Assert(t, icmd.Expected{Out: `"RESULT": "SUCCESS"`})
 	})
 	})
 
 
+	t.Run("build failed with ssh default value", func(t *testing.T) {
+		//unset SSH_AUTH_SOCK to be sure we don't have a default value for the SSH Agent
+		defaultSSHAUTHSOCK := os.Getenv("SSH_AUTH_SOCK")
+		os.Unsetenv("SSH_AUTH_SOCK")                         //nolint:errcheck
+		defer os.Setenv("SSH_AUTH_SOCK", defaultSSHAUTHSOCK) //nolint:errcheck
+
+		res := c.RunDockerComposeCmdNoCheck("--project-directory", "fixtures/build-test", "build", "--ssh", "")
+		res.Assert(t, icmd.Expected{
+			ExitCode: 1,
+			Err:      "invalid empty ssh agent socket: make sure SSH_AUTH_SOCK is set",
+		})
+
+	})
+
 	t.Run("build as part of up", func(t *testing.T) {
 	t.Run("build as part of up", func(t *testing.T) {
 		c.RunDockerOrExitError("rmi", "build-test_nginx")
 		c.RunDockerOrExitError("rmi", "build-test_nginx")
 		c.RunDockerOrExitError("rmi", "custom-nginx")
 		c.RunDockerOrExitError("rmi", "custom-nginx")

+ 9 - 6
pkg/e2e/framework.go

@@ -204,17 +204,20 @@ func (c *E2eCLI) RunDockerCmd(args ...string) *icmd.Result {
 
 
 // RunDockerComposeCmd runs a docker compose command, expects no error and returns a result
 // RunDockerComposeCmd runs a docker compose command, expects no error and returns a result
 func (c *E2eCLI) RunDockerComposeCmd(args ...string) *icmd.Result {
 func (c *E2eCLI) RunDockerComposeCmd(args ...string) *icmd.Result {
+	res := c.RunDockerComposeCmdNoCheck(args...)
+	res.Assert(c.test, icmd.Success)
+	return res
+}
+
+// RunDockerComposeCmdNoCheck runs a docker compose command, don't presume of any expectation and returns a result
+func (c *E2eCLI) RunDockerComposeCmdNoCheck(args ...string) *icmd.Result {
 	if composeStandaloneMode {
 	if composeStandaloneMode {
 		composeBinary, err := findExecutable(DockerComposeExecutableName, []string{"../../bin", "../../../bin"})
 		composeBinary, err := findExecutable(DockerComposeExecutableName, []string{"../../bin", "../../../bin"})
 		assert.NilError(c.test, err)
 		assert.NilError(c.test, err)
-		res := icmd.RunCmd(c.NewCmd(composeBinary, args...))
-		res.Assert(c.test, icmd.Success)
-		return res
+		return icmd.RunCmd(c.NewCmd(composeBinary, args...))
 	}
 	}
 	args = append([]string{"compose"}, args...)
 	args = append([]string{"compose"}, args...)
-	res := icmd.RunCmd(c.NewCmd(DockerExecutableName, args...))
-	res.Assert(c.test, icmd.Success)
-	return res
+	return icmd.RunCmd(c.NewCmd(DockerExecutableName, args...))
 }
 }
 
 
 // StdoutContains returns a predicate on command result expecting a string in stdout
 // StdoutContains returns a predicate on command result expecting a string in stdout