move up logic from CLI into local backend

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2021-06-07 14:21:55 +02:00
Родитель b86ff6b18f
Коммит c135bd1d7c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 9858809D6F8F6E7E
15 изменённых файлов: 323 добавлений и 304 удалений

Просмотреть файл

@ -87,6 +87,12 @@ func (cs *aciComposeService) Copy(ctx context.Context, project *types.Project, o
} }
func (cs *aciComposeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error { func (cs *aciComposeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
return progress.Run(ctx, func(ctx context.Context) error {
return cs.up(ctx, project)
})
}
func (cs *aciComposeService) up(ctx context.Context, project *types.Project) error {
logrus.Debugf("Up on project with name %q", project.Name) logrus.Debugf("Up on project with name %q", project.Name)
if err := autocreateFileshares(ctx, project); err != nil { if err := autocreateFileshares(ctx, project); err != nil {

Просмотреть файл

@ -112,10 +112,14 @@ type CreateOptions struct {
// StartOptions group options of the Start API // StartOptions group options of the Start API
type StartOptions struct { type StartOptions struct {
// Attach will attach to service containers and send container logs and events // Attach to container and forward logs if not nil
Attach ContainerEventListener Attach LogConsumer
// Services passed in the command line to be started // AttachTo set the services to attach to
Services []string AttachTo []string
// CascadeStop stops the application when a container stops
CascadeStop bool
// ExitCodeFrom return exit code from specified service
ExitCodeFrom string
} }
// RestartOptions group options of the Restart API // RestartOptions group options of the Restart API
@ -136,10 +140,8 @@ type StopOptions struct {
// UpOptions group options of the Up API // UpOptions group options of the Up API
type UpOptions struct { type UpOptions struct {
// Detach will create services and return immediately Create CreateOptions
Detach bool Start StartOptions
// QuietPull makes the pulling process quiet
QuietPull bool
} }
// DownOptions group options of the Down API // DownOptions group options of the Down API

Просмотреть файл

@ -21,6 +21,7 @@ import (
"fmt" "fmt"
"os" "os"
"os/signal" "os/signal"
"path/filepath"
"strings" "strings"
"syscall" "syscall"
@ -90,14 +91,43 @@ type projectOptions struct {
// ProjectFunc does stuff within a types.Project // ProjectFunc does stuff within a types.Project
type ProjectFunc func(ctx context.Context, project *types.Project) error type ProjectFunc func(ctx context.Context, project *types.Project) error
// ProjectServicesFunc does stuff within a types.Project and a selection of services
type ProjectServicesFunc func(ctx context.Context, project *types.Project, services []string) error
// WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services // WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
func (o *projectOptions) WithServices(services []string, fn ProjectFunc) func(cmd *cobra.Command, args []string) error { func (o *projectOptions) WithProject(fn ProjectFunc) func(cmd *cobra.Command, args []string) error {
return Adapt(func(ctx context.Context, strings []string) error { return o.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
project, err := o.toProject(services) return fn(ctx, project)
})
}
// WithServices creates a cobra run command from a ProjectFunc based on configured project options and selected services
func (o *projectOptions) WithServices(fn ProjectServicesFunc) func(cmd *cobra.Command, args []string) error {
return Adapt(func(ctx context.Context, args []string) error {
project, err := o.toProject(args)
if err != nil { if err != nil {
return err return err
} }
return fn(ctx, project)
if o.EnvFile != "" {
var services types.Services
for _, s := range project.Services {
ef := o.EnvFile
if ef != "" {
if !filepath.IsAbs(ef) {
ef = filepath.Join(project.WorkingDir, o.EnvFile)
}
if s.Labels == nil {
s.Labels = make(map[string]string)
}
s.Labels[compose.EnvironmentFileLabel] = ef
services = append(services, s)
}
}
project.Services = services
}
return fn(ctx, project, args)
}) })
} }
@ -217,7 +247,7 @@ func RootCommand(contextType string, backend compose.Service) *cobra.Command {
} }
command.AddCommand( command.AddCommand(
upCommand(&opts, contextType, backend), upCommand(&opts, backend),
downCommand(&opts, backend), downCommand(&opts, backend),
startCommand(&opts, backend), startCommand(&opts, backend),
restartCommand(&opts, backend), restartCommand(&opts, backend),

Просмотреть файл

@ -19,22 +19,29 @@ package compose
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/compose-spec/compose-go/types"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
) )
type createOptions struct { type createOptions struct {
*composeOptions Build bool
noBuild bool
removeOrphans bool
forceRecreate bool forceRecreate bool
noRecreate bool noRecreate bool
recreateDeps bool
noInherit bool
timeChanged bool
timeout int
quietPull bool
} }
func createCommand(p *projectOptions, backend compose.Service) *cobra.Command { func createCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := createOptions{ opts := createOptions{}
composeOptions: &composeOptions{},
}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "create [SERVICE...]", Use: "create [SERVICE...]",
Short: "Creates containers for a service.", Short: "Creates containers for a service.",
@ -47,17 +54,15 @@ func createCommand(p *projectOptions, backend compose.Service) *cobra.Command {
} }
return nil return nil
}), }),
RunE: Adapt(func(ctx context.Context, args []string) error { RunE: p.WithProject(func(ctx context.Context, project *types.Project) error {
return runCreateStart(ctx, backend, upOptions{ return backend.Create(ctx, project, compose.CreateOptions{
composeOptions: &composeOptions{ RemoveOrphans: opts.removeOrphans,
projectOptions: p, Recreate: opts.recreateStrategy(),
Build: opts.Build, RecreateDependencies: opts.dependenciesRecreateStrategy(),
noBuild: opts.noBuild, Inherit: !opts.noInherit,
}, Timeout: opts.GetTimeout(),
noStart: true, QuietPull: false,
forceRecreate: opts.forceRecreate, })
noRecreate: opts.noRecreate,
}, args)
}), }),
} }
flags := cmd.Flags() flags := cmd.Flags()
@ -67,3 +72,46 @@ func createCommand(p *projectOptions, backend compose.Service) *cobra.Command {
flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.") flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
return cmd return cmd
} }
func (opts createOptions) recreateStrategy() string {
if opts.noRecreate {
return compose.RecreateNever
}
if opts.forceRecreate {
return compose.RecreateForce
}
return compose.RecreateDiverged
}
func (opts createOptions) dependenciesRecreateStrategy() string {
if opts.noRecreate {
return compose.RecreateNever
}
if opts.recreateDeps {
return compose.RecreateForce
}
return compose.RecreateDiverged
}
func (opts createOptions) GetTimeout() *time.Duration {
if opts.timeChanged {
t := time.Duration(opts.timeout) * time.Second
return &t
}
return nil
}
func (opts createOptions) Apply(project *types.Project) {
if opts.Build {
for i, service := range project.Services {
service.PullPolicy = types.PullPolicyBuild
project.Services[i] = service
}
}
if opts.noBuild {
for i, service := range project.Services {
service.Build = nil
project.Services[i] = service
}
}
}

