Added env-file flag to docker exec

Signed-off-by: Brian Wieder <brian@4wieders.com>
This commit is contained in:
Brian Wieder 2020-06-29 18:32:44 -04:00
parent 44ff366562
commit a6cfbd2351
5 changed files with 85 additions and 17 deletions

View File

@ -27,10 +27,14 @@ type execOptions struct {
workdir string workdir string
container string container string
command []string command []string
envFile opts.ListOpts
} }
func newExecOptions() execOptions { func newExecOptions() execOptions {
return execOptions{env: opts.NewListOpts(opts.ValidateEnv)} return execOptions{
env: opts.NewListOpts(opts.ValidateEnv),
envFile: opts.NewListOpts(nil),
}
} }
// NewExecCommand creates a new cobra.Command for `docker exec` // NewExecCommand creates a new cobra.Command for `docker exec`
@ -59,6 +63,8 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
flags.BoolVarP(&options.privileged, "privileged", "", false, "Give extended privileges to the command") flags.BoolVarP(&options.privileged, "privileged", "", false, "Give extended privileges to the command")
flags.VarP(&options.env, "env", "e", "Set environment variables") flags.VarP(&options.env, "env", "e", "Set environment variables")
flags.SetAnnotation("env", "version", []string{"1.25"}) flags.SetAnnotation("env", "version", []string{"1.25"})
flags.Var(&options.envFile, "env-file", "Read in a file of environment variables")
flags.SetAnnotation("env-file", "version", []string{"1.25"})
flags.StringVarP(&options.workdir, "workdir", "w", "", "Working directory inside the container") flags.StringVarP(&options.workdir, "workdir", "w", "", "Working directory inside the container")
flags.SetAnnotation("workdir", "version", []string{"1.35"}) flags.SetAnnotation("workdir", "version", []string{"1.35"})
@ -66,7 +72,11 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
} }
func runExec(dockerCli command.Cli, options execOptions) error { func runExec(dockerCli command.Cli, options execOptions) error {
execConfig := parseExec(options, dockerCli.ConfigFile()) execConfig, err := parseExec(options, dockerCli.ConfigFile())
if err != nil {
return err
}
ctx := context.Background() ctx := context.Background()
client := dockerCli.Client() client := dockerCli.Client()
@ -185,30 +195,35 @@ func getExecExitStatus(ctx context.Context, client apiclient.ContainerAPIClient,
// parseExec parses the specified args for the specified command and generates // parseExec parses the specified args for the specified command and generates
// an ExecConfig from it. // an ExecConfig from it.
func parseExec(opts execOptions, configFile *configfile.ConfigFile) *types.ExecConfig { func parseExec(execOpts execOptions, configFile *configfile.ConfigFile) (*types.ExecConfig, error) {
execConfig := &types.ExecConfig{ execConfig := &types.ExecConfig{
User: opts.user, User: execOpts.user,
Privileged: opts.privileged, Privileged: execOpts.privileged,
Tty: opts.tty, Tty: execOpts.tty,
Cmd: opts.command, Cmd: execOpts.command,
Detach: opts.detach, Detach: execOpts.detach,
Env: opts.env.GetAll(), WorkingDir: execOpts.workdir,
WorkingDir: opts.workdir, }
// collect all the environment variables for the container
var err error
if execConfig.Env, err = opts.ReadKVEnvStrings(execOpts.envFile.GetAll(), execOpts.env.GetAll()); err != nil {
return nil, err
} }
// If -d is not set, attach to everything by default // If -d is not set, attach to everything by default
if !opts.detach { if !execOpts.detach {
execConfig.AttachStdout = true execConfig.AttachStdout = true
execConfig.AttachStderr = true execConfig.AttachStderr = true
if opts.interactive { if execOpts.interactive {
execConfig.AttachStdin = true execConfig.AttachStdin = true
} }
} }
if opts.detachKeys != "" { if execOpts.detachKeys != "" {
execConfig.DetachKeys = opts.detachKeys execConfig.DetachKeys = execOpts.detachKeys
} else { } else {
execConfig.DetachKeys = configFile.DetachKeys execConfig.DetachKeys = configFile.DetachKeys
} }
return execConfig return execConfig, nil
} }

View File

@ -3,6 +3,7 @@ package container
import ( import (
"context" "context"
"io/ioutil" "io/ioutil"
"os"
"testing" "testing"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
@ -13,10 +14,12 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp" is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/fs"
) )
func withDefaultOpts(options execOptions) execOptions { func withDefaultOpts(options execOptions) execOptions {
options.env = opts.NewListOpts(opts.ValidateEnv) options.env = opts.NewListOpts(opts.ValidateEnv)
options.envFile = opts.NewListOpts(nil)
if len(options.command) == 0 { if len(options.command) == 0 {
options.command = []string{"command"} options.command = []string{"command"}
} }
@ -24,6 +27,13 @@ func withDefaultOpts(options execOptions) execOptions {
} }
func TestParseExec(t *testing.T) { func TestParseExec(t *testing.T) {
content := `ONE=1
TWO=2
`
tmpFile := fs.NewFile(t, t.Name(), fs.WithContent(content))
defer tmpFile.Remove()
testcases := []struct { testcases := []struct {
options execOptions options execOptions
configFile configfile.ConfigFile configFile configfile.ConfigFile
@ -102,14 +112,51 @@ func TestParseExec(t *testing.T) {
Detach: true, Detach: true,
}, },
}, },
{
expected: types.ExecConfig{
Cmd: []string{"command"},
AttachStdout: true,
AttachStderr: true,
Env: []string{"ONE=1", "TWO=2"},
},
options: func() execOptions {
o := withDefaultOpts(execOptions{})
o.envFile.Set(tmpFile.Path())
return o
}(),
},
{
expected: types.ExecConfig{
Cmd: []string{"command"},
AttachStdout: true,
AttachStderr: true,
Env: []string{"ONE=1", "TWO=2", "ONE=override"},
},
options: func() execOptions {
o := withDefaultOpts(execOptions{})
o.envFile.Set(tmpFile.Path())
o.env.Set("ONE=override")
return o
}(),
},
} }
for _, testcase := range testcases { for _, testcase := range testcases {
execConfig := parseExec(testcase.options, &testcase.configFile) execConfig, err := parseExec(testcase.options, &testcase.configFile)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(testcase.expected, *execConfig)) assert.Check(t, is.DeepEqual(testcase.expected, *execConfig))
} }
} }
func TestParseExecNoSuchFile(t *testing.T) {
execOpts := withDefaultOpts(execOptions{})
execOpts.envFile.Set("no-such-env-file")
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
assert.ErrorContains(t, err, "no-such-env-file")
assert.Check(t, os.IsNotExist(err))
assert.Check(t, execConfig == nil)
}
func TestRunExec(t *testing.T) { func TestRunExec(t *testing.T) {
var testcases = []struct { var testcases = []struct {
doc string doc string

View File

@ -1604,6 +1604,10 @@ _docker_container_exec() {
__docker_nospace __docker_nospace
return return
;; ;;
--env-file)
_filedir
return
;;
--user|-u) --user|-u)
__docker_complete_user_group __docker_complete_user_group
return return
@ -1615,7 +1619,7 @@ _docker_container_exec() {
case "$cur" in case "$cur" in
-*) -*)
COMPREPLY=( $( compgen -W "--detach -d --detach-keys --env -e --help --interactive -i --privileged -t --tty -u --user --workdir -w" -- "$cur" ) ) COMPREPLY=( $( compgen -W "--detach -d --detach-keys --env -e --env-file --help --interactive -i --privileged -t --tty -u --user --workdir -w" -- "$cur" ) )
;; ;;
*) *)
__docker_complete_containers_running __docker_complete_containers_running

