From 02744ac4f93f2d4179943c719497b704c3d7c7a6 Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Mon, 22 Mar 2021 18:50:46 -0300 Subject: [PATCH] Add --ignore-pull-failures to command pull Signed-off-by: Ulysses Souza --- aci/compose.go | 2 +- api/client/compose.go | 2 +- api/compose/api.go | 7 +- cli/cmd/compose/pull.go | 19 ++++-- ecs/local/compose.go | 4 +- ecs/up.go | 2 +- kube/compose.go | 2 +- local/compose/pull.go | 138 +++++++++++++++++++++++----------------- 8 files changed, 104 insertions(+), 72 deletions(-) diff --git a/aci/compose.go b/aci/compose.go index 3c8f9360d..5551e0a69 100644 --- a/aci/compose.go +++ b/aci/compose.go @@ -52,7 +52,7 @@ func (cs *aciComposeService) Push(ctx context.Context, project *types.Project, o return errdefs.ErrNotImplemented } -func (cs *aciComposeService) Pull(ctx context.Context, project *types.Project) error { +func (cs *aciComposeService) Pull(ctx context.Context, project *types.Project, options compose.PullOptions) error { return errdefs.ErrNotImplemented } diff --git a/api/client/compose.go b/api/client/compose.go index 1832bf20f..ac54ac8f4 100644 --- a/api/client/compose.go +++ b/api/client/compose.go @@ -36,7 +36,7 @@ func (c *composeService) Push(ctx context.Context, project *types.Project, optio return errdefs.ErrNotImplemented } -func (c *composeService) Pull(ctx context.Context, project *types.Project) error { +func (c *composeService) Pull(ctx context.Context, project *types.Project, options compose.PullOptions) error { return errdefs.ErrNotImplemented } diff --git a/api/compose/api.go b/api/compose/api.go index 1af579313..0ad6e1a51 100644 --- a/api/compose/api.go +++ b/api/compose/api.go @@ -33,7 +33,7 @@ type Service interface { // Push executes the equivalent ot a `compose push` Push(ctx context.Context, project *types.Project, options PushOptions) error // Pull executes the equivalent of a `compose pull` - Pull(ctx context.Context, project *types.Project) error + Pull(ctx context.Context, project *types.Project, opts PullOptions) error // Create executes the equivalent to a `compose create` Create(ctx context.Context, project *types.Project, opts CreateOptions) error // Start executes the equivalent to a `compose start` @@ -157,6 +157,11 @@ type PushOptions struct { IgnoreFailures bool } +// PullOptions group options of the Push API +type PullOptions struct { + IgnoreFailures bool +} + // KillOptions group options of the Kill API type KillOptions struct { // Signal to send to containers diff --git a/cli/cmd/compose/pull.go b/cli/cmd/compose/pull.go index e7e5a8cbc..c84dbb052 100644 --- a/cli/cmd/compose/pull.go +++ b/cli/cmd/compose/pull.go @@ -25,6 +25,7 @@ import ( "github.com/spf13/cobra" "github.com/docker/compose-cli/api/client" + "github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/progress" "github.com/docker/compose-cli/utils" ) @@ -32,10 +33,11 @@ import ( type pullOptions struct { *projectOptions composeOptions - quiet bool - parallel bool - noParallel bool - includeDeps bool + quiet bool + parallel bool + noParallel bool + includeDeps bool + ignorePullFailures bool } func pullCommand(p *projectOptions) *cobra.Command { @@ -59,6 +61,7 @@ func pullCommand(p *projectOptions) *cobra.Command { flags.MarkHidden("parallel") //nolint:errcheck cmd.Flags().BoolVar(&opts.parallel, "no-parallel", true, "DEPRECATED disable parallel pulling.") flags.MarkHidden("no-parallel") //nolint:errcheck + cmd.Flags().BoolVar(&opts.ignorePullFailures, "ignore-pull-failures", false, "Pull what it can and ignores images with pull failures") return cmd } @@ -86,12 +89,16 @@ func runPull(ctx context.Context, opts pullOptions, services []string) error { project.Services = enabled } + apiOpts := compose.PullOptions{ + IgnoreFailures: opts.ignorePullFailures, + } + if opts.quiet { - return c.ComposeService().Pull(ctx, project) + return c.ComposeService().Pull(ctx, project, apiOpts) } _, err = progress.Run(ctx, func(ctx context.Context) (string, error) { - return "", c.ComposeService().Pull(ctx, project) + return "", c.ComposeService().Pull(ctx, project, apiOpts) }) return err } diff --git a/ecs/local/compose.go b/ecs/local/compose.go index 3b368e9d4..7a15b1d82 100644 --- a/ecs/local/compose.go +++ b/ecs/local/compose.go @@ -40,8 +40,8 @@ func (e ecsLocalSimulation) Push(ctx context.Context, project *types.Project, op return e.compose.Push(ctx, project, options) } -func (e ecsLocalSimulation) Pull(ctx context.Context, project *types.Project) error { - return e.compose.Pull(ctx, project) +func (e ecsLocalSimulation) Pull(ctx context.Context, project *types.Project, options compose.PullOptions) error { + return e.compose.Pull(ctx, project, options) } func (e ecsLocalSimulation) Create(ctx context.Context, project *types.Project, opts compose.CreateOptions) error { diff --git a/ecs/up.go b/ecs/up.go index e0cc344a1..db1ce902f 100644 --- a/ecs/up.go +++ b/ecs/up.go @@ -39,7 +39,7 @@ func (b *ecsAPIService) Push(ctx context.Context, project *types.Project, option return errdefs.ErrNotImplemented } -func (b *ecsAPIService) Pull(ctx context.Context, project *types.Project) error { +func (b *ecsAPIService) Pull(ctx context.Context, project *types.Project, options compose.PullOptions) error { return errdefs.ErrNotImplemented } diff --git a/kube/compose.go b/kube/compose.go index 49bca75a6..e8d40ad3e 100644 --- a/kube/compose.go +++ b/kube/compose.go @@ -174,7 +174,7 @@ func (s *composeService) Push(ctx context.Context, project *types.Project, optio } // Pull executes the equivalent of a `compose pull` -func (s *composeService) Pull(ctx context.Context, project *types.Project) error { +func (s *composeService) Pull(ctx context.Context, project *types.Project, options compose.PullOptions) error { return errdefs.ErrNotImplemented } diff --git a/local/compose/pull.go b/local/compose/pull.go index e0a277c1a..8a4f79257 100644 --- a/local/compose/pull.go +++ b/local/compose/pull.go @@ -21,22 +21,25 @@ import ( "encoding/base64" "encoding/json" "errors" + "fmt" "io" "strings" "github.com/compose-spec/compose-go/types" "github.com/distribution/distribution/v3/reference" + "github.com/docker/buildx/driver" cliconfig "github.com/docker/cli/cli/config" moby "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/registry" "golang.org/x/sync/errgroup" + "github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/config" "github.com/docker/compose-cli/api/progress" ) -func (s *composeService) Pull(ctx context.Context, project *types.Project) error { +func (s *composeService) Pull(ctx context.Context, project *types.Project, opts compose.PullOptions) error { configFile, err := cliconfig.Load(config.Dir()) if err != nil { return err @@ -64,68 +67,19 @@ func (s *composeService) Pull(ctx context.Context, project *types.Project) error continue } eg.Go(func() error { - w.Event(progress.Event{ - ID: service.Name, - Status: progress.Working, - Text: "Pulling", - }) - ref, err := reference.ParseNormalizedNamed(service.Image) + err := s.pullServiceImage(ctx, service, info, configFile, w) if err != nil { - return err - } - - repoInfo, err := registry.ParseRepositoryInfo(ref) - if err != nil { - return err - } - - key := repoInfo.Index.Name - if repoInfo.Index.Official { - key = info.IndexServerAddress - } - - authConfig, err := configFile.GetAuthConfig(key) - if err != nil { - return err - } - - buf, err := json.Marshal(authConfig) - if err != nil { - return err - } - - stream, err := s.apiClient.ImagePull(ctx, service.Image, moby.ImagePullOptions{ - RegistryAuth: base64.URLEncoding.EncodeToString(buf), - Platform: service.Platform, - }) - if err != nil { - w.Event(progress.Event{ - ID: service.Name, - Status: progress.Error, - Text: "Error", - }) - return err - } - - dec := json.NewDecoder(stream) - for { - var jm jsonmessage.JSONMessage - if err := dec.Decode(&jm); err != nil { - if err == io.EOF { - break - } + if !opts.IgnoreFailures { return err } - if jm.Error != nil { - return errors.New(jm.Error.Message) - } - toPullProgressEvent(service.Name, jm, w) + // If IgnoreFailures we still want to show the error message + w.Event(progress.Event{ + ID: fmt.Sprintf("Pulling %s:", service.Name), + Text: fmt.Sprintf("%v", err), + Status: progress.Error, + StatusText: fmt.Sprintf("%s", err), + }) } - w.Event(progress.Event{ - ID: service.Name, - Status: progress.Done, - Text: "Pulled", - }) return nil }) } @@ -133,6 +87,72 @@ func (s *composeService) Pull(ctx context.Context, project *types.Project) error return eg.Wait() } +func (s *composeService) pullServiceImage(ctx context.Context, service types.ServiceConfig, info moby.Info, configFile driver.Auth, w progress.Writer) error { + w.Event(progress.Event{ + ID: service.Name, + Status: progress.Working, + Text: "Pulling", + }) + ref, err := reference.ParseNormalizedNamed(service.Image) + if err != nil { + return err + } + + repoInfo, err := registry.ParseRepositoryInfo(ref) + if err != nil { + return err + } + + key := repoInfo.Index.Name + if repoInfo.Index.Official { + key = info.IndexServerAddress + } + + authConfig, err := configFile.GetAuthConfig(key) + if err != nil { + return err + } + + buf, err := json.Marshal(authConfig) + if err != nil { + return err + } + + stream, err := s.apiClient.ImagePull(ctx, service.Image, moby.ImagePullOptions{ + RegistryAuth: base64.URLEncoding.EncodeToString(buf), + Platform: service.Platform, + }) + if err != nil { + w.Event(progress.Event{ + ID: service.Name, + Status: progress.Error, + Text: "Error", + }) + return err + } + + dec := json.NewDecoder(stream) + for { + var jm jsonmessage.JSONMessage + if err := dec.Decode(&jm); err != nil { + if err == io.EOF { + break + } + return err + } + if jm.Error != nil { + return errors.New(jm.Error.Message) + } + toPullProgressEvent(service.Name, jm, w) + } + w.Event(progress.Event{ + ID: service.Name, + Status: progress.Done, + Text: "Pulled", + }) + return nil +} + func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, w progress.Writer) { if jm.ID == "" || jm.Progress == nil { return