From d08255c4ffa6906dbe0ec6e32a6e62771f5fcea8 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Thu, 4 Mar 2021 14:45:04 +0100 Subject: [PATCH] implement -v, -p, --service-ports and --use-aliases on compose run Signed-off-by: Nicolas De Loof --- api/compose/api.go | 29 +++--- cli/cmd/compose/run.go | 126 ++++++++++++++++++-------- local/compose/convergence.go | 25 +++-- local/compose/run.go | 2 +- local/e2e/compose/compose_run_test.go | 17 ++++ 5 files changed, 139 insertions(+), 60 deletions(-) diff --git a/api/compose/api.go b/api/compose/api.go index 02d2ef31d..f9d4ee930 100644 --- a/api/compose/api.go +++ b/api/compose/api.go @@ -151,20 +151,21 @@ type RemoveOptions struct { // RunOptions options to execute compose run type RunOptions struct { - Name string - Service string - Command []string - Entrypoint []string - Detach bool - AutoRemove bool - Writer io.Writer - Reader io.Reader - Tty bool - WorkingDir string - User string - Environment []string - Labels types.Labels - Privileged bool + Name string + Service string + Command []string + Entrypoint []string + Detach bool + AutoRemove bool + Writer io.Writer + Reader io.Reader + Tty bool + WorkingDir string + User string + Environment []string + Labels types.Labels + Privileged bool + UseNetworkAliases bool // used by exec Index int } diff --git a/cli/cmd/compose/run.go b/cli/cmd/compose/run.go index 4eacd0708..8729316f6 100644 --- a/cli/cmd/compose/run.go +++ b/cli/cmd/compose/run.go @@ -22,6 +22,7 @@ import ( "os" "strings" + "github.com/compose-spec/compose-go/loader" "github.com/compose-spec/compose-go/types" "github.com/mattn/go-shellwords" "github.com/spf13/cobra" @@ -34,18 +35,69 @@ import ( type runOptions struct { *composeOptions - Service string - Command []string - environment []string - Detach bool - Remove bool - noTty bool - user string - workdir string - entrypoint string - labels []string - name string - noDeps bool + Service string + Command []string + environment []string + Detach bool + Remove bool + noTty bool + user string + workdir string + entrypoint string + labels []string + volumes []string + publish []string + useAliases bool + servicePorts bool + name string + noDeps bool +} + +func (opts runOptions) apply(project *types.Project) error { + target, err := project.GetService(opts.Service) + if err != nil { + return err + } + if !opts.servicePorts { + target.Ports = []types.ServicePortConfig{} + } + if len(opts.publish) > 0 { + target.Ports = []types.ServicePortConfig{} + for _, p := range opts.publish { + config, err := types.ParsePortConfig(p) + if err != nil { + return err + } + target.Ports = append(target.Ports, config...) + } + } + if len(opts.volumes) > 0 { + target.Volumes = []types.ServiceVolumeConfig{} + for _, v := range opts.volumes { + volume, err := loader.ParseVolume(v) + if err != nil { + return err + } + target.Volumes = append(target.Volumes, volume) + } + } + + if opts.noDeps { + for _, s := range project.Services { + if s.Name != opts.Service { + project.DisabledServices = append(project.DisabledServices, s) + } + } + project.Services = types.Services{target} + } + + for i, s := range project.Services { + if s.Name == opts.Service { + project.Services[i] = target + break + } + } + return nil } func runCommand(p *projectOptions) *cobra.Command { @@ -63,6 +115,9 @@ func runCommand(p *projectOptions) *cobra.Command { opts.Command = args[1:] } opts.Service = args[0] + if len(opts.publish) > 0 && opts.servicePorts { + return fmt.Errorf("--service-ports and --publish are incompatible") + } return runRun(cmd.Context(), opts) }, } @@ -77,6 +132,10 @@ func runCommand(p *projectOptions) *cobra.Command { 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, "volumes", "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.SetInterspersed(false) return cmd @@ -88,17 +147,9 @@ func runRun(ctx context.Context, opts runOptions) error { return err } - if opts.noDeps { - enabled, err := project.GetService(opts.Service) - if err != nil { - return err - } - for _, s := range project.Services { - if s.Name != opts.Service { - project.DisabledServices = append(project.DisabledServices, s) - } - } - project.Services = types.Services{enabled} + err = opts.apply(project) + if err != nil { + return err } _, err = progress.Run(ctx, func(ctx context.Context) (string, error) { @@ -127,20 +178,21 @@ func runRun(ctx context.Context, opts runOptions) error { // start container and attach to container streams runOpts := compose.RunOptions{ - Name: opts.name, - Service: opts.Service, - Command: opts.Command, - Detach: opts.Detach, - AutoRemove: opts.Remove, - Writer: os.Stdout, - Reader: os.Stdin, - Tty: !opts.noTty, - WorkingDir: opts.workdir, - User: opts.user, - Environment: opts.environment, - Entrypoint: entrypoint, - Labels: labels, - Index: 0, + Name: opts.name, + Service: opts.Service, + Command: opts.Command, + Detach: opts.Detach, + AutoRemove: opts.Remove, + Writer: os.Stdout, + Reader: os.Stdin, + Tty: !opts.noTty, + WorkingDir: opts.workdir, + User: opts.user, + Environment: opts.environment, + Entrypoint: entrypoint, + Labels: labels, + UseNetworkAliases: opts.useAliases, + Index: 0, } exitCode, err := c.ComposeService().RunOneOffContainer(ctx, project, runOpts) if exitCode != 0 { diff --git a/local/compose/convergence.go b/local/compose/convergence.go index fe0571a56..a443de619 100644 --- a/local/compose/convergence.go +++ b/local/compose/convergence.go @@ -64,7 +64,7 @@ func (s *composeService) ensureScale(ctx context.Context, project *types.Project number := next + i name := getContainerName(project.Name, service, number) eg.Go(func() error { - return s.createContainer(ctx, project, service, name, number, false) + return s.createContainer(ctx, project, service, name, number, false, true) }) } } @@ -197,11 +197,11 @@ func getScale(config types.ServiceConfig) (int, error) { return scale, err } -func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, autoRemove bool) error { +func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, autoRemove bool, useNetworkAliases bool) error { w := progress.ContextWriter(ctx) eventName := "Container " + name w.Event(progress.CreatingEvent(eventName)) - err := s.createMobyContainer(ctx, project, service, name, number, nil, autoRemove) + err := s.createMobyContainer(ctx, project, service, name, number, nil, autoRemove, useNetworkAliases) if err != nil { return err } @@ -231,7 +231,7 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P if inherit { inherited = &container } - err = s.createMobyContainer(ctx, project, service, name, number, inherited, false) + err = s.createMobyContainer(ctx, project, service, name, number, inherited, false, true) if err != nil { return err } @@ -268,8 +268,10 @@ func (s *composeService) restartContainer(ctx context.Context, container moby.Co return nil } -func (s *composeService) createMobyContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, inherit *moby.Container, - autoRemove bool) error { +func (s *composeService) createMobyContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, + inherit *moby.Container, + autoRemove bool, + useNetworkAliases bool) error { cState, err := GetContextContainerState(ctx) if err != nil { return err @@ -287,9 +289,16 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types Labels: containerConfig.Labels, } cState.Add(createdContainer) - for netName := range service.Networks { + for netName, cfg := range service.Networks { netwrk := project.Networks[netName] - err = s.connectContainerToNetwork(ctx, created.ID, netwrk.Name, service.Name, getContainerName(project.Name, service, number)) + aliases := []string{getContainerName(project.Name, service, number)} + if useNetworkAliases { + aliases = append(aliases, service.Name) + if cfg != nil { + aliases = append(aliases, cfg.Aliases...) + } + } + err = s.connectContainerToNetwork(ctx, created.ID, netwrk.Name, aliases...) if err != nil { return err } diff --git a/local/compose/run.go b/local/compose/run.go index d29f07f12..14a1f9716 100644 --- a/local/compose/run.go +++ b/local/compose/run.go @@ -62,7 +62,7 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types. if err := s.waitDependencies(ctx, project, service); err != nil { return 0, err } - if err := s.createContainer(ctx, project, service, service.ContainerName, 1, opts.AutoRemove); err != nil { + if err := s.createContainer(ctx, project, service, service.ContainerName, 1, opts.AutoRemove, opts.UseNetworkAliases); err != nil { return 0, err } containerID := service.ContainerName diff --git a/local/e2e/compose/compose_run_test.go b/local/e2e/compose/compose_run_test.go index 777d38695..59fb75e94 100644 --- a/local/e2e/compose/compose_run_test.go +++ b/local/e2e/compose/compose_run_test.go @@ -17,6 +17,7 @@ package e2e import ( + "os" "strings" "testing" @@ -83,4 +84,20 @@ func TestLocalComposeRun(t *testing.T) { res := c.RunDockerCmd("ps", "--all") assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout()) }) + + t.Run("compose run --volumes", func(t *testing.T) { + wd, err := os.Getwd() + assert.NilError(t, err) + res := c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yml", "run", "--volumes", wd+":/foo", "back", "/bin/sh", "-c", "ls /foo") + res.Assert(t, icmd.Expected{Out: "compose_run_test.go"}) + + res = c.RunDockerCmd("ps", "--all") + assert.Assert(t, strings.Contains(res.Stdout(), "run-test_back"), res.Stdout()) + }) + + t.Run("compose run --publish", func(t *testing.T) { + c.RunDockerCmd("compose", "-f", "./fixtures/run-test/compose.yml", "run", "--rm", "--publish", "8080:80", "-d", "back", "/bin/sh", "-c", "sleep 10") + res := c.RunDockerCmd("ps") + assert.Assert(t, strings.Contains(res.Stdout(), "8080->80/tcp"), res.Stdout()) + }) }