Merge pull request #1752 from ndeloof/up_backend

move up logic from CLI into local backend
This commit is contained in:
Nicolas De loof 2021-06-09 07:29:35 +02:00 committed by GitHub
commit 2edec5aa90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 323 additions and 304 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 Compose file to Helm charts"

View File

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

View File

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

View File

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

View File

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

View File

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