Просмотреть файл

@ -18,7 +18,6 @@ package compose
import ( import (
"context" "context"
"os"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -31,7 +30,7 @@ func killCommand(p *projectOptions, backend compose.Service) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "kill [options] [SERVICE...]", Use: "kill [options] [SERVICE...]",
Short: "Force stop service containers.", Short: "Force stop service containers.",
RunE: p.WithServices(os.Args, func(ctx context.Context, project *types.Project) error { RunE: p.WithProject(func(ctx context.Context, project *types.Project) error {
return backend.Kill(ctx, project, opts) return backend.Kill(ctx, project, opts)
}), }),
} }

Просмотреть файл

@ -120,7 +120,11 @@ func runCommand(p *projectOptions, backend compose.Service) *cobra.Command {
return nil return nil
}), }),
RunE: Adapt(func(ctx context.Context, args []string) error { RunE: Adapt(func(ctx context.Context, args []string) error {
return runRun(ctx, backend, opts) project, err := p.toProject([]string{opts.Service})
if err != nil {
return err
}
return runRun(ctx, backend, project, opts)
}), }),
} }
flags := cmd.Flags() flags := cmd.Flags()
@ -143,13 +147,8 @@ func runCommand(p *projectOptions, backend compose.Service) *cobra.Command {
return cmd return cmd
} }
func runRun(ctx context.Context, backend compose.Service, opts runOptions) error { func runRun(ctx context.Context, backend compose.Service, project *types.Project, opts runOptions) error {
project, err := setup(*opts.composeOptions, []string{opts.Service}) err := opts.apply(project)
if err != nil {
return err
}
err = opts.apply(project)
if err != nil { if err != nil {
return err return err
} }

Просмотреть файл

@ -20,40 +20,25 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"os/signal"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/context/store"
"github.com/docker/compose-cli/api/progress"
"github.com/docker/compose-cli/cli/formatter" "github.com/docker/compose-cli/cli/formatter"
"github.com/docker/compose-cli/utils" "github.com/docker/compose-cli/utils"
"github.com/spf13/cobra"
) )
// composeOptions hold options common to `up` and `run` to run compose project // composeOptions hold options common to `up` and `run` to run compose project
type composeOptions struct { type composeOptions struct {
*projectOptions *projectOptions
Build bool
noBuild bool
} }
type upOptions struct { type upOptions struct {
*composeOptions *composeOptions
Detach bool Detach bool
Environment []string Environment []string
removeOrphans bool
forceRecreate bool
noRecreate bool
recreateDeps bool
noStart bool noStart bool
noDeps bool noDeps bool
cascadeStop bool cascadeStop bool
@ -61,39 +46,7 @@ type upOptions struct {
scale []string scale []string
noColor bool noColor bool
noPrefix bool noPrefix bool
timeChanged bool
timeout int
noInherit bool
attachDependencies bool attachDependencies bool
quietPull bool
}
func (opts upOptions) recreateStrategy() string {
if opts.noRecreate {
return compose.RecreateNever
}
if opts.forceRecreate {
return compose.RecreateForce
}
return compose.RecreateDiverged
}
func (opts upOptions) dependenciesRecreateStrategy() string {
if opts.noRecreate {
return compose.RecreateNever
}
if opts.recreateDeps {
return compose.RecreateForce
}
return compose.RecreateDiverged
}
func (opts upOptions) GetTimeout() *time.Duration {
if opts.timeChanged {
t := time.Duration(opts.timeout) * time.Second
return &t
}
return nil
} }
func (opts upOptions) apply(project *types.Project, services []string) error { func (opts upOptions) apply(project *types.Project, services []string) error {
@ -136,192 +89,105 @@ func (opts upOptions) apply(project *types.Project, services []string) error {
return nil return nil
} }
func upCommand(p *projectOptions, contextType string, backend compose.Service) *cobra.Command { func upCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := upOptions{ up := upOptions{}
composeOptions: &composeOptions{ create := createOptions{}
projectOptions: p,
},
}
upCmd := &cobra.Command{ upCmd := &cobra.Command{
Use: "up [SERVICE...]", Use: "up [SERVICE...]",
Short: "Create and start containers", Short: "Create and start containers",
PreRun: func(cmd *cobra.Command, args []string) { PreRun: func(cmd *cobra.Command, args []string) {
opts.timeChanged = cmd.Flags().Changed("timeout") create.timeChanged = cmd.Flags().Changed("timeout")
}, },
PreRunE: Adapt(func(ctx context.Context, args []string) error { PreRunE: Adapt(func(ctx context.Context, args []string) error {
if opts.exitCodeFrom != "" { if up.exitCodeFrom != "" {
opts.cascadeStop = true up.cascadeStop = true
} }
if opts.Build && opts.noBuild { if create.Build && create.noBuild {
return fmt.Errorf("--build and --no-build are incompatible") return fmt.Errorf("--build and --no-build are incompatible")
} }
if opts.Detach && (opts.attachDependencies || opts.cascadeStop) { if up.Detach && (up.attachDependencies || up.cascadeStop) {
return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit or --attach-dependencies") return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit or --attach-dependencies")
} }
if opts.forceRecreate && opts.noRecreate { if create.forceRecreate && create.noRecreate {
return fmt.Errorf("--force-recreate and --no-recreate are incompatible") return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
} }
if opts.recreateDeps && opts.noRecreate { if create.recreateDeps && create.noRecreate {
return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible") return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible")
} }
return nil return nil
}), }),
RunE: Adapt(func(ctx context.Context, args []string) error { RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
switch contextType { return runUp(ctx, backend, create, up, project, services)
case store.LocalContextType, store.DefaultContextType, store.EcsLocalSimulationContextType:
return runCreateStart(ctx, backend, opts, args)
default:
return runUp(ctx, backend, opts, args)
}
}), }),
} }
flags := upCmd.Flags() flags := upCmd.Flags()
flags.StringArrayVarP(&opts.Environment, "environment", "e", []string{}, "Environment variables") flags.StringArrayVarP(&up.Environment, "environment", "e", []string{}, "Environment variables")
flags.BoolVarP(&opts.Detach, "detach", "d", false, "Detached mode: Run containers in the background") flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.") flags.BoolVar(&create.Build, "build", false, "Build images before starting containers.")
flags.BoolVar(&opts.noBuild, "no-build", false, "Don't build an image, even if it's missing.") flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's missing.")
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.") flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
flags.StringArrayVar(&opts.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.") flags.StringArrayVar(&up.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
flags.BoolVar(&opts.noColor, "no-color", false, "Produce monochrome output.") flags.BoolVar(&up.noColor, "no-color", false, "Produce monochrome output.")
flags.BoolVar(&opts.noPrefix, "no-log-prefix", false, "Don't print prefix in logs.") flags.BoolVar(&up.noPrefix, "no-log-prefix", false, "Don't print prefix in logs.")
flags.BoolVar(&create.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
switch contextType { flags.BoolVar(&create.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
case store.LocalContextType, store.DefaultContextType, store.EcsLocalSimulationContextType: flags.BoolVar(&up.noStart, "no-start", false, "Don't start the services after creating them.")
flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.") flags.BoolVar(&up.cascadeStop, "abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d")
flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.") flags.StringVar(&up.exitCodeFrom, "exit-code-from", "", "Return the exit code of the selected service container. Implies --abort-on-container-exit")
flags.BoolVar(&opts.noStart, "no-start", false, "Don't start the services after creating them.") flags.IntVarP(&create.timeout, "timeout", "t", 10, "Use this timeout in seconds for container shutdown when attached or when containers are already running.")
flags.BoolVar(&opts.cascadeStop, "abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d") flags.BoolVar(&up.noDeps, "no-deps", false, "Don't start linked services.")
flags.StringVar(&opts.exitCodeFrom, "exit-code-from", "", "Return the exit code of the selected service container. Implies --abort-on-container-exit") flags.BoolVar(&create.recreateDeps, "always-recreate-deps", false, "Recreate dependent containers. Incompatible with --no-recreate.")
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Use this timeout in seconds for container shutdown when attached or when containers are already running.") flags.BoolVarP(&create.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers.")
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services.") flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Attach to dependent containers.")
flags.BoolVar(&opts.recreateDeps, "always-recreate-deps", false, "Recreate dependent containers. Incompatible with --no-recreate.") flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information.")
flags.BoolVarP(&opts.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers.")
flags.BoolVar(&opts.attachDependencies, "attach-dependencies", false, "Attach to dependent containers.")
flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information.")
}
return upCmd return upCmd
} }
func runUp(ctx context.Context, backend compose.Service, opts upOptions, services []string) error { func runUp(ctx context.Context, backend compose.Service, createOptions createOptions, upOptions upOptions, project *types.Project, services []string) error {
project, err := setup(*opts.composeOptions, services)
if err != nil {
return err
}
err = opts.apply(project, services)
if err != nil {
return err
}
return progress.Run(ctx, func(ctx context.Context) error {
return backend.Up(ctx, project, compose.UpOptions{
Detach: opts.Detach,
QuietPull: opts.quietPull,
})
})
}
func runCreateStart(ctx context.Context, backend compose.Service, opts upOptions, services []string) error {
project, err := setup(*opts.composeOptions, services)
if err != nil {
return err
}
err = opts.apply(project, services)
if err != nil {
return err
}
if len(project.Services) == 0 { if len(project.Services) == 0 {
return fmt.Errorf("no service selected") return fmt.Errorf("no service selected")
} }
err = progress.Run(ctx, func(ctx context.Context) error { createOptions.Apply(project)
err := backend.Create(ctx, project, compose.CreateOptions{
Services: services, err := upOptions.apply(project, services)
RemoveOrphans: opts.removeOrphans,
Recreate: opts.recreateStrategy(),
RecreateDependencies: opts.dependenciesRecreateStrategy(),
Inherit: !opts.noInherit,
Timeout: opts.GetTimeout(),
QuietPull: opts.quietPull,
})
if err != nil {
return err
}
if opts.Detach {
err = backend.Start(ctx, project, compose.StartOptions{
Services: services,
})
}
return err
})
if err != nil { if err != nil {
return err return err
} }
if opts.noStart { var consumer compose.LogConsumer
return nil if !upOptions.Detach {
consumer = formatter.NewLogConsumer(ctx, os.Stdout, !upOptions.noColor, !upOptions.noPrefix)
} }
if opts.attachDependencies { attachTo := services
services = nil if upOptions.attachDependencies {
attachTo = project.ServiceNames()
} }
if opts.Detach { create := compose.CreateOptions{
return nil RemoveOrphans: createOptions.removeOrphans,
Recreate: createOptions.recreateStrategy(),
RecreateDependencies: createOptions.dependenciesRecreateStrategy(),
Inherit: !createOptions.noInherit,
Timeout: createOptions.GetTimeout(),
QuietPull: createOptions.quietPull,
} }
consumer := formatter.NewLogConsumer(ctx, os.Stdout, !opts.noColor, !opts.noPrefix) if upOptions.noStart {
printer := compose.NewLogPrinter(consumer) return backend.Create(ctx, project, create)
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
stopFunc := func() error {
ctx := context.Background()
return progress.Run(ctx, func(ctx context.Context) error {
go func() {
<-signalChan
backend.Kill(ctx, project, compose.KillOptions{}) // nolint:errcheck
}()
return backend.Stop(ctx, project, compose.StopOptions{})
})
} }
go func() {
<-signalChan
printer.Cancel()
fmt.Println("Gracefully stopping... (press Ctrl+C again to force)")
stopFunc() // nolint:errcheck
}()
var exitCode int return backend.Up(ctx, project, compose.UpOptions{
eg, ctx := errgroup.WithContext(ctx) Create: create,
eg.Go(func() error { Start: compose.StartOptions{
code, err := printer.Run(opts.cascadeStop, opts.exitCodeFrom, stopFunc) Attach: consumer,
exitCode = code AttachTo: attachTo,
return err ExitCodeFrom: upOptions.exitCodeFrom,
CascadeStop: upOptions.cascadeStop,
},
}) })
err = backend.Start(ctx, project, compose.StartOptions{
Attach: printer.HandleEvent,
Services: services,
})
if err != nil {
return err
}
err = eg.Wait()
if exitCode != 0 {
errMsg := ""
if err != nil {
errMsg = err.Error()
}
return cli.StatusError{StatusCode: exitCode, Status: errMsg}
}
return err
} }
func setServiceScale(project *types.Project, name string, replicas int) error { func setServiceScale(project *types.Project, name string, replicas int) error {
@ -338,43 +204,3 @@ func setServiceScale(project *types.Project, name string, replicas int) error {
} }
return fmt.Errorf("unknown service %q", name) return fmt.Errorf("unknown service %q", name)
} }
func setup(opts composeOptions, services []string) (*types.Project, error) {
project, err := opts.toProject(services)
if err != nil {
return nil, err
}
if opts.Build {
for i, service := range project.Services {
service.PullPolicy = types.PullPolicyBuild
project.Services[i] = service
}
}
if opts.noBuild {
for i, service := range project.Services {
service.Build = nil
project.Services[i] = service
}
}
if opts.EnvFile != "" {
var services types.Services
for _, s := range project.Services {
ef := opts.EnvFile
if ef != "" {
if !filepath.IsAbs(ef) {
ef = filepath.Join(project.WorkingDir, opts.EnvFile)
}
if s.Labels == nil {
s.Labels = make(map[string]string)
}
s.Labels[compose.EnvironmentFileLabel] = ef
services = append(services, s)
}
}
project.Services = services
}
return project, nil
}

