mirror of https://github.com/docker/compose.git
Merge pull request #1341 from docker/run_opts
introduce a few more `compose run` options
This commit is contained in:
commit
ea24e499e6
|
@ -19,6 +19,7 @@ package compose
|
|||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
|
@ -130,20 +131,38 @@ type RemoveOptions struct {
|
|||
|
||||
// RunOptions options to execute compose run
|
||||
type RunOptions struct {
|
||||
Service string
|
||||
Command []string
|
||||
Detach bool
|
||||
AutoRemove bool
|
||||
Writer io.Writer
|
||||
Reader io.Reader
|
||||
|
||||
// used by exec
|
||||
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
|
||||
Index int
|
||||
// used by exec
|
||||
Index int
|
||||
}
|
||||
|
||||
// EnvironmentMap return RunOptions.Environment as a MappingWithEquals
|
||||
func (opts *RunOptions) EnvironmentMap() types.MappingWithEquals {
|
||||
environment := types.MappingWithEquals{}
|
||||
for _, s := range opts.Environment {
|
||||
parts := strings.SplitN(s, "=", 2)
|
||||
key := parts[0]
|
||||
switch {
|
||||
case len(parts) == 1:
|
||||
environment[key] = nil
|
||||
default:
|
||||
environment[key] = &parts[1]
|
||||
}
|
||||
}
|
||||
return environment
|
||||
}
|
||||
|
||||
// PsOptions group options of the Ps API
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestRunOptionsEnvironmentMap(t *testing.T) {
|
||||
opts := RunOptions{
|
||||
Environment: []string{
|
||||
"FOO=BAR",
|
||||
"ZOT=",
|
||||
"QIX",
|
||||
},
|
||||
}
|
||||
env := opts.EnvironmentMap()
|
||||
assert.Equal(t, *env["FOO"], "BAR")
|
||||
assert.Equal(t, *env["ZOT"], "")
|
||||
assert.Check(t, env["QIX"] == nil)
|
||||
}
|
|
@ -18,9 +18,12 @@ package compose
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/mattn/go-shellwords"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose-cli/api/client"
|
||||
|
@ -33,9 +36,15 @@ type runOptions struct {
|
|||
*composeOptions
|
||||
Service string
|
||||
Command []string
|
||||
Environment []string
|
||||
environment []string
|
||||
Detach bool
|
||||
Remove bool
|
||||
noTty bool
|
||||
user string
|
||||
workdir string
|
||||
entrypoint string
|
||||
labels []string
|
||||
name string
|
||||
}
|
||||
|
||||
func runCommand(p *projectOptions) *cobra.Command {
|
||||
|
@ -44,7 +53,7 @@ func runCommand(p *projectOptions) *cobra.Command {
|
|||
projectOptions: p,
|
||||
},
|
||||
}
|
||||
runCmd := &cobra.Command{
|
||||
cmd := &cobra.Command{
|
||||
Use: "run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...] SERVICE [COMMAND] [ARGS...]",
|
||||
Short: "Run a one-off command on a service.",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
|
@ -56,12 +65,19 @@ func runCommand(p *projectOptions) *cobra.Command {
|
|||
return runRun(cmd.Context(), opts)
|
||||
},
|
||||
}
|
||||
runCmd.Flags().BoolVarP(&opts.Detach, "detach", "d", false, "Run container in background and print container ID")
|
||||
runCmd.Flags().StringArrayVarP(&opts.Environment, "env", "e", []string{}, "Set environment variables")
|
||||
runCmd.Flags().BoolVar(&opts.Remove, "rm", false, "Automatically remove the container when it exits")
|
||||
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, "labels", "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", false, "Disable pseudo-tty allocation. By default docker compose run allocates a TTY")
|
||||
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")
|
||||
|
||||
runCmd.Flags().SetInterspersed(false)
|
||||
return runCmd
|
||||
flags.SetInterspersed(false)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRun(ctx context.Context, opts runOptions) error {
|
||||
|
@ -77,14 +93,39 @@ func runRun(ctx context.Context, opts runOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
var entrypoint []string
|
||||
if opts.entrypoint != "" {
|
||||
entrypoint, err = shellwords.Parse(opts.entrypoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
labels := types.Labels{}
|
||||
for _, s := range opts.labels {
|
||||
parts := strings.SplitN(s, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("label must be set as KEY=VALUE")
|
||||
}
|
||||
labels[parts[0]] = parts[1]
|
||||
}
|
||||
|
||||
// start container and attach to container streams
|
||||
runOpts := compose.RunOptions{
|
||||
Service: opts.Service,
|
||||
Command: opts.Command,
|
||||
Detach: opts.Detach,
|
||||
AutoRemove: opts.Remove,
|
||||
Writer: os.Stdout,
|
||||
Reader: os.Stdin,
|
||||
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,
|
||||
}
|
||||
exitCode, err := c.ComposeService().RunOneOffContainer(ctx, project, runOpts)
|
||||
if exitCode != 0 {
|
||||
|
|
1
go.mod
1
go.mod
|
@ -39,6 +39,7 @@ require (
|
|||
github.com/joho/godotenv v1.3.0
|
||||
github.com/labstack/echo v3.3.10+incompatible
|
||||
github.com/labstack/gommon v0.3.0 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.11
|
||||
github.com/moby/buildkit v0.8.1-0.20201205083753-0af7b1b9c693
|
||||
github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf
|
||||
github.com/morikuni/aec v1.0.0
|
||||
|
|
|
@ -30,37 +30,32 @@ import (
|
|||
)
|
||||
|
||||
func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.Project, opts compose.RunOptions) (int, error) {
|
||||
originalServices := project.Services
|
||||
var requestedService types.ServiceConfig
|
||||
for _, service := range originalServices {
|
||||
if service.Name == opts.Service {
|
||||
requestedService = service
|
||||
}
|
||||
service, err := project.GetService(opts.Service)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
project.Services = originalServices
|
||||
if len(opts.Command) > 0 {
|
||||
requestedService.Command = opts.Command
|
||||
}
|
||||
requestedService.Scale = 1
|
||||
requestedService.Tty = true
|
||||
requestedService.StdinOpen = true
|
||||
applyRunOptions(&service, opts)
|
||||
|
||||
slug := moby.GenerateRandomID()
|
||||
requestedService.ContainerName = fmt.Sprintf("%s_%s_run_%s", project.Name, requestedService.Name, moby.TruncateID(slug))
|
||||
requestedService.Labels = requestedService.Labels.Add(slugLabel, slug)
|
||||
requestedService.Labels = requestedService.Labels.Add(oneoffLabel, "True")
|
||||
if service.ContainerName == "" {
|
||||
service.ContainerName = fmt.Sprintf("%s_%s_run_%s", project.Name, service.Name, moby.TruncateID(slug))
|
||||
}
|
||||
service.Scale = 1
|
||||
service.StdinOpen = true
|
||||
service.Labels = service.Labels.Add(slugLabel, slug)
|
||||
service.Labels = service.Labels.Add(oneoffLabel, "True")
|
||||
|
||||
if err := s.ensureImagesExists(ctx, project); err != nil { // all dependencies already checked, but might miss requestedService img
|
||||
if err := s.ensureImagesExists(ctx, project); err != nil { // all dependencies already checked, but might miss service img
|
||||
return 0, err
|
||||
}
|
||||
if err := s.waitDependencies(ctx, project, requestedService); err != nil {
|
||||
if err := s.waitDependencies(ctx, project, service); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := s.createContainer(ctx, project, requestedService, requestedService.ContainerName, 1, opts.AutoRemove); err != nil {
|
||||
if err := s.createContainer(ctx, project, service, service.ContainerName, 1, opts.AutoRemove); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
containerID := requestedService.ContainerName
|
||||
containerID := service.ContainerName
|
||||
|
||||
if opts.Detach {
|
||||
err := s.apiClient.ContainerStart(ctx, containerID, apitypes.ContainerStartOptions{})
|
||||
|
@ -81,7 +76,7 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
|
|||
return 0, err
|
||||
}
|
||||
oneoffContainer := containers[0]
|
||||
err = s.attachContainerStreams(ctx, oneoffContainer, true, opts.Reader, opts.Writer)
|
||||
err = s.attachContainerStreams(ctx, oneoffContainer, service.Tty, opts.Reader, opts.Writer)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -100,3 +95,27 @@ func (s *composeService) RunOneOffContainer(ctx context.Context, project *types.
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func applyRunOptions(service *types.ServiceConfig, opts compose.RunOptions) {
|
||||
service.Tty = opts.Tty
|
||||
service.ContainerName = opts.Name
|
||||
|
||||
if len(opts.Command) > 0 {
|
||||
service.Command = opts.Command
|
||||
}
|
||||
if len(opts.User) > 0 {
|
||||
service.User = opts.User
|
||||
}
|
||||
if len(opts.WorkingDir) > 0 {
|
||||
service.WorkingDir = opts.WorkingDir
|
||||
}
|
||||
if len(opts.Entrypoint) > 0 {
|
||||
service.Entrypoint = opts.Entrypoint
|
||||
}
|
||||
if len(opts.Environment) > 0 {
|
||||
service.Environment.OverrideBy(opts.EnvironmentMap())
|
||||
}
|
||||
for k, v := range opts.Labels {
|
||||
service.Labels.Add(k, v)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue