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

Signed-off-by: Guillaume Lours <guillaume.lours@docker.com>
This commit is contained in:
Guillaume Lours 2022-03-30 11:47:34 +02:00 committed by Nicolas De loof
parent 3c12b94519
commit ff73827a6f
9 changed files with 93 additions and 18 deletions

View File

@ -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{ apiBuildOptions, err := opts.toAPIBuildOptions(services)
Pull: opts.pull, if err != nil {
Progress: opts.progress, return err
Args: types.NewMappingWithEquals(opts.args), }
NoCache: opts.noCache, return backend.Build(ctx, project, apiBuildOptions)
Quiet: opts.quiet,
Services: services,
})
} }

View File

@ -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-->

View File

@ -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

2
go.mod
View File

@ -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

4
go.sum
View File

@ -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.2 h1:y1dwl3KUTBnWPVur6EZno9zUIum6Q87/F5keljnGQB4=
github.com/compose-spec/compose-go v1.2.1/go.mod h1:pAy7Mikpeft4pxkFU565/DRHEbDfR84G6AQuiL+Hdg8= 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=

View File

@ -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

View File

@ -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)
}

View File

@ -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")

View File

@ -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...)) return icmd.RunCmd(c.NewCmd(composeBinary, args...))
res.Assert(c.test, icmd.Success)
return res
} }
args = append([]string{"compose"}, args...) args = append([]string{"compose"}, args...)
res := icmd.RunCmd(c.NewCmd(DockerExecutableName, args...)) return icmd.RunCmd(c.NewCmd(DockerExecutableName, args...))
res.Assert(c.test, icmd.Success)
return res
} }
// StdoutContains returns a predicate on command result expecting a string in stdout // StdoutContains returns a predicate on command result expecting a string in stdout