Просмотреть файл

@ -23,12 +23,12 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/compose-spec/compose-go/types"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/errdefs" "github.com/docker/compose-cli/api/errdefs"
"github.com/docker/compose-cli/api/progress"
"github.com/compose-spec/compose-go/types"
) )
func (b *ecsAPIService) Build(ctx context.Context, project *types.Project, options compose.BuildOptions) error { func (b *ecsAPIService) Build(ctx context.Context, project *types.Project, options compose.BuildOptions) error {
@ -80,6 +80,12 @@ func (b *ecsAPIService) Copy(ctx context.Context, project *types.Project, option
} }
func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error { func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
return progress.Run(ctx, func(ctx context.Context) error {
return b.up(ctx, project, options)
})
}
func (b *ecsAPIService) up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
logrus.Debugf("deploying on AWS with region=%q", b.Region) logrus.Debugf("deploying on AWS with region=%q", b.Region)
err := b.aws.CheckRequirements(ctx, b.Region) err := b.aws.CheckRequirements(ctx, b.Region)
if err != nil { if err != nil {
@ -124,7 +130,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, options
return err return err
} }
} }
if options.Detach { if options.Start.Attach == nil {
return nil return nil
} }
signalChan := make(chan os.Signal, 1) signalChan := make(chan os.Signal, 1)

Просмотреть файл

@ -72,6 +72,12 @@ func NewComposeService() (compose.Service, error) {
// Up executes the equivalent to a `compose up` // Up executes the equivalent to a `compose up`
func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error { func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
return progress.Run(ctx, func(ctx context.Context) error {
return s.up(ctx, project)
})
}
func (s *composeService) up(ctx context.Context, project *types.Project) error {
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
eventName := "Convert to Helm charts" eventName := "Convert to Helm charts"

Просмотреть файл

@ -23,7 +23,6 @@ import (
"strings" "strings"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/errdefs"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/configfile"
@ -45,10 +44,6 @@ type composeService struct {
configFile *configfile.ConfigFile configFile *configfile.ConfigFile
} }
func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
return errdefs.ErrNotImplemented
}
func getCanonicalContainerName(c moby.Container) string { func getCanonicalContainerName(c moby.Container) string {
// Names return container canonical name /foo + link aliases /linked_by/foo // Names return container canonical name /foo + link aliases /linked_by/foo
for _, name := range c.Names { for _, name := range c.Names {

Просмотреть файл

@ -43,9 +43,15 @@ import (
"github.com/docker/compose-cli/utils" "github.com/docker/compose-cli/utils"
) )
func (s *composeService) Create(ctx context.Context, project *types.Project, opts compose.CreateOptions) error { func (s *composeService) Create(ctx context.Context, project *types.Project, options compose.CreateOptions) error {
if len(opts.Services) == 0 { return progress.Run(ctx, func(ctx context.Context) error {
opts.Services = project.ServiceNames() return s.create(ctx, project, options)
})
}
func (s *composeService) create(ctx context.Context, project *types.Project, options compose.CreateOptions) error {
if len(options.Services) == 0 {
options.Services = project.ServiceNames()
} }
var observedState Containers var observedState Containers
@ -56,7 +62,7 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt
containerState := NewContainersState(observedState) containerState := NewContainersState(observedState)
ctx = context.WithValue(ctx, ContainersKey{}, containerState) ctx = context.WithValue(ctx, ContainersKey{}, containerState)
err = s.ensureImagesExists(ctx, project, observedState, opts.QuietPull) err = s.ensureImagesExists(ctx, project, observedState, options.QuietPull)
if err != nil { if err != nil {
return err return err
} }
@ -83,7 +89,7 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt
} }
orphans := observedState.filter(isNotService(allServiceNames...)) orphans := observedState.filter(isNotService(allServiceNames...))
if len(orphans) > 0 { if len(orphans) > 0 {
if opts.RemoveOrphans { if options.RemoveOrphans {
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
err := s.removeContainers(ctx, w, orphans, nil) err := s.removeContainers(ctx, w, orphans, nil)
if err != nil { if err != nil {
@ -100,10 +106,10 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt
prepareServicesDependsOn(project) prepareServicesDependsOn(project)
return InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error { return InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
if utils.StringContains(opts.Services, service.Name) { if utils.StringContains(options.Services, service.Name) {
return s.ensureService(c, project, service, opts.Recreate, opts.Inherit, opts.Timeout) return s.ensureService(c, project, service, options.Recreate, options.Inherit, options.Timeout)
} }
return s.ensureService(c, project, service, opts.RecreateDependencies, opts.Inherit, opts.Timeout) return s.ensureService(c, project, service, options.RecreateDependencies, options.Inherit, options.Timeout)
}) })
} }

Просмотреть файл

@ -28,6 +28,12 @@ import (
) )
func (s *composeService) Kill(ctx context.Context, project *types.Project, options compose.KillOptions) error { func (s *composeService) Kill(ctx context.Context, project *types.Project, options compose.KillOptions) error {
return progress.Run(ctx, func(ctx context.Context) error {
return s.kill(ctx, project, options)
})
}
func (s *composeService) kill(ctx context.Context, project *types.Project, options compose.KillOptions) error {
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
var containers Containers var containers Containers

Просмотреть файл

@ -50,7 +50,7 @@ func TestKillAll(t *testing.T) {
api.EXPECT().ContainerKill(anyCancellableContext(), "456", "").Return(nil) api.EXPECT().ContainerKill(anyCancellableContext(), "456", "").Return(nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "789", "").Return(nil) api.EXPECT().ContainerKill(anyCancellableContext(), "789", "").Return(nil)
err := tested.Kill(ctx, &project, compose.KillOptions{}) err := tested.kill(ctx, &project, compose.KillOptions{})
assert.NilError(t, err) assert.NilError(t, err)
} }
@ -66,7 +66,7 @@ func TestKillSignal(t *testing.T) {
api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return([]apitypes.Container{testContainer("service1", "123")}, nil) api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return([]apitypes.Container{testContainer("service1", "123")}, nil)
api.EXPECT().ContainerKill(anyCancellableContext(), "123", "SIGTERM").Return(nil) api.EXPECT().ContainerKill(anyCancellableContext(), "123", "SIGTERM").Return(nil)
err := tested.Kill(ctx, &project, compose.KillOptions{Signal: "SIGTERM"}) err := tested.kill(ctx, &project, compose.KillOptions{Signal: "SIGTERM"})
assert.NilError(t, err) assert.NilError(t, err)
} }

Просмотреть файл

@ -19,45 +19,40 @@ package compose
import ( import (
"context" "context"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
"github.com/docker/compose-cli/utils"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types" moby "github.com/docker/docker/api/types"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
) )
func (s *composeService) Start(ctx context.Context, project *types.Project, options compose.StartOptions) error { func (s *composeService) Start(ctx context.Context, project *types.Project, options compose.StartOptions) error {
return progress.Run(ctx, func(ctx context.Context) error { return progress.Run(ctx, func(ctx context.Context) error {
return s.start(ctx, project, options) return s.start(ctx, project, options, nil)
}) })
} }
func (s *composeService) start(ctx context.Context, project *types.Project, options compose.StartOptions) error { func (s *composeService) start(ctx context.Context, project *types.Project, options compose.StartOptions, listener func(event compose.ContainerEvent)) error {
listener := options.Attach if len(options.AttachTo) == 0 {
if len(options.Services) == 0 { options.AttachTo = project.ServiceNames()
options.Services = project.ServiceNames()
} }
eg, ctx := errgroup.WithContext(ctx) eg, ctx := errgroup.WithContext(ctx)
if listener != nil { if listener != nil {
attached, err := s.attach(ctx, project, listener, options.Services) attached, err := s.attach(ctx, project, listener, options.AttachTo)
if err != nil { if err != nil {
return err return err
} }
eg.Go(func() error { eg.Go(func() error {
return s.watchContainers(project, options.Services, listener, attached) return s.watchContainers(project, options.AttachTo, listener, attached)
}) })
} }
err := InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error { err := InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
if utils.StringContains(options.Services, service.Name) { return s.startService(ctx, project, service)
return s.startService(ctx, project, service)
}
return nil
}) })
if err != nil { if err != nil {
return err return err

95
local/compose/up.go Normal file
Просмотреть файл

@ -0,0 +1,95 @@
/*
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 (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli"
"golang.org/x/sync/errgroup"
)
func (s *composeService) Up(ctx context.Context, project *types.Project, options compose.UpOptions) error {
err := progress.Run(ctx, func(ctx context.Context) error {
err := s.create(ctx, project, options.Create)
if err != nil {
return err
}
return s.start(ctx, project, options.Start, nil)
})
if err != nil {
return err
}
if options.Start.Attach == nil {
return err
}
printer := compose.NewLogPrinter(options.Start.Attach)
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
stopFunc := func() error {
ctx := context.Background()
return progress.Run(ctx, func(ctx context.Context) error {
go func() {
<-signalChan
s.Kill(ctx, project, compose.KillOptions{}) // nolint:errcheck
}()
return s.Stop(ctx, project, compose.StopOptions{})
})
}
go func() {
<-signalChan
printer.Cancel()
fmt.Println("Gracefully stopping... (press Ctrl+C again to force)")
stopFunc() // nolint:errcheck
}()
var exitCode int
eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error {
code, err := printer.Run(options.Start.CascadeStop, options.Start.ExitCodeFrom, stopFunc)
exitCode = code
return err
})
err = s.start(ctx, project, options.Start, printer.HandleEvent)
if err != nil {
return err
}
err = eg.Wait()
if exitCode != 0 {
errMsg := ""
if err != nil {
errMsg = err.Error()
}
return cli.StatusError{StatusCode: exitCode, Status: errMsg}
}
return err
}