implement -v, -p, --service-ports and --use-aliases on compose run

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2021-03-04 14:45:04 +01:00
parent 91b39d0772
commit d08255c4ff
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
5 changed files with 139 additions and 60 deletions

View File

@ -151,20 +151,21 @@ type RemoveOptions struct {
// RunOptions options to execute compose run // RunOptions options to execute compose run
type RunOptions struct { type RunOptions struct {
Name string Name string
Service string Service string
Command []string Command []string
Entrypoint []string Entrypoint []string
Detach bool Detach bool
AutoRemove bool AutoRemove bool
Writer io.Writer Writer io.Writer
Reader io.Reader Reader io.Reader
Tty bool Tty bool
WorkingDir string WorkingDir string
User string User string
Environment []string Environment []string
Labels types.Labels Labels types.Labels
Privileged bool Privileged bool
UseNetworkAliases bool
// used by exec // used by exec
Index int Index int
} }

View File

@ -22,6 +22,7 @@ import (
"os" "os"
"strings" "strings"
"github.com/compose-spec/compose-go/loader"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
"github.com/mattn/go-shellwords" "github.com/mattn/go-shellwords"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -34,18 +35,69 @@ import (
type runOptions struct { type runOptions struct {
*composeOptions *composeOptions
Service string Service string
Command []string Command []string
environment []string environment []string
Detach bool Detach bool
Remove bool Remove bool
noTty bool noTty bool
user string user string
workdir string workdir string
entrypoint string entrypoint string
labels []string labels []string
name string volumes []string
noDeps bool 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 { func runCommand(p *projectOptions) *cobra.Command {
@ -63,6 +115,9 @@ func runCommand(p *projectOptions) *cobra.Command {
opts.Command = args[1:] opts.Command = args[1:]
} }
opts.Service = args[0] 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) 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.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
flags.StringVar(&opts.entrypoint, "entrypoint", "", "Override the entrypoint of the image") flags.StringVar(&opts.entrypoint, "entrypoint", "", "Override the entrypoint of the image")
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services.") 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) flags.SetInterspersed(false)
return cmd return cmd
@ -88,17 +147,9 @@ func runRun(ctx context.Context, opts runOptions) error {
return err return err
} }
if opts.noDeps { err = opts.apply(project)
enabled, err := project.GetService(opts.Service) if err != nil {
if err != nil { return err
return err
}
for _, s := range project.Services {
if s.Name != opts.Service {
project.DisabledServices = append(project.DisabledServices, s)
}
}
project.Services = types.Services{enabled}
} }
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) { _, 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 // start container and attach to container streams
runOpts := compose.RunOptions{ runOpts := compose.RunOptions{
Name: opts.name, Name: opts.name,
Service: opts.Service, Service: opts.Service,
Command: opts.Command, Command: opts.Command,
Detach: opts.Detach, Detach: opts.Detach,
AutoRemove: opts.Remove, AutoRemove: opts.Remove,
Writer: os.Stdout, Writer: os.Stdout,
Reader: os.Stdin, Reader: os.Stdin,
Tty: !opts.noTty, Tty: !opts.noTty,
WorkingDir: opts.workdir, WorkingDir: opts.workdir,
User: opts.user, User: opts.user,
Environment: opts.environment, Environment: opts.environment,
Entrypoint: entrypoint, Entrypoint: entrypoint,
Labels: labels, Labels: labels,
Index: 0, UseNetworkAliases: opts.useAliases,
Index: 0,
} }
exitCode, err := c.ComposeService().RunOneOffContainer(ctx, project, runOpts) exitCode, err := c.ComposeService().RunOneOffContainer(ctx, project, runOpts)
if exitCode != 0 { if exitCode != 0 {

View File

@ -64,7 +64,7 @@ func (s *composeService) ensureScale(ctx context.Context, project *types.Project
number := next + i number := next + i
name := getContainerName(project.Name, service, number) name := getContainerName(project.Name, service, number)
eg.Go(func() error { 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 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) w := progress.ContextWriter(ctx)
eventName := "Container " + name eventName := "Container " + name
w.Event(progress.CreatingEvent(eventName)) 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 { if err != nil {
return err return err
} }
@ -231,7 +231,7 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
if inherit { if inherit {
inherited = &container 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 { if err != nil {
return err return err
} }
@ -268,8 +268,10 @@ func (s *composeService) restartContainer(ctx context.Context, container moby.Co
return nil return nil
} }
func (s *composeService) createMobyContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, inherit *moby.Container, func (s *composeService) createMobyContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int,
autoRemove bool) error { inherit *moby.Container,
autoRemove bool,
useNetworkAliases bool) error {
cState, err := GetContextContainerState(ctx) cState, err := GetContextContainerState(ctx)
if err != nil { if err != nil {
return err return err
@ -287,9 +289,16 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
Labels: containerConfig.Labels, Labels: containerConfig.Labels,
} }
cState.Add(createdContainer) cState.Add(createdContainer)
for netName := range service.Networks { for netName, cfg := range service.Networks {
netwrk := project.Networks[netName] 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 { if err != nil {
return err return err
} }

View File

@ -62,7 +62,7 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
if err := s.waitDependencies(ctx, project, service); err != nil { if err := s.waitDependencies(ctx, project, service); err != nil {
return 0, err 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 return 0, err
} }
containerID := service.ContainerName containerID := service.ContainerName

View File

@ -17,6 +17,7 @@
package e2e package e2e
import ( import (
"os"
"strings" "strings"
"testing" "testing"
@ -83,4 +84,20 @@ func TestLocalComposeRun(t *testing.T) {
res := c.RunDockerCmd("ps", "--all") res := c.RunDockerCmd("ps", "--all")
assert.Assert(t, !strings.Contains(res.Stdout(), "run-test"), res.Stdout()) 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())
})
} }