Bladeren bron

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

Signed-off-by: Guillaume Lours <[email protected]>
Guillaume Lours 3 jaren geleden
bovenliggende
commit
ff73827a6f

+ 33 - 9
cmd/compose/build.go

@@ -23,6 +23,7 @@ import (
 	"strings"
 
 	"github.com/compose-spec/compose-go/cli"
+	"github.com/compose-spec/compose-go/loader"
 	"github.com/compose-spec/compose-go/types"
 	buildx "github.com/docker/buildx/util/progress"
 	"github.com/docker/compose/v2/pkg/utils"
@@ -40,6 +41,28 @@ type buildOptions struct {
 	args     []string
 	noCache  bool
 	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{
@@ -73,7 +96,10 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command {
 			}
 			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)
 		}),
 		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().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().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().MarkHidden("parallel") //nolint:errcheck
 	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 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) |
 | `--pull` |  |  | Always attempt to pull a newer version of the image. |
 | `-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-->

+ 10 - 0
docs/reference/docker_compose_build.yaml

@@ -117,6 +117,16 @@ options:
   experimentalcli: false
   kubernetes: 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
 experimental: false
 experimentalcli: false

+ 1 - 1
go.mod

@@ -6,7 +6,7 @@ require (
 	github.com/AlecAivazis/survey/v2 v2.3.2
 	github.com/buger/goterm v1.0.4
 	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/containerd v1.6.1
 	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/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.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/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=

+ 2 - 0
pkg/api/api.go

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

+ 20 - 0
pkg/compose/build.go

@@ -31,6 +31,7 @@ import (
 	bclient "github.com/moby/buildkit/client"
 	"github.com/moby/buildkit/session"
 	"github.com/moby/buildkit/session/auth/authprovider"
+	"github.com/moby/buildkit/session/sshforward/sshprovider"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 
 	"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
 		}
 	}
@@ -296,3 +305,14 @@ func dockerFilePath(context string, dockerfile string) string {
 	}
 	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 (
 	"net/http"
+	"os"
 	"strings"
 	"testing"
 	"time"
@@ -79,6 +80,20 @@ func TestLocalComposeBuild(t *testing.T) {
 		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) {
 		c.RunDockerOrExitError("rmi", "build-test_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
 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 {
 		composeBinary, err := findExecutable(DockerComposeExecutableName, []string{"../../bin", "../../../bin"})
 		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...)
-	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