Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.

Commit b2b9ce0

Browse files
authored
Merge pull request #1003 from aiordache/compose_run_cmd
Add local `compose run` command
2 parents a17e397 + 3714ab7 commit b2b9ce0

File tree

16 files changed

+380
-17
lines changed

16 files changed

+380
-17
lines changed

aci/compose.go

+4
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,7 @@ func (cs *aciComposeService) Logs(ctx context.Context, projectName string, consu
201201
func (cs *aciComposeService) Convert(ctx context.Context, project *types.Project, options compose.ConvertOptions) ([]byte, error) {
202202
return nil, errdefs.ErrNotImplemented
203203
}
204+
205+
func (cs *aciComposeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
206+
return errdefs.ErrNotImplemented
207+
}

api/client/compose.go

+4
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,7 @@ func (c *composeService) List(context.Context, string) ([]compose.Stack, error)
7171
func (c *composeService) Convert(context.Context, *types.Project, compose.ConvertOptions) ([]byte, error) {
7272
return nil, errdefs.ErrNotImplemented
7373
}
74+
75+
func (c *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
76+
return errdefs.ErrNotImplemented
77+
}

api/compose/api.go

+13
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package compose
1818

1919
import (
2020
"context"
21+
"io"
2122

2223
"github.com/compose-spec/compose-go/types"
2324
)
@@ -46,6 +47,8 @@ type Service interface {
4647
List(ctx context.Context, projectName string) ([]Stack, error)
4748
// Convert translate compose model into backend's native format
4849
Convert(ctx context.Context, project *types.Project, options ConvertOptions) ([]byte, error)
50+
// RunOneOffContainer creates a service oneoff container and starts its dependencies
51+
RunOneOffContainer(ctx context.Context, project *types.Project, opts RunOptions) error
4952
}
5053

5154
// UpOptions group options of the Up API
@@ -66,6 +69,16 @@ type ConvertOptions struct {
6669
Format string
6770
}
6871

72+
// RunOptions options to execute compose run
73+
type RunOptions struct {
74+
Name string
75+
Command []string
76+
Detach bool
77+
AutoRemove bool
78+
Writer io.Writer
79+
Reader io.Reader
80+
}
81+
6982
// PortPublisher hold status about published port
7083
type PortPublisher struct {
7184
URL string

cli/cmd/compose/compose.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ func Command(contextType string) *cobra.Command {
9090
listCommand(),
9191
logsCommand(),
9292
convertCommand(),
93+
runCommand(),
9394
)
9495

9596
if contextType == store.LocalContextType || contextType == store.DefaultContextType {
@@ -99,7 +100,7 @@ func Command(contextType string) *cobra.Command {
99100
pullCommand(),
100101
)
101102
}
102-
103+
command.Flags().SetInterspersed(false)
103104
return command
104105
}
105106

cli/cmd/compose/run.go

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
Copyright 2020 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package compose
18+
19+
import (
20+
"context"
21+
"os"
22+
23+
"github.com/compose-spec/compose-go/types"
24+
"github.com/spf13/cobra"
25+
26+
"github.com/docker/compose-cli/api/client"
27+
"github.com/docker/compose-cli/api/compose"
28+
"github.com/docker/compose-cli/progress"
29+
)
30+
31+
type runOptions struct {
32+
Name string
33+
Command []string
34+
WorkingDir string
35+
ConfigPaths []string
36+
Environment []string
37+
Detach bool
38+
Remove bool
39+
}
40+
41+
func runCommand() *cobra.Command {
42+
opts := runOptions{}
43+
runCmd := &cobra.Command{
44+
Use: "run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...] SERVICE [COMMAND] [ARGS...]",
45+
Short: "Run a one-off command on a service.",
46+
Args: cobra.MinimumNArgs(1),
47+
RunE: func(cmd *cobra.Command, args []string) error {
48+
if len(args) > 1 {
49+
opts.Command = args[1:]
50+
}
51+
opts.Name = args[0]
52+
return runRun(cmd.Context(), opts)
53+
},
54+
}
55+
runCmd.Flags().StringVar(&opts.WorkingDir, "workdir", "", "Work dir")
56+
runCmd.Flags().StringArrayVarP(&opts.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
57+
runCmd.Flags().BoolVarP(&opts.Detach, "detach", "d", false, "Run container in background and print container ID")
58+
runCmd.Flags().StringArrayVarP(&opts.Environment, "env", "e", []string{}, "Set environment variables")
59+
runCmd.Flags().BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
60+
61+
runCmd.Flags().SetInterspersed(false)
62+
return runCmd
63+
}
64+
65+
func runRun(ctx context.Context, opts runOptions) error {
66+
projectOpts := composeOptions{
67+
ConfigPaths: opts.ConfigPaths,
68+
WorkingDir: opts.WorkingDir,
69+
Environment: opts.Environment,
70+
}
71+
c, project, err := setup(ctx, projectOpts, []string{opts.Name})
72+
if err != nil {
73+
return err
74+
}
75+
76+
originalServices := project.Services
77+
_, err = progress.Run(ctx, func(ctx context.Context) (string, error) {
78+
return "", startDependencies(ctx, c, project, opts.Name)
79+
})
80+
if err != nil {
81+
return err
82+
}
83+
84+
project.Services = originalServices
85+
// start container and attach to container streams
86+
runOpts := compose.RunOptions{
87+
Name: opts.Name,
88+
Command: opts.Command,
89+
Detach: opts.Detach,
90+
AutoRemove: opts.Remove,
91+
Writer: os.Stdout,
92+
Reader: os.Stdin,
93+
}
94+
return c.ComposeService().RunOneOffContainer(ctx, project, runOpts)
95+
}
96+
97+
func startDependencies(ctx context.Context, c *client.Client, project *types.Project, requestedService string) error {
98+
originalServices := project.Services
99+
dependencies := types.Services{}
100+
for _, service := range originalServices {
101+
if service.Name != requestedService {
102+
dependencies = append(dependencies, service)
103+
}
104+
}
105+
project.Services = dependencies
106+
if err := c.ComposeService().Create(ctx, project); err != nil {
107+
return err
108+
}
109+
if err := c.ComposeService().Start(ctx, project, nil); err != nil {
110+
return err
111+
}
112+
return nil
113+
114+
}

ecs/local/compose.go

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/compose-spec/compose-go/types"
2828
"github.com/docker/compose-cli/api/compose"
2929
"github.com/docker/compose-cli/errdefs"
30+
"github.com/pkg/errors"
3031
"github.com/sanathkr/go-yaml"
3132
)
3233

@@ -162,3 +163,6 @@ func (e ecsLocalSimulation) Ps(ctx context.Context, projectName string) ([]compo
162163
func (e ecsLocalSimulation) List(ctx context.Context, projectName string) ([]compose.Stack, error) {
163164
return e.compose.List(ctx, projectName)
164165
}
166+
func (e ecsLocalSimulation) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
167+
return errors.Wrap(errdefs.ErrNotImplemented, "use docker-compose run")
168+
}

ecs/run.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright 2020 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package ecs
18+
19+
import (
20+
"context"
21+
22+
"github.com/compose-spec/compose-go/types"
23+
"github.com/docker/compose-cli/api/compose"
24+
"github.com/docker/compose-cli/errdefs"
25+
)
26+
27+
func (b *ecsAPIService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
28+
return errdefs.ErrNotImplemented
29+
}

example/backend.go

+3
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,6 @@ func (cs *composeService) Logs(ctx context.Context, projectName string, consumer
182182
func (cs *composeService) Convert(ctx context.Context, project *types.Project, options compose.ConvertOptions) ([]byte, error) {
183183
return nil, errdefs.ErrNotImplemented
184184
}
185+
func (cs *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) error {
186+
return errdefs.ErrNotImplemented
187+
}

local/compose/attach.go

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ func (s *composeService) getContainerStreams(ctx context.Context, container moby
120120
Stdin: true,
121121
Stdout: true,
122122
Stderr: true,
123+
Logs: true,
123124
})
124125
if err != nil {
125126
return nil, nil, err

local/compose/convergence.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func (s *composeService) ensureService(ctx context.Context, project *types.Proje
6262
number := next + i
6363
name := fmt.Sprintf("%s_%s_%d", project.Name, service.Name, number)
6464
eg.Go(func() error {
65-
return s.createContainer(ctx, project, service, name, number)
65+
return s.createContainer(ctx, project, service, name, number, false)
6666
})
6767
}
6868
}
@@ -163,10 +163,10 @@ func getScale(config types.ServiceConfig) int {
163163
return 1
164164
}
165165

166-
func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int) error {
166+
func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, autoRemove bool) error {
167167
w := progress.ContextWriter(ctx)
168168
w.Event(progress.CreatingEvent(name))
169-
err := s.runContainer(ctx, project, service, name, number, nil)
169+
err := s.createMobyContainer(ctx, project, service, name, number, nil, autoRemove)
170170
if err != nil {
171171
return err
172172
}
@@ -191,7 +191,7 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
191191
if err != nil {
192192
return err
193193
}
194-
err = s.runContainer(ctx, project, service, name, number, &container)
194+
err = s.createMobyContainer(ctx, project, service, name, number, &container, false)
195195
if err != nil {
196196
return err
197197
}
@@ -228,8 +228,8 @@ func (s *composeService) restartContainer(ctx context.Context, container moby.Co
228228
return nil
229229
}
230230

231-
func (s *composeService) runContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, container *moby.Container) error {
232-
containerConfig, hostConfig, networkingConfig, err := getContainerCreateOptions(project, service, number, container)
231+
func (s *composeService) createMobyContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int, container *moby.Container, autoRemove bool) error {
232+
containerConfig, hostConfig, networkingConfig, err := getCreateOptions(project, service, number, container, autoRemove)
233233
if err != nil {
234234
return err
235235
}

local/compose/create.go

+23-7
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,20 @@ func (s *composeService) Create(ctx context.Context, project *types.Project) err
4444
return err
4545
}
4646

