introduce run --cap-add to run maintenance commands using service image
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
ff3984e609
commit
c61b8aa5ac
@ -24,6 +24,7 @@ import (
|
|||||||
cgo "github.com/compose-spec/compose-go/cli"
|
cgo "github.com/compose-spec/compose-go/cli"
|
||||||
"github.com/compose-spec/compose-go/loader"
|
"github.com/compose-spec/compose-go/loader"
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/mattn/go-shellwords"
|
"github.com/mattn/go-shellwords"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@ -48,6 +49,8 @@ type runOptions struct {
|
|||||||
workdir string
|
workdir string
|
||||||
entrypoint string
|
entrypoint string
|
||||||
entrypointCmd []string
|
entrypointCmd []string
|
||||||
|
capAdd opts.ListOpts
|
||||||
|
capDrop opts.ListOpts
|
||||||
labels []string
|
labels []string
|
||||||
volumes []string
|
volumes []string
|
||||||
publish []string
|
publish []string
|
||||||
@ -59,20 +62,20 @@ type runOptions struct {
|
|||||||
quietPull bool
|
quietPull bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts runOptions) apply(project *types.Project) error {
|
func (options runOptions) apply(project *types.Project) error {
|
||||||
target, err := project.GetService(opts.Service)
|
target, err := project.GetService(options.Service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
target.Tty = !opts.noTty
|
target.Tty = !options.noTty
|
||||||
target.StdinOpen = opts.interactive
|
target.StdinOpen = options.interactive
|
||||||
if !opts.servicePorts {
|
if !options.servicePorts {
|
||||||
target.Ports = []types.ServicePortConfig{}
|
target.Ports = []types.ServicePortConfig{}
|
||||||
}
|
}
|
||||||
if len(opts.publish) > 0 {
|
if len(options.publish) > 0 {
|
||||||
target.Ports = []types.ServicePortConfig{}
|
target.Ports = []types.ServicePortConfig{}
|
||||||
for _, p := range opts.publish {
|
for _, p := range options.publish {
|
||||||
config, err := types.ParsePortConfig(p)
|
config, err := types.ParsePortConfig(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -80,8 +83,8 @@ func (opts runOptions) apply(project *types.Project) error {
|
|||||||
target.Ports = append(target.Ports, config...)
|
target.Ports = append(target.Ports, config...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(opts.volumes) > 0 {
|
if len(options.volumes) > 0 {
|
||||||
for _, v := range opts.volumes {
|
for _, v := range options.volumes {
|
||||||
volume, err := loader.ParseVolume(v)
|
volume, err := loader.ParseVolume(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -90,15 +93,15 @@ func (opts runOptions) apply(project *types.Project) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.noDeps {
|
if options.noDeps {
|
||||||
err := project.ForServices([]string{opts.Service}, types.IgnoreDependencies)
|
err := project.ForServices([]string{options.Service}, types.IgnoreDependencies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, s := range project.Services {
|
for i, s := range project.Services {
|
||||||
if s.Name == opts.Service {
|
if s.Name == options.Service {
|
||||||
project.Services[i] = target
|
project.Services[i] = target
|
||||||
break
|
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 {
|
func runCommand(p *ProjectOptions, streams api.Streams, backend api.Service) *cobra.Command {
|
||||||
opts := runOptions{
|
options := runOptions{
|
||||||
composeOptions: &composeOptions{
|
composeOptions: &composeOptions{
|
||||||
ProjectOptions: p,
|
ProjectOptions: p,
|
||||||
},
|
},
|
||||||
|
capAdd: opts.NewListOpts(nil),
|
||||||
|
capDrop: opts.NewListOpts(nil),
|
||||||
}
|
}
|
||||||
createOpts := createOptions{}
|
createOpts := createOptions{}
|
||||||
cmd := &cobra.Command{
|
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.",
|
Short: "Run a one-off command on a service.",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||||
opts.Service = args[0]
|
options.Service = args[0]
|
||||||
if len(args) > 1 {
|
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")
|
return fmt.Errorf("--service-ports and --publish are incompatible")
|
||||||
}
|
}
|
||||||
if cmd.Flags().Changed("entrypoint") {
|
if cmd.Flags().Changed("entrypoint") {
|
||||||
command, err := shellwords.Parse(opts.entrypoint)
|
command, err := shellwords.Parse(options.entrypoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
opts.entrypointCmd = command
|
options.entrypointCmd = command
|
||||||
}
|
}
|
||||||
if cmd.Flags().Changed("tty") {
|
if cmd.Flags().Changed("tty") {
|
||||||
if cmd.Flags().Changed("no-TTY") {
|
if cmd.Flags().Changed("no-TTY") {
|
||||||
return fmt.Errorf("--tty and --no-TTY can't be used together")
|
return fmt.Errorf("--tty and --no-TTY can't be used together")
|
||||||
} else {
|
} else {
|
||||||
opts.noTty = !opts.tty
|
options.noTty = !options.tty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}),
|
}),
|
||||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.ignoreOrphans = utils.StringToBool(project.Environment[ComposeIgnoreOrphans])
|
options.ignoreOrphans = utils.StringToBool(project.Environment[ComposeIgnoreOrphans])
|
||||||
return runRun(ctx, backend, project, opts, createOpts, streams)
|
return runRun(ctx, backend, project, options, createOpts, streams)
|
||||||
}),
|
}),
|
||||||
ValidArgsFunction: completeServiceNames(p),
|
ValidArgsFunction: completeServiceNames(p),
|
||||||
}
|
}
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.Detach, "detach", "d", false, "Run container in background and print container ID")
|
flags.BoolVarP(&options.Detach, "detach", "d", false, "Run container in background and print container ID")
|
||||||
flags.StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
|
flags.StringArrayVarP(&options.environment, "env", "e", []string{}, "Set environment variables")
|
||||||
flags.StringArrayVarP(&opts.labels, "label", "l", []string{}, "Add or override a label")
|
flags.StringArrayVarP(&options.labels, "label", "l", []string{}, "Add or override a label")
|
||||||
flags.BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
|
flags.BoolVar(&options.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.BoolVarP(&options.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.StringVar(&options.name, "name", "", "Assign a name to the container")
|
||||||
flags.StringVarP(&opts.user, "user", "u", "", "Run as specified username or uid")
|
flags.StringVarP(&options.user, "user", "u", "", "Run as specified username or uid")
|
||||||
flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
|
flags.StringVarP(&options.workdir, "workdir", "w", "", "Working directory inside the container")
|
||||||
flags.StringVar(&opts.entrypoint, "entrypoint", "", "Override the entrypoint of the image")
|
flags.StringVar(&options.entrypoint, "entrypoint", "", "Override the entrypoint of the image")
|
||||||
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services.")
|
flags.Var(&options.capAdd, "cap-add", "Add Linux capabilities")
|
||||||
flags.StringArrayVarP(&opts.volumes, "volume", "v", []string{}, "Bind mount a volume.")
|
flags.Var(&options.capDrop, "cap-drop", "Drop Linux capabilities")
|
||||||
flags.StringArrayVarP(&opts.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host.")
|
flags.BoolVar(&options.noDeps, "no-deps", false, "Don't start linked services.")
|
||||||
flags.BoolVar(&opts.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to.")
|
flags.StringArrayVarP(&options.volumes, "volume", "v", []string{}, "Bind mount a volume.")
|
||||||
flags.BoolVar(&opts.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
|
flags.StringArrayVarP(&options.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host.")
|
||||||
flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
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.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.")
|
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(&options.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.tty, "tty", "t", true, "Allocate a pseudo-TTY.")
|
||||||
cmd.Flags().MarkHidden("tty") //nolint:errcheck
|
cmd.Flags().MarkHidden("tty") //nolint:errcheck
|
||||||
|
|
||||||
flags.SetNormalizeFunc(normalizeRunFlags)
|
flags.SetNormalizeFunc(normalizeRunFlags)
|
||||||
@ -190,8 +197,8 @@ func normalizeRunFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
|||||||
return pflag.NormalizedName(name)
|
return pflag.NormalizedName(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRun(ctx context.Context, backend api.Service, project *types.Project, opts runOptions, createOpts createOptions, streams api.Streams) error {
|
func runRun(ctx context.Context, backend api.Service, project *types.Project, options runOptions, createOpts createOptions, streams api.Streams) error {
|
||||||
err := opts.apply(project)
|
err := options.apply(project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
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())
|
}, streams.Err())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
labels := types.Labels{}
|
labels := types.Labels{}
|
||||||
for _, s := range opts.labels {
|
for _, s := range options.labels {
|
||||||
parts := strings.SplitN(s, "=", 2)
|
parts := strings.SplitN(s, "=", 2)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return fmt.Errorf("label must be set as KEY=VALUE")
|
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
|
// start container and attach to container streams
|
||||||
runOpts := api.RunOptions{
|
runOpts := api.RunOptions{
|
||||||
Name: opts.name,
|
Name: options.name,
|
||||||
Service: opts.Service,
|
Service: options.Service,
|
||||||
Command: opts.Command,
|
Command: options.Command,
|
||||||
Detach: opts.Detach,
|
Detach: options.Detach,
|
||||||
AutoRemove: opts.Remove,
|
AutoRemove: options.Remove,
|
||||||
Tty: !opts.noTty,
|
Tty: !options.noTty,
|
||||||
Interactive: opts.interactive,
|
Interactive: options.interactive,
|
||||||
WorkingDir: opts.workdir,
|
WorkingDir: options.workdir,
|
||||||
User: opts.user,
|
User: options.user,
|
||||||
Environment: opts.environment,
|
CapAdd: options.capAdd.GetAll(),
|
||||||
Entrypoint: opts.entrypointCmd,
|
CapDrop: options.capDrop.GetAll(),
|
||||||
|
Environment: options.environment,
|
||||||
|
Entrypoint: options.entrypointCmd,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
UseNetworkAliases: opts.useAliases,
|
UseNetworkAliases: options.useAliases,
|
||||||
NoDeps: opts.noDeps,
|
NoDeps: options.noDeps,
|
||||||
Index: 0,
|
Index: 0,
|
||||||
QuietPull: opts.quietPull,
|
QuietPull: options.quietPull,
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, service := range project.Services {
|
for i, service := range project.Services {
|
||||||
if service.Name == opts.Service {
|
if service.Name == options.Service {
|
||||||
service.StdinOpen = opts.interactive
|
service.StdinOpen = options.interactive
|
||||||
project.Services[i] = service
|
project.Services[i] = service
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@ Run a one-off command on a service.
|
|||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:----------------------|:--------------|:--------|:----------------------------------------------------------------------------------|
|
|:----------------------|:--------------|:--------|:----------------------------------------------------------------------------------|
|
||||||
| `--build` | | | Build image before starting container. |
|
| `--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 |
|
| `-d`, `--detach` | | | Run container in background and print container ID |
|
||||||
| `--dry-run` | | | Execute command in dry run mode |
|
| `--dry-run` | | | Execute command in dry run mode |
|
||||||
| `--entrypoint` | `string` | | Override the entrypoint of the image |
|
| `--entrypoint` | `string` | | Override the entrypoint of the image |
|
||||||
|
@ -68,6 +68,24 @@ options:
|
|||||||
experimentalcli: false
|
experimentalcli: false
|
||||||
kubernetes: false
|
kubernetes: false
|
||||||
swarm: 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
|
- option: detach
|
||||||
shorthand: d
|
shorthand: d
|
||||||
value_type: bool
|
value_type: bool
|
||||||
|
@ -305,6 +305,8 @@ type RunOptions struct {
|
|||||||
WorkingDir string
|
WorkingDir string
|
||||||
User string
|
User string
|
||||||
Environment []string
|
Environment []string
|
||||||
|
CapAdd []string
|
||||||
|
CapDrop []string
|
||||||
Labels types.Labels
|
Labels types.Labels
|
||||||
Privileged bool
|
Privileged bool
|
||||||
UseNetworkAliases bool
|
UseNetworkAliases bool
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
cmd "github.com/docker/cli/cli/command/container"
|
cmd "github.com/docker/cli/cli/command/container"
|
||||||
"github.com/docker/compose/v2/pkg/api"
|
"github.com/docker/compose/v2/pkg/api"
|
||||||
|
"github.com/docker/compose/v2/pkg/utils"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -117,6 +118,14 @@ func applyRunOptions(project *types.Project, service *types.ServiceConfig, opts
|
|||||||
if len(opts.User) > 0 {
|
if len(opts.User) > 0 {
|
||||||
service.User = opts.User
|
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 {
|
if len(opts.WorkingDir) > 0 {
|
||||||
service.WorkingDir = opts.WorkingDir
|
service.WorkingDir = opts.WorkingDir
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user