浏览代码

introduce run --cap-add to run maintenance commands using service image

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 2 年之前
父节点
当前提交
c61b8aa5ac
共有 5 个文件被更改,包括 99 次插入59 次删除
  1. 68 59
      cmd/compose/run.go
  2. 2 0
      docs/reference/compose_run.md
  3. 18 0
      docs/reference/docker_compose_run.yaml
  4. 2 0
      pkg/api/api.go
  5. 9 0
      pkg/compose/run.go

+ 68 - 59
cmd/compose/run.go

@@ -24,6 +24,7 @@ import (
 	cgo "github.com/compose-spec/compose-go/cli"
 	"github.com/compose-spec/compose-go/loader"
 	"github.com/compose-spec/compose-go/types"
+	"github.com/docker/cli/opts"
 	"github.com/mattn/go-shellwords"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
@@ -48,6 +49,8 @@ type runOptions struct {
 	workdir       string
 	entrypoint    string
 	entrypointCmd []string
+	capAdd        opts.ListOpts
+	capDrop       opts.ListOpts
 	labels        []string
 	volumes       []string
 	publish       []string
@@ -59,20 +62,20 @@ type runOptions struct {
 	quietPull     bool
 }
 
-func (opts runOptions) apply(project *types.Project) error {
-	target, err := project.GetService(opts.Service)
+func (options runOptions) apply(project *types.Project) error {
+	target, err := project.GetService(options.Service)
 	if err != nil {
 		return err
 	}
 
-	target.Tty = !opts.noTty
-	target.StdinOpen = opts.interactive
-	if !opts.servicePorts {
+	target.Tty = !options.noTty
+	target.StdinOpen = options.interactive
+	if !options.servicePorts {
 		target.Ports = []types.ServicePortConfig{}
 	}
-	if len(opts.publish) > 0 {
+	if len(options.publish) > 0 {
 		target.Ports = []types.ServicePortConfig{}
-		for _, p := range opts.publish {
+		for _, p := range options.publish {
 			config, err := types.ParsePortConfig(p)
 			if err != nil {
 				return err
@@ -80,8 +83,8 @@ func (opts runOptions) apply(project *types.Project) error {
 			target.Ports = append(target.Ports, config...)
 		}
 	}
-	if len(opts.volumes) > 0 {
-		for _, v := range opts.volumes {
+	if len(options.volumes) > 0 {
+		for _, v := range options.volumes {
 			volume, err := loader.ParseVolume(v)
 			if err != nil {
 				return err
@@ -90,15 +93,15 @@ func (opts runOptions) apply(project *types.Project) error {
 		}
 	}
 
-	if opts.noDeps {
-		err := project.ForServices([]string{opts.Service}, types.IgnoreDependencies)
+	if options.noDeps {
+		err := project.ForServices([]string{options.Service}, types.IgnoreDependencies)
 		if err != nil {
 			return err
 		}
 	}
 
 	for i, s := range project.Services {
-		if s.Name == opts.Service {
+		if s.Name == options.Service {
 			project.Services[i] = target
 			break
 		}
@@ -107,10 +110,12 @@ func (opts runOptions) apply(project *types.Project) error {
 }
 
 func runCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
-	opts := runOptions{
+	options := runOptions{
 		composeOptions: &composeOptions{
 			ProjectOptions: p,
 		},
+		capAdd:  opts.NewListOpts(nil),
+		capDrop: opts.NewListOpts(nil),
 	}
 	createOpts := createOptions{}
 	cmd := &cobra.Command{
@@ -118,61 +123,63 @@ func runCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *co
 		Short: "Run a one-off command on a service.",
 		Args:  cobra.MinimumNArgs(1),
 		PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
-			opts.Service = args[0]
+			options.Service = args[0]
 			if len(args) > 1 {
-				opts.Command = args[1:]
+				options.Command = args[1:]
 			}
-			if len(opts.publish) > 0 && opts.servicePorts {
+			if len(options.publish) > 0 && options.servicePorts {
 				return fmt.Errorf("--service-ports and --publish are incompatible")
 			}
 			if cmd.Flags().Changed("entrypoint") {
-				command, err := shellwords.Parse(opts.entrypoint)
+				command, err := shellwords.Parse(options.entrypoint)
 				if err != nil {
 					return err
 				}
-				opts.entrypointCmd = command
+				options.entrypointCmd = command
 			}
 			if cmd.Flags().Changed("tty") {
 				if cmd.Flags().Changed("no-TTY") {
 					return fmt.Errorf("--tty and --no-TTY can't be used together")
 				} else {
-					opts.noTty = !opts.tty
+					options.noTty = !options.tty
 				}
 			}
 			return nil
 		}),
 		RunE: Adapt(func(ctx context.Context, args []string) error {
-			project, err := p.ToProject([]string{opts.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
+			project, err := p.ToProject([]string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
 			if err != nil {
 				return err
 			}
 
-			opts.ignoreOrphans = utils.StringToBool(project.Environment[ComposeIgnoreOrphans])
-			return runRun(ctx, backend, project, opts, createOpts, streams)
+			options.ignoreOrphans = utils.StringToBool(project.Environment[ComposeIgnoreOrphans])
+			return runRun(ctx, backend, project, options, createOpts, streams)
 		}),
 		ValidArgsFunction: completeServiceNames(p),
 	}
 	flags := cmd.Flags()
-	flags.BoolVarP(&opts.Detach, "detach", "d", false, "Run container in background and print container ID")
-	flags.StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
-	flags.StringArrayVarP(&opts.labels, "label", "l", []string{}, "Add or override a label")
-	flags.BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
-	flags.BoolVarP(&opts.noTty, "no-TTY", "T", !streams.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
-	flags.StringVar(&opts.name, "name", "", "Assign a name to the container")
-	flags.StringVarP(&opts.user, "user", "u", "", "Run as specified username or uid")
-	flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
-	flags.StringVar(&opts.entrypoint, "entrypoint", "", "Override the entrypoint of the image")
-	flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services.")
-	flags.StringArrayVarP(&opts.volumes, "volume", "v", []string{}, "Bind mount a volume.")
-	flags.StringArrayVarP(&opts.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host.")
-	flags.BoolVar(&opts.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to.")
-	flags.BoolVar(&opts.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
-	flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information.")
+	flags.BoolVarP(&options.Detach, "detach", "d", false, "Run container in background and print container ID")
+	flags.StringArrayVarP(&options.environment, "env", "e", []string{}, "Set environment variables")
+	flags.StringArrayVarP(&options.labels, "label", "l", []string{}, "Add or override a label")
+	flags.BoolVar(&options.Remove, "rm", false, "Automatically remove the container when it exits")
+	flags.BoolVarP(&options.noTty, "no-TTY", "T", !streams.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
+	flags.StringVar(&options.name, "name", "", "Assign a name to the container")
+	flags.StringVarP(&options.user, "user", "u", "", "Run as specified username or uid")
+	flags.StringVarP(&options.workdir, "workdir", "w", "", "Working directory inside the container")
+	flags.StringVar(&options.entrypoint, "entrypoint", "", "Override the entrypoint of the image")
+	flags.Var(&options.capAdd, "cap-add", "Add Linux capabilities")
+	flags.Var(&options.capDrop, "cap-drop", "Drop Linux capabilities")
+	flags.BoolVar(&options.noDeps, "no-deps", false, "Don't start linked services.")
+	flags.StringArrayVarP(&options.volumes, "volume", "v", []string{}, "Bind mount a volume.")
+	flags.StringArrayVarP(&options.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host.")
+	flags.BoolVar(&options.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to.")
+	flags.BoolVar(&options.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
+	flags.BoolVar(&options.quietPull, "quiet-pull", false, "Pull without printing progress information.")
 	flags.BoolVar(&createOpts.Build, "build", false, "Build image before starting container.")
 	flags.BoolVar(&createOpts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
 
-	cmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
-	cmd.Flags().BoolVarP(&opts.tty, "tty", "t", true, "Allocate a pseudo-TTY.")
+	cmd.Flags().BoolVarP(&options.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
+	cmd.Flags().BoolVarP(&options.tty, "tty", "t", true, "Allocate a pseudo-TTY.")
 	cmd.Flags().MarkHidden("tty") //nolint:errcheck
 
 	flags.SetNormalizeFunc(normalizeRunFlags)
@@ -190,8 +197,8 @@ func normalizeRunFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
 	return pflag.NormalizedName(name)
 }
 
-func runRun(ctx context.Context, backend api.Service, project *types.Project, opts runOptions, createOpts createOptions, streams api.Streams) error {
-	err := opts.apply(project)
+func runRun(ctx context.Context, backend api.Service, project *types.Project, options runOptions, createOpts createOptions, streams api.Streams) error {
+	err := options.apply(project)
 	if err != nil {
 		return err
 	}
@@ -202,14 +209,14 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
 	}
 
 	err = progress.Run(ctx, func(ctx context.Context) error {
-		return startDependencies(ctx, backend, *project, opts.Service, opts.ignoreOrphans)
+		return startDependencies(ctx, backend, *project, options.Service, options.ignoreOrphans)
 	}, streams.Err())
 	if err != nil {
 		return err
 	}
 
 	labels := types.Labels{}
-	for _, s := range opts.labels {
+	for _, s := range options.labels {
 		parts := strings.SplitN(s, "=", 2)
 		if len(parts) != 2 {
 			return fmt.Errorf("label must be set as KEY=VALUE")
@@ -219,27 +226,29 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
 
 	// start container and attach to container streams
 	runOpts := api.RunOptions{
-		Name:              opts.name,
-		Service:           opts.Service,
-		Command:           opts.Command,
-		Detach:            opts.Detach,
-		AutoRemove:        opts.Remove,
-		Tty:               !opts.noTty,
-		Interactive:       opts.interactive,
-		WorkingDir:        opts.workdir,
-		User:              opts.user,
-		Environment:       opts.environment,
-		Entrypoint:        opts.entrypointCmd,
+		Name:              options.name,
+		Service:           options.Service,
+		Command:           options.Command,
+		Detach:            options.Detach,
+		AutoRemove:        options.Remove,
+		Tty:               !options.noTty,
+		Interactive:       options.interactive,
+		WorkingDir:        options.workdir,
+		User:              options.user,
+		CapAdd:            options.capAdd.GetAll(),
+		CapDrop:           options.capDrop.GetAll(),
+		Environment:       options.environment,
+		Entrypoint:        options.entrypointCmd,
 		Labels:            labels,
-		UseNetworkAliases: opts.useAliases,
-		NoDeps:            opts.noDeps,
+		UseNetworkAliases: options.useAliases,
+		NoDeps:            options.noDeps,
 		Index:             0,
-		QuietPull:         opts.quietPull,
+		QuietPull:         options.quietPull,
 	}
 
 	for i, service := range project.Services {
-		if service.Name == opts.Service {
-			service.StdinOpen = opts.interactive
+		if service.Name == options.Service {
+			service.StdinOpen = options.interactive
 			project.Services[i] = service
 		}
 	}

+ 2 - 0
docs/reference/compose_run.md

@@ -8,6 +8,8 @@ Run a one-off command on a service.
 | Name                  | Type          | Default | Description                                                                       |
 |:----------------------|:--------------|:--------|:----------------------------------------------------------------------------------|
 | `--build`             |               |         | Build image before starting container.                                            |
+| `--cap-add`           | `list`        |         | Add Linux capabilities                                                            |
+| `--cap-drop`          | `list`        |         | Drop Linux capabilities                                                           |
 | `-d`, `--detach`      |               |         | Run container in background and print container ID                                |
 | `--dry-run`           |               |         | Execute command in dry run mode                                                   |
 | `--entrypoint`        | `string`      |         | Override the entrypoint of the image                                              |

+ 18 - 0
docs/reference/docker_compose_run.yaml

@@ -68,6 +68,24 @@ options:
       experimentalcli: false
       kubernetes: false
       swarm: false
+    - option: cap-add
+      value_type: list
+      description: Add Linux capabilities
+      deprecated: false
+      hidden: false
+      experimental: false
+      experimentalcli: false
+      kubernetes: false
+      swarm: false
+    - option: cap-drop
+      value_type: list
+      description: Drop Linux capabilities
+      deprecated: false
+      hidden: false
+      experimental: false
+      experimentalcli: false
+      kubernetes: false
+      swarm: false
     - option: detach
       shorthand: d
       value_type: bool

+ 2 - 0
pkg/api/api.go

@@ -305,6 +305,8 @@ type RunOptions struct {
 	WorkingDir        string
 	User              string
 	Environment       []string
+	CapAdd            []string
+	CapDrop           []string
 	Labels            types.Labels
 	Privileged        bool
 	UseNetworkAliases bool

+ 9 - 0
pkg/compose/run.go

@@ -26,6 +26,7 @@ import (
 	"github.com/docker/cli/cli"
 	cmd "github.com/docker/cli/cli/command/container"
 	"github.com/docker/compose/v2/pkg/api"
+	"github.com/docker/compose/v2/pkg/utils"
 	"github.com/docker/docker/pkg/stringid"
 )
 
@@ -117,6 +118,14 @@ func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts
 	if len(opts.User) > 0 {
 		service.User = opts.User
 	}
+	if len(opts.CapAdd) > 0 {
+		service.CapAdd = append(service.CapAdd, opts.CapAdd...)
+		service.CapDrop = utils.Remove(service.CapDrop, opts.CapAdd...)
+	}
+	if len(opts.CapDrop) > 0 {
+		service.CapDrop = append(service.CapDrop, opts.CapDrop...)
+		service.CapAdd = utils.Remove(service.CapAdd, opts.CapDrop...)
+	}
 	if len(opts.WorkingDir) > 0 {
 		service.WorkingDir = opts.WorkingDir
 	}