View File

@ -750,6 +750,7 @@ __docker_container_subcommand() {
$opts_attach_exec_run_start \ $opts_attach_exec_run_start \
"($help -d --detach)"{-d,--detach}"[Detached mode: leave the container running in the background]" \ "($help -d --detach)"{-d,--detach}"[Detached mode: leave the container running in the background]" \
"($help)*"{-e=,--env=}"[Set environment variables]:environment variable: " \ "($help)*"{-e=,--env=}"[Set environment variables]:environment variable: " \
"($help)*--env-file=[Read environment variables from a file]:environment file:_files" \
"($help -i --interactive)"{-i,--interactive}"[Keep stdin open even if not attached]" \ "($help -i --interactive)"{-i,--interactive}"[Keep stdin open even if not attached]" \
"($help)--privileged[Give extended Linux capabilities to the command]" \ "($help)--privileged[Give extended Linux capabilities to the command]" \
"($help -t --tty)"{-t,--tty}"[Allocate a pseudo-tty]" \ "($help -t --tty)"{-t,--tty}"[Allocate a pseudo-tty]" \

View File

@ -15,6 +15,7 @@ Options:
-d, --detach Detached mode: run command in the background -d, --detach Detached mode: run command in the background
--detach-keys Override the key sequence for detaching a container --detach-keys Override the key sequence for detaching a container
-e, --env=[] Set environment variables -e, --env=[] Set environment variables
--env-file Read in a file of environment variables
--help Print usage --help Print usage
-i, --interactive Keep STDIN open even if not attached -i, --interactive Keep STDIN open even if not attached
--privileged Give extended privileges to the command --privileged Give extended privileges to the command