diff --git a/go.mod b/go.mod index 371a7f468dc..3a64d66a90d 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/spf13/cobra v1.3.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.0 + github.com/theupdateframework/notary v0.6.1 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c gotest.tools v2.2.0+incompatible gotest.tools/v3 v3.1.0 @@ -93,7 +94,6 @@ require ( github.com/qri-io/jsonpointer v0.1.0 // indirect github.com/qri-io/jsonschema v0.1.1 // indirect github.com/sergi/go-diff v1.1.0 // indirect - github.com/theupdateframework/notary v0.6.1 // indirect github.com/tonistiigi/fsutil v0.0.0-20210818161904-4442383b5028 // indirect github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f // indirect @@ -135,7 +135,7 @@ require ( // (for buildx) replace ( - github.com/docker/cli => github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible + github.com/docker/cli => github.com/ndeloof/cli v0.0.0-20220222095154-9145a1b2e6a6 github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220121014307-40bb9831756f+incompatible go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.0.0-20210714055410-d010b05b4939 go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace => github.com/tonistiigi/opentelemetry-go-contrib/instrumentation/net/http/httptrace/otelhttptrace v0.0.0-20210714055410-d010b05b4939 diff --git a/go.sum b/go.sum index 1cb0bc60488..5932f40d799 100644 --- a/go.sum +++ b/go.sum @@ -467,8 +467,6 @@ github.com/distribution/distribution/v3 v3.0.0-20210316161203-a01c71e2477e/go.mo github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/buildx v0.7.1 h1:l2DlW8YDbB3pH2bUFY5Q9pPQdhd42wqlnO5hoyWrjYM= github.com/docker/buildx v0.7.1/go.mod h1:PzxALHhYWPNhw/8JajPOJBkvx1w2tgOnppL4ESg0wOY= -github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible h1:CaaxCD/l9Dxogu6lxf7AQautlv3sHULrasPadayp0fM= -github.com/docker/cli v20.10.3-0.20210702143511-f782d1355eff+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli-docs-tool v0.2.1 h1:ffZhhdws6kE+dCKHLMlYROZz8z01RtWbz+g4xz0eBIU= github.com/docker/cli-docs-tool v0.2.1/go.mod h1:rgW5KKdNpLMBIuH4WQ/1RNh38nH+/Ay5jgL4P0ZMPpY= github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496/go.mod h1:iT2pYfi580XlpaV4KmK0T6+4/9+XoKmk/fhoDod1emE= @@ -1086,6 +1084,8 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+ github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/ndeloof/cli v0.0.0-20220222095154-9145a1b2e6a6 h1:PSmURWsOn7VfWBafKAISoFgPQKDvQ2/YL+X9YXpD6ek= +github.com/ndeloof/cli v0.0.0-20220222095154-9145a1b2e6a6/go.mod h1:wxmb4t3v3ZwISBr3B/B631+3VJe+erAxkl3vXQwXj2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= diff --git a/pkg/compose/build.go b/pkg/compose/build.go index bc3d61f1d58..3250f5c4fbc 100644 --- a/pkg/compose/build.go +++ b/pkg/compose/build.go @@ -28,7 +28,6 @@ import ( _ "github.com/docker/buildx/driver/docker" // required to get default driver registered "github.com/docker/buildx/util/buildflags" xprogress "github.com/docker/buildx/util/progress" - "github.com/docker/cli/cli/command" "github.com/docker/docker/pkg/urlutil" bclient "github.com/moby/buildkit/client" "github.com/moby/buildkit/session" @@ -192,30 +191,10 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ return images, nil } -func (s *composeService) serverInfo(ctx context.Context) (command.ServerInfo, error) { - ping, err := s.apiClient.Ping(ctx) - if err != nil { - return command.ServerInfo{}, err - } - serverInfo := command.ServerInfo{ - HasExperimental: ping.Experimental, - OSType: ping.OSType, - BuildkitVersion: ping.BuilderVersion, - } - return serverInfo, err -} - func (s *composeService) doBuild(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) { if len(opts) == 0 { return nil, nil } - serverInfo, err := s.serverInfo(ctx) - if err != nil { - return nil, err - } - if buildkitEnabled, err := command.BuildKitEnabled(serverInfo); err != nil || !buildkitEnabled { - return s.doBuildClassic(ctx, opts) - } return s.doBuildBuildkit(ctx, project, opts, mode) } diff --git a/pkg/compose/cli.go b/pkg/compose/cli.go new file mode 100644 index 00000000000..e35902fc871 --- /dev/null +++ b/pkg/compose/cli.go @@ -0,0 +1,130 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package compose + +import ( + "io" + "os" + + "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/cli/context/docker" + "github.com/docker/cli/cli/context/store" + manifeststore "github.com/docker/cli/cli/manifest/store" + client2 "github.com/docker/cli/cli/registry/client" + "github.com/docker/cli/cli/streams" + "github.com/docker/cli/cli/trust" + "github.com/docker/docker/client" + notaryclient "github.com/theupdateframework/notary/client" +) + +// dockerCli creates a (partial) command.Cli so we can use docker/cli commands +func (s *composeService) dockerCli() command.Cli { + return dockerCli{ + apiClient: s.apiClient, + configFile: s.configFile, + } +} + +type dockerCli struct { + apiClient client.APIClient + configFile *configfile.ConfigFile +} + +func (d dockerCli) Client() client.APIClient { + return d.apiClient +} + +func (d dockerCli) ConfigFile() *configfile.ConfigFile { + return d.configFile +} + +func (d dockerCli) Out() *streams.Out { + return streams.NewOut(os.Stdout) +} + +func (d dockerCli) Err() io.Writer { + return os.Stderr +} + +func (d dockerCli) In() *streams.In { + return streams.NewIn(os.Stdin) +} + +func (d dockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) { + //TODO implement me + panic("implement me") +} + +func (d dockerCli) DefaultVersion() string { + //TODO implement me + panic("implement me") +} + +func (d dockerCli) ManifestStore() manifeststore.Store { + //TODO implement me + panic("implement me") +} + +func (d dockerCli) RegistryClient(b bool) client2.RegistryClient { + //TODO implement me + panic("implement me") +} + +func (d dockerCli) ContentTrustEnabled() bool { + //TODO implement me + panic("implement me") +} + +func (d dockerCli) ContextStore() store.Store { + //TODO implement me + panic("implement me") +} + +func (d dockerCli) CurrentContext() string { + //TODO implement me + panic("implement me") +} + +func (d dockerCli) StackOrchestrator(flagValue string) (command.Orchestrator, error) { + //TODO implement me + panic("implement me") +} + +func (d dockerCli) DockerEndpoint() docker.Endpoint { + //TODO implement me + panic("implement me") +} + +func (d dockerCli) SetIn(in *streams.In) { + // Nop +} + +func (d dockerCli) Apply(ops ...command.DockerCliOption) error { + // Nop + return nil +} + +func (d dockerCli) ServerInfo() command.ServerInfo { + panic("implement me") +} + +func (d dockerCli) ClientInfo() command.ClientInfo { + panic("implement me") +} + +var _ command.Cli = dockerCli{} diff --git a/pkg/compose/exec.go b/pkg/compose/exec.go index b0f06004c81..bcf2603a0e1 100644 --- a/pkg/compose/exec.go +++ b/pkg/compose/exec.go @@ -19,124 +19,41 @@ package compose import ( "context" "fmt" - "io" - - "github.com/docker/cli/cli/streams" + "github.com/docker/cli/cli" + "github.com/docker/cli/cli/command/container" + cliopts "github.com/docker/cli/opts" + "github.com/docker/compose/v2/pkg/api" moby "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/pkg/stdcopy" - "github.com/moby/term" - - "github.com/docker/compose/v2/pkg/api" ) func (s *composeService) Exec(ctx context.Context, project string, opts api.RunOptions) (int, error) { - container, err := s.getExecTarget(ctx, project, opts) - if err != nil { - return 0, err - } - - exec, err := s.apiClient.ContainerExecCreate(ctx, container.ID, moby.ExecConfig{ - Cmd: opts.Command, - Env: opts.Environment, - User: opts.User, - Privileged: opts.Privileged, - Tty: opts.Tty, - Detach: opts.Detach, - WorkingDir: opts.WorkingDir, - - AttachStdin: true, - AttachStdout: true, - AttachStderr: true, - }) + target, err := s.getExecTarget(ctx, project, opts) if err != nil { return 0, err } - if opts.Detach { - return 0, s.apiClient.ContainerExecStart(ctx, exec.ID, moby.ExecStartCheck{ - Detach: true, - Tty: opts.Tty, - }) + env := cliopts.NewListOpts(nil) + for _, s := range opts.Environment { + env.Set(s) // nolint:errcheck - ListOpts has no validator } - resp, err := s.apiClient.ContainerExecAttach(ctx, exec.ID, moby.ExecStartCheck{ - Tty: opts.Tty, + err = container.RunExec(s.dockerCli(), container.ExecOptions{ + Interactive: true, + Tty: opts.Tty, + Detach: opts.Detach, + User: opts.User, + Privileged: opts.Privileged, + Env: env, + EnvFile: cliopts.NewListOpts(nil), + Workdir: opts.WorkingDir, + Container: target.ID, + Command: opts.Command, }) - if err != nil { - return 0, err - } - defer resp.Close() //nolint:errcheck - - if opts.Tty { - s.monitorTTySize(ctx, exec.ID, s.apiClient.ContainerExecResize) - if err != nil { - return 0, err - } - } - - err = s.interactiveExec(ctx, opts, resp) - if err != nil { - return 0, err - } - - return s.getExecExitStatus(ctx, exec.ID) -} - -// inspired by https://github.com/docker/cli/blob/master/cli/command/container/exec.go#L116 -func (s *composeService) interactiveExec(ctx context.Context, opts api.RunOptions, resp moby.HijackedResponse) error { - outputDone := make(chan error) - inputDone := make(chan error) - - stdout := ContainerStdout{HijackedResponse: resp} - stdin := ContainerStdin{HijackedResponse: resp} - r, err := s.getEscapeKeyProxy(opts.Stdin, opts.Tty) - if err != nil { - return err - } - - in := streams.NewIn(opts.Stdin) - if in.IsTerminal() && opts.Tty { - state, err := term.SetRawTerminal(in.FD()) - if err != nil { - return err - } - defer term.RestoreTerminal(in.FD(), state) //nolint:errcheck - } - - go func() { - if opts.Tty { - _, err := io.Copy(opts.Stdout, stdout) - outputDone <- err - } else { - _, err := stdcopy.StdCopy(opts.Stdout, opts.Stderr, stdout) - outputDone <- err - } - stdout.Close() //nolint:errcheck - }() - - go func() { - _, err := io.Copy(stdin, r) - inputDone <- err - stdin.Close() //nolint:errcheck - }() - - for { - select { - case err := <-outputDone: - return err - case err := <-inputDone: - if _, ok := err.(term.EscapeError); ok { - return nil - } - if err != nil { - return err - } - // Wait for output to complete streaming - case <-ctx.Done(): - return ctx.Err() - } + if sterr, ok := err.(cli.StatusError); ok { + return sterr.StatusCode, nil } + return 0, err } func (s *composeService) getExecTarget(ctx context.Context, projectName string, opts api.RunOptions) (moby.Container, error) { @@ -156,11 +73,3 @@ func (s *composeService) getExecTarget(ctx context.Context, projectName string, container := containers[0] return container, nil } - -func (s *composeService) getExecExitStatus(ctx context.Context, execID string) (int, error) { - resp, err := s.apiClient.ContainerExecInspect(ctx, execID) - if err != nil { - return 0, err - } - return resp.ExitCode, nil -}