47+
if err := s.ensureProjectNetworks(ctx, project); err != nil {
48+
return err
49+
}
50+
51+
if err := s.ensureProjectVolumes(ctx, project); err != nil {
52+
return err
53+
}
54+
55+
return InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
56+
return s.ensureService(c, project, service)
57+
})
58+
}
59+
60+
func (s *composeService) ensureProjectNetworks(ctx context.Context, project *types.Project) error {
4761
for k, network := range project.Networks {
4862
if !network.External.External && network.Name != "" {
4963
network.Name = fmt.Sprintf("%s_%s", project.Name, k)
@@ -57,7 +71,10 @@ func (s *composeService) Create(ctx context.Context, project *types.Project) err
5771
return err
5872
}
5973
}
74+
return nil
75+
}
6076

77+
func (s *composeService) ensureProjectVolumes(ctx context.Context, project *types.Project) error {
6178
for k, volume := range project.Volumes {
6279
if !volume.External.External && volume.Name != "" {
6380
volume.Name = fmt.Sprintf("%s_%s", project.Name, k)
@@ -71,13 +88,10 @@ func (s *composeService) Create(ctx context.Context, project *types.Project) err
7188
return err
7289
}
7390
}
74-
75-
return InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
76-
return s.ensureService(c, project, service)
77-
})
91+
return nil
7892
}
7993

