| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- /*
- 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"
- "strconv"
- "strings"
- "github.com/docker/compose/v2/cmd/formatter"
- "github.com/compose-spec/compose-go/types"
- "github.com/spf13/cobra"
- "github.com/docker/compose/v2/pkg/api"
- "github.com/docker/compose/v2/pkg/utils"
- )
- // composeOptions hold options common to `up` and `run` to run compose project
- type composeOptions struct {
- *ProjectOptions
- }
- type upOptions struct {
- *composeOptions
- Detach bool
- noStart bool
- noDeps bool
- cascadeStop bool
- exitCodeFrom string
- scale []string
- noColor bool
- noPrefix bool
- attachDependencies bool
- attach []string
- timestamp bool
- wait bool
- }
- func (opts upOptions) apply(project *types.Project, services []string) error {
- if opts.noDeps {
- err := withSelectedServicesOnly(project, services)
- if err != nil {
- return err
- }
- }
- if opts.exitCodeFrom != "" {
- _, err := project.GetService(opts.exitCodeFrom)
- if err != nil {
- return err
- }
- }
- for _, scale := range opts.scale {
- split := strings.Split(scale, "=")
- if len(split) != 2 {
- return fmt.Errorf("invalid --scale option %q. Should be SERVICE=NUM", scale)
- }
- name := split[0]
- replicas, err := strconv.Atoi(split[1])
- if err != nil {
- return err
- }
- err = setServiceScale(project, name, uint64(replicas))
- if err != nil {
- return err
- }
- }
- return nil
- }
- func upCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
- up := upOptions{}
- create := createOptions{}
- upCmd := &cobra.Command{
- Use: "up [OPTIONS] [SERVICE...]",
- Short: "Create and start containers",
- PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
- create.timeChanged = cmd.Flags().Changed("timeout")
- return validateFlags(&up, &create)
- }),
- RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error {
- create.ignoreOrphans = utils.StringToBool(project.Environment["COMPOSE_IGNORE_ORPHANS"])
- if create.ignoreOrphans && create.removeOrphans {
- return fmt.Errorf("COMPOSE_IGNORE_ORPHANS and --remove-orphans cannot be combined")
- }
- return runUp(ctx, streams, backend, create, up, project, services)
- }),
- ValidArgsFunction: completeServiceNames(p),
- }
- flags := upCmd.Flags()
- 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.StringVar(&create.Pull, "pull", "missing", `Pull image before running ("always"|"missing"|"never")`)
- 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.timestamp, "timestamps", false, "Show timestamps.")
- 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.")
- flags.StringArrayVar(&up.attach, "attach", []string{}, "Attach to service output.")
- flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.")
- return upCmd
- }
- func validateFlags(up *upOptions, create *createOptions) error {
- if up.exitCodeFrom != "" {
- up.cascadeStop = true
- }
- if up.wait {
- if up.attachDependencies || up.cascadeStop || len(up.attach) > 0 {
- return fmt.Errorf("--wait cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
- }
- up.Detach = true
- }
- if create.Build && create.noBuild {
- return fmt.Errorf("--build and --no-build are incompatible")
- }
- if up.Detach && (up.attachDependencies || up.cascadeStop || len(up.attach) > 0) {
- return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
- }
- if create.forceRecreate && create.noRecreate {
- return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
- }
- if create.recreateDeps && create.noRecreate {
- return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible")
- }
- return nil
- }
- func runUp(ctx context.Context, streams api.Streams, backend api.Service, createOptions createOptions, upOptions upOptions, project *types.Project, services []string) error {
- if len(project.Services) == 0 {
- return fmt.Errorf("no service selected")
- }
- createOptions.Apply(project)
- err := upOptions.apply(project, services)
- if err != nil {
- return err
- }
- var consumer api.LogConsumer
- if !upOptions.Detach {
- consumer = formatter.NewLogConsumer(ctx, streams.Out(), streams.Err(), !upOptions.noColor, !upOptions.noPrefix, upOptions.timestamp)
- }
- attachTo := services
- if len(upOptions.attach) > 0 {
- attachTo = upOptions.attach
- }
- if upOptions.attachDependencies {
- attachTo = project.ServiceNames()
- }
- if len(attachTo) == 0 {
- attachTo = project.ServiceNames()
- }
- create := api.CreateOptions{
- Services: services,
- RemoveOrphans: createOptions.removeOrphans,
- IgnoreOrphans: createOptions.ignoreOrphans,
- Recreate: createOptions.recreateStrategy(),
- RecreateDependencies: createOptions.dependenciesRecreateStrategy(),
- Inherit: !createOptions.noInherit,
- Timeout: createOptions.GetTimeout(),
- QuietPull: createOptions.quietPull,
- }
- if upOptions.noStart {
- return backend.Create(ctx, project, create)
- }
- return backend.Up(ctx, project, api.UpOptions{
- Create: create,
- Start: api.StartOptions{
- Project: project,
- Attach: consumer,
- AttachTo: attachTo,
- ExitCodeFrom: upOptions.exitCodeFrom,
- CascadeStop: upOptions.cascadeStop,
- Wait: upOptions.wait,
- Services: services,
- },
- })
- }
- func setServiceScale(project *types.Project, name string, replicas uint64) error {
- for i, s := range project.Services {
- if s.Name != name {
- continue
- }
- service, err := project.GetService(name)
- if err != nil {
- return err
- }
- if service.Deploy == nil {
- service.Deploy = &types.DeployConfig{}
- }
- service.Deploy.Replicas = &replicas
- project.Services[i] = service
- return nil
- }
- return fmt.Errorf("unknown service %q", name)
- }
|