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 {
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)
if err := autocreateFileshares(ctx, project); err != nil {

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

@ -112,10 +112,14 @@ type CreateOptions struct {
// StartOptions group options of the Start API
type StartOptions struct {
// Attach will attach to service containers and send container logs and events
Attach ContainerEventListener
// Services passed in the command line to be started
Services []string
// Attach to container and forward logs if not nil
Attach LogConsumer
// AttachTo set the services to attach to
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
@ -136,10 +140,8 @@ type StopOptions struct {
// UpOptions group options of the Up API
type UpOptions struct {
// Detach will create services and return immediately
Detach bool
// QuietPull makes the pulling process quiet
QuietPull bool
Create CreateOptions
Start StartOptions
}
// DownOptions group options of the Down API

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

@ -21,6 +21,7 @@ import (
"fmt"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
@ -90,14 +91,43 @@ type projectOptions struct {
// ProjectFunc does stuff within a types.Project
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
func (o *projectOptions) WithServices(services []string, fn ProjectFunc) func(cmd *cobra.Command, args []string) error {
return Adapt(func(ctx context.Context, strings []string) error {
project, err := o.toProject(services)
func (o *projectOptions) WithProject(fn ProjectFunc) func(cmd *cobra.Command, args []string) error {
return o.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
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 {
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(
upCommand(&opts, contextType, backend),
upCommand(&opts, backend),
downCommand(&opts, backend),
startCommand(&opts, backend),
restartCommand(&opts, backend),

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

@ -19,22 +19,29 @@ package compose
import (
"context"
"fmt"
"time"
"github.com/compose-spec/compose-go/types"
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/compose"
)
type createOptions struct {
*composeOptions
Build bool
noBuild bool
removeOrphans bool
forceRecreate bool
noRecreate bool
recreateDeps bool
noInherit bool
timeChanged bool
timeout int
quietPull bool
}
func createCommand(p *projectOptions, backend compose.Service) *cobra.Command {
opts := createOptions{
composeOptions: &composeOptions{},
}
opts := createOptions{}
cmd := &cobra.Command{
Use: "create [SERVICE...]",
Short: "Creates containers for a service.",
@ -47,17 +54,15 @@ func createCommand(p *projectOptions, backend compose.Service) *cobra.Command {
}
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
return runCreateStart(ctx, backend, upOptions{
composeOptions: &composeOptions{
projectOptions: p,
Build: opts.Build,
noBuild: opts.noBuild,
},
noStart: true,
forceRecreate: opts.forceRecreate,
noRecreate: opts.noRecreate,
}, args)
RunE: p.WithProject(func(ctx context.Context, project *types.Project) error {
return backend.Create(ctx, project, compose.CreateOptions{
RemoveOrphans: opts.removeOrphans,
Recreate: opts.recreateStrategy(),
RecreateDependencies: opts.dependenciesRecreateStrategy(),
Inherit: !opts.noInherit,
Timeout: opts.GetTimeout(),
QuietPull: false,
})
}),
}
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.")
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 (
"context"
"os"
"github.com/compose-spec/compose-go/types"
"github.com/spf13/cobra"
@ -31,7 +30,7 @@ func killCommand(p *projectOptions, backend compose.Service) *cobra.Command {
cmd := &cobra.Command{
Use: "kill [options] [SERVICE...]",
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)
}),
}

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

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

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

@ -20,40 +20,25 @@ import (
"context"
"fmt"
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"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/context/store"
"github.com/docker/compose-cli/api/progress"
"github.com/docker/compose-cli/cli/formatter"
"github.com/docker/compose-cli/utils"
"github.com/spf13/cobra"
)
// composeOptions hold options common to `up` and `run` to run compose project
type composeOptions struct {
*projectOptions
Build bool
noBuild bool
}
type upOptions struct {
*composeOptions
Detach bool
Environment []string
removeOrphans bool
forceRecreate bool
noRecreate bool
recreateDeps bool
noStart bool
noDeps bool
cascadeStop bool
@ -61,39 +46,7 @@ type upOptions struct {
scale []string
noColor bool
noPrefix bool
timeChanged bool
timeout int
noInherit 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 {
@ -136,192 +89,105 @@ func (opts upOptions) apply(project *types.Project, services []string) error {
return nil
}
func upCommand(p *projectOptions, contextType string, backend compose.Service) *cobra.Command {
opts := upOptions{
composeOptions: &composeOptions{
projectOptions: p,
},
}
func upCommand(p *projectOptions, backend compose.Service) *cobra.Command {
up := upOptions{}
create := createOptions{}
upCmd := &cobra.Command{
Use: "up [SERVICE...]",
Short: "Create and start containers",
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 {
if opts.exitCodeFrom != "" {
opts.cascadeStop = true
if up.exitCodeFrom != "" {
up.cascadeStop = true
}
if opts.Build && opts.noBuild {
if create.Build && create.noBuild {
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")
}
if opts.forceRecreate && opts.noRecreate {
if create.forceRecreate && create.noRecreate {
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 nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
switch contextType {
case store.LocalContextType, store.DefaultContextType, store.EcsLocalSimulationContextType:
return runCreateStart(ctx, backend, opts, args)
default:
return runUp(ctx, backend, opts, args)
}
RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
return runUp(ctx, backend, create, up, project, services)
}),
}
flags := upCmd.Flags()
flags.StringArrayVarP(&opts.Environment, "environment", "e", []string{}, "Environment variables")
flags.BoolVarP(&opts.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
flags.BoolVar(&opts.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(&opts.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.BoolVar(&opts.noColor, "no-color", false, "Produce monochrome output.")
flags.BoolVar(&opts.noPrefix, "no-log-prefix", false, "Don't print prefix in logs.")
switch contextType {
case store.LocalContextType, store.DefaultContextType, store.EcsLocalSimulationContextType:
flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
flags.BoolVar(&opts.noStart, "no-start", false, "Don't start the services after creating them.")
flags.BoolVar(&opts.cascadeStop, "abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d")
flags.StringVar(&opts.exitCodeFrom, "exit-code-from", "", "Return the exit code of the selected service container. Implies --abort-on-container-exit")
flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Use this timeout in seconds for container shutdown when attached or when containers are already running.")
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services.")
flags.BoolVar(&opts.recreateDeps, "always-recreate-deps", false, "Recreate dependent containers. Incompatible with --no-recreate.")
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.")
}
flags.StringArrayVarP(&up.Environment, "environment", "e", []string{}, "Environment variables")
flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
flags.BoolVar(&create.Build, "build", false, "Build images before starting containers.")
flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's missing.")
flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
flags.StringArrayVar(&up.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
flags.BoolVar(&up.noColor, "no-color", false, "Produce monochrome output.")
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.")
flags.BoolVar(&create.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
flags.BoolVar(&up.noStart, "no-start", false, "Don't start the services after creating them.")
flags.BoolVar(&up.cascadeStop, "abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d")
flags.StringVar(&up.exitCodeFrom, "exit-code-from", "", "Return the exit code of the selected service container. Implies --abort-on-container-exit")
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(&up.noDeps, "no-deps", false, "Don't start linked services.")
flags.BoolVar(&create.recreateDeps, "always-recreate-deps", false, "Recreate dependent containers. Incompatible with --no-recreate.")
flags.BoolVarP(&create.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers.")
flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Attach to dependent containers.")
flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information.")
return upCmd
}
func runUp(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
}
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
}
func runUp(ctx context.Context, backend compose.Service, createOptions createOptions, upOptions upOptions, project *types.Project, services []string) error {
if len(project.Services) == 0 {
return fmt.Errorf("no service selected")
}
err = progress.Run(ctx, func(ctx context.Context) error {
err := backend.Create(ctx, project, compose.CreateOptions{
Services: 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
})
createOptions.Apply(project)
err := upOptions.apply(project, services)
if err != nil {
return err
}
if opts.noStart {
return nil
var consumer compose.LogConsumer
if !upOptions.Detach {
consumer = formatter.NewLogConsumer(ctx, os.Stdout, !upOptions.noColor, !upOptions.noPrefix)
}
if opts.attachDependencies {
services = nil
attachTo := services
if upOptions.attachDependencies {
attachTo = project.ServiceNames()
}
if opts.Detach {
return nil
create := compose.CreateOptions{
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)
printer := compose.NewLogPrinter(consumer)
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{})
})
if upOptions.noStart {
return backend.Create(ctx, project, create)
}
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(opts.cascadeStop, opts.exitCodeFrom, stopFunc)
exitCode = code
return err
return backend.Up(ctx, project, compose.UpOptions{
Create: create,
Start: compose.StartOptions{
Attach: consumer,
AttachTo: attachTo,
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 {
@ -338,43 +204,3 @@ func setServiceScale(project *types.Project, name string, replicas int) error {
}
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"
"syscall"
"github.com/compose-spec/compose-go/types"
"github.com/sirupsen/logrus"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/errdefs"
"github.com/compose-spec/compose-go/types"
"github.com/docker/compose-cli/api/progress"
)
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 {
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)
err := b.aws.CheckRequirements(ctx, b.Region)
if err != nil {
@ -124,7 +130,7 @@ func (b *ecsAPIService) Up(ctx context.Context, project *types.Project, options
return err
}
}
if options.Detach {
if options.Start.Attach == nil {
return nil
}
signalChan := make(chan os.Signal, 1)

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

@ -72,6 +72,12 @@ func NewComposeService() (compose.Service, error) {
// Up executes the equivalent to a `compose up`
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)
eventName := "Convert to Helm charts"

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

@ -23,7 +23,6 @@ import (
"strings"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/errdefs"
"github.com/compose-spec/compose-go/types"
"github.com/docker/cli/cli/config/configfile"
@ -45,10 +44,6 @@ type composeService struct {
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 {
// Names return container canonical name /foo + link aliases /linked_by/foo
for _, name := range c.Names {

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

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

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

@ -50,7 +50,7 @@ func TestKillAll(t *testing.T) {
api.EXPECT().ContainerKill(anyCancellableContext(), "456", "").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)
}
@ -66,7 +66,7 @@ func TestKillSignal(t *testing.T) {
api.EXPECT().ContainerList(ctx, projectFilterListOpt()).Return([]apitypes.Container{testContainer("service1", "123")}, 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)
}

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

@ -19,45 +19,40 @@ package compose
import (
"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"
moby "github.com/docker/docker/api/types"
"github.com/pkg/errors"
"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 {
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 {
listener := options.Attach
if len(options.Services) == 0 {
options.Services = project.ServiceNames()
func (s *composeService) start(ctx context.Context, project *types.Project, options compose.StartOptions, listener func(event compose.ContainerEvent)) error {
if len(options.AttachTo) == 0 {
options.AttachTo = project.ServiceNames()
}
eg, ctx := errgroup.WithContext(ctx)
if listener != nil {
attached, err := s.attach(ctx, project, listener, options.Services)
attached, err := s.attach(ctx, project, listener, options.AttachTo)
if err != nil {
return err
}
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 {
if utils.StringContains(options.Services, service.Name) {
return s.startService(ctx, project, service)
}
return nil
return s.startService(ctx, project, service)
})
if err != nil {
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
}