diff --git a/cmd/drone-docker/main.go b/cmd/drone-docker/main.go index a5447a0..d467591 100644 --- a/cmd/drone-docker/main.go +++ b/cmd/drone-docker/main.go @@ -254,6 +254,16 @@ func main() { Usage: "secret key value pair eg id=MYSECRET", EnvVar: "PLUGIN_SECRET", }, + cli.StringSliceFlag{ + Name: "secrets-from-env", + Usage: "secret key value pair eg secret_name=secret", + EnvVar: "PLUGIN_SECRETS_FROM_ENV", + }, + cli.StringSliceFlag{ + Name: "secrets-from-file", + Usage: "secret key value pairs eg secret_name=/path/to/secret", + EnvVar: "PLUGIN_SECRETS_FROM_FILE", + }, cli.StringFlag{ Name: "drone-card-path", Usage: "card path location to write to", @@ -298,6 +308,8 @@ func run(c *cli.Context) error { Link: c.String("link"), NoCache: c.Bool("no-cache"), Secret: c.String("secret"), + SecretEnvs: c.StringSlice("secrets-from-env"), + SecretFiles: c.StringSlice("secrets-from-file"), AddHost: c.StringSlice("add-host"), Quiet: c.Bool("quiet"), }, diff --git a/docker.go b/docker.go index 9d53c24..236065d 100644 --- a/docker.go +++ b/docker.go @@ -59,6 +59,8 @@ type ( Link string // Git repo link NoCache bool // Docker build no-cache Secret string // secret keypair + SecretEnvs []string // Docker build secrets with env var as source + SecretFiles []string // Docker build secrets with file as source AddHost []string // Docker build add-host Quiet bool // Docker build quiet } @@ -306,6 +308,16 @@ func commandBuild(build Build) *exec.Cmd { if build.Secret != "" { args = append(args, "--secret", build.Secret) } + for _, secret := range build.SecretEnvs { + if arg, err := getSecretStringCmdArg(secret); err == nil { + args = append(args, "--secret", arg) + } + } + for _, secret := range build.SecretFiles { + if arg, err := getSecretFileCmdArg(secret); err == nil { + args = append(args, "--secret", arg) + } + } if build.Target != "" { args = append(args, "--target", build.Target) } @@ -338,12 +350,40 @@ func commandBuild(build Build) *exec.Cmd { } // we need to enable buildkit, for secret support - if build.Secret != "" { + if build.Secret != "" || len(build.SecretEnvs) > 0 || len(build.SecretFiles) > 0 { os.Setenv("DOCKER_BUILDKIT", "1") } return exec.Command(dockerExe, args...) } +func getSecretStringCmdArg(kvp string) (string, error) { + return getSecretCmdArg(kvp, false) +} + +func getSecretFileCmdArg(kvp string) (string, error) { + return getSecretCmdArg(kvp, true) +} + +func getSecretCmdArg(kvp string, file bool) (string, error) { + delimIndex := strings.IndexByte(kvp, '=') + if delimIndex == -1 { + return "", fmt.Errorf("%s is not a valid secret", kvp) + } + + key := kvp[:delimIndex] + value := kvp[delimIndex+1:] + + if key == "" || value == "" { + return "", fmt.Errorf("%s is not a valid secret", kvp) + } + + if file { + return fmt.Sprintf("id=%s,src=%s", key, value), nil + } + + return fmt.Sprintf("id=%s,env=%s", key, value), nil +} + // helper function to add proxy values from the environment func addProxyBuildArgs(build *Build) { addProxyValue(build, "http_proxy") diff --git a/docker_test.go b/docker_test.go index 1cdc3ff..ea90181 100644 --- a/docker_test.go +++ b/docker_test.go @@ -1 +1,130 @@ package docker + +import ( + "os/exec" + "reflect" + "testing" +) + +func TestCommandBuild(t *testing.T) { + tcs := []struct { + name string + build Build + want *exec.Cmd + }{ + { + name: "secret from env var", + build: Build{ + Name: "plugins/drone-docker:latest", + Dockerfile: "Dockerfile", + Context: ".", + SecretEnvs: []string{ + "foo_secret=FOO_SECRET_ENV_VAR", + }, + }, + want: exec.Command( + dockerExe, + "build", + "--rm=true", + "-f", + "Dockerfile", + "-t", + "plugins/drone-docker:latest", + ".", + "--secret id=foo_secret,env=FOO_SECRET_ENV_VAR", + ), + }, + { + name: "secret from file", + build: Build{ + Name: "plugins/drone-docker:latest", + Dockerfile: "Dockerfile", + Context: ".", + SecretFiles: []string{ + "foo_secret=/path/to/foo_secret", + }, + }, + want: exec.Command( + dockerExe, + "build", + "--rm=true", + "-f", + "Dockerfile", + "-t", + "plugins/drone-docker:latest", + ".", + "--secret id=foo_secret,src=/path/to/foo_secret", + ), + }, + { + name: "multiple mixed secrets", + build: Build{ + Name: "plugins/drone-docker:latest", + Dockerfile: "Dockerfile", + Context: ".", + SecretEnvs: []string{ + "foo_secret=FOO_SECRET_ENV_VAR", + "bar_secret=BAR_SECRET_ENV_VAR", + }, + SecretFiles: []string{ + "foo_secret=/path/to/foo_secret", + "bar_secret=/path/to/bar_secret", + }, + }, + want: exec.Command( + dockerExe, + "build", + "--rm=true", + "-f", + "Dockerfile", + "-t", + "plugins/drone-docker:latest", + ".", + "--secret id=foo_secret,env=FOO_SECRET_ENV_VAR", + "--secret id=bar_secret,env=BAR_SECRET_ENV_VAR", + "--secret id=foo_secret,src=/path/to/foo_secret", + "--secret id=bar_secret,src=/path/to/bar_secret", + ), + }, + { + name: "invalid mixed secrets", + build: Build{ + Name: "plugins/drone-docker:latest", + Dockerfile: "Dockerfile", + Context: ".", + SecretEnvs: []string{ + "foo_secret=", + "=FOO_SECRET_ENV_VAR", + "", + }, + SecretFiles: []string{ + "foo_secret=", + "=/path/to/bar_secret", + "", + }, + }, + want: exec.Command( + dockerExe, + "build", + "--rm=true", + "-f", + "Dockerfile", + "-t", + "plugins/drone-docker:latest", + ".", + ), + }, + } + + for _, tc := range tcs { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + cmd := commandBuild(tc.build) + + if !reflect.DeepEqual(cmd.String(), tc.want.String()) { + t.Errorf("Got cmd %v, want %v", cmd, tc.want) + } + }) + } +}