80-
func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, number int, inherit *moby.Container) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
94+
func getCreateOptions(p *types.Project, s types.ServiceConfig, number int, inherit *moby.Container, autoRemove bool) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) {
8195
hash, err := jsonHash(s)
8296
if err != nil {
8397
return nil, nil, nil, err
@@ -88,11 +102,12 @@ func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, number i
88102
labels[k] = v
89103
}
90104

91-
// TODO: change oneoffLabel value for containers started with `docker compose run`
92105
labels[projectLabel] = p.Name
93106
labels[serviceLabel] = s.Name
94107
labels[versionLabel] = ComposeVersion
95-
labels[oneoffLabel] = "False"
108+
if _, ok := s.Labels[oneoffLabel]; !ok {
109+
labels[oneoffLabel] = "False"
110+
}
96111
labels[configHashLabel] = hash
97112
labels[workingDirLabel] = p.WorkingDir
98113
labels[configFilesLabel] = strings.Join(p.ComposeFiles, ",")
@@ -152,6 +167,7 @@ func getContainerCreateOptions(p *types.Project, s types.ServiceConfig, number i
152167

153168
networkMode := getNetworkMode(p, s)
154169
hostConfig := container.HostConfig{
170+
AutoRemove: autoRemove,
155171
Mounts: mountOptions,
156172
CapAdd: strslice.StrSlice(s.CapAdd),
157173
CapDrop: strslice.StrSlice(s.CapDrop),

local/compose/down.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,17 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c
9191

9292
func (s *composeService) removeContainers(ctx context.Context, w progress.Writer, eg *errgroup.Group, containers []moby.Container) error {
9393
for _, container := range containers {
94+
toDelete := container
9495
eg.Go(func() error {
95-
eventName := "Container " + getContainerName(container)
96+
eventName := "Container " + getContainerName(toDelete)
9697
w.Event(progress.StoppingEvent(eventName))
97-
err := s.apiClient.ContainerStop(ctx, container.ID, nil)
98+
err := s.apiClient.ContainerStop(ctx, toDelete.ID, nil)
9899
if err != nil {
99100
w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
100101
return err
101102
}
102103
w.Event(progress.RemovingEvent(eventName))
103-
err = s.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
104+
err = s.apiClient.ContainerRemove(ctx, toDelete.ID, moby.ContainerRemoveOptions{})
104105
if err != nil {
105106
w.Event(progress.ErrorMessageEvent(eventName, "Error while Removing"))
106107
return err

local/compose/labels.go

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
const (
2626
containerNumberLabel = "com.docker.compose.container-number"
2727
oneoffLabel = "com.docker.compose.oneoff"
28+
slugLabel = "com.docker.compose.slug"
2829
projectLabel = "com.docker.compose.project"
2930
volumeLabel = "com.docker.compose.volume"
3031
workingDirLabel = "com.docker.compose.project.working_dir"

0 commit comments

Comments
 (0)