瀏覽代碼

Pass secret definition to init container as json struct

this avoid yet another new micro-formats that is poorly documented

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 5 年之前
父節點
當前提交
d74796aca2
共有 6 個文件被更改,包括 155 次插入168 次删除
  1. 20 11
      ecs/pkg/amazon/backend/convert.go
  2. 2 2
      ecs/secrets/Dockerfile
  3. 87 0
      ecs/secrets/init.go
  4. 13 33
      ecs/secrets/init_test.go
  5. 0 122
      ecs/secrets/main.go
  6. 33 0
      ecs/secrets/main/main.go

+ 20 - 11
ecs/pkg/amazon/backend/convert.go

@@ -1,12 +1,15 @@
 package backend
 package backend
 
 
 import (
 import (
+	"encoding/json"
 	"fmt"
 	"fmt"
 	"sort"
 	"sort"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
+	"github.com/docker/ecs-plugin/secrets"
+
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws"
 	ecsapi "github.com/aws/aws-sdk-go/service/ecs"
 	ecsapi "github.com/aws/aws-sdk-go/service/ecs"
 	"github.com/awslabs/goformation/v4/cloudformation"
 	"github.com/awslabs/goformation/v4/cloudformation"
@@ -55,6 +58,7 @@ func Convert(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefi
 		initContainers []ecs.TaskDefinition_ContainerDependency
 		initContainers []ecs.TaskDefinition_ContainerDependency
 	)
 	)
 	if len(service.Secrets) > 0 {
 	if len(service.Secrets) > 0 {
+		initContainerName := fmt.Sprintf("%s_Secrets_InitContainer", normalizeResourceName(service.Name))
 		volumes = append(volumes, ecs.TaskDefinition_Volume{
 		volumes = append(volumes, ecs.TaskDefinition_Volume{
 			Name: "secrets",
 			Name: "secrets",
 		})
 		})
@@ -65,25 +69,24 @@ func Convert(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefi
 		})
 		})
 		initContainers = append(initContainers, ecs.TaskDefinition_ContainerDependency{
 		initContainers = append(initContainers, ecs.TaskDefinition_ContainerDependency{
 			Condition:     ecsapi.ContainerConditionSuccess,
 			Condition:     ecsapi.ContainerConditionSuccess,
-			ContainerName: "Secrets_InitContainer",
+			ContainerName: initContainerName,
 		})
 		})
 
 
 		var (
 		var (
-			names   []string
-			secrets []ecs.TaskDefinition_Secret
+			args        []secrets.Secret
+			taskSecrets []ecs.TaskDefinition_Secret
 		)
 		)
 		for _, s := range service.Secrets {
 		for _, s := range service.Secrets {
 			secretConfig := project.Secrets[s.Source]
 			secretConfig := project.Secrets[s.Source]
 			if s.Target == "" {
 			if s.Target == "" {
 				s.Target = s.Source
 				s.Target = s.Source
 			}
 			}
-			secrets = append(secrets, ecs.TaskDefinition_Secret{
+			taskSecrets = append(taskSecrets, ecs.TaskDefinition_Secret{
 				Name:      s.Target,
 				Name:      s.Target,
 				ValueFrom: secretConfig.Name,
 				ValueFrom: secretConfig.Name,
 			})
 			})
-			name := s.Target
+			var keys []string
 			if ext, ok := secretConfig.Extensions[compose.ExtensionKeys]; ok {
 			if ext, ok := secretConfig.Extensions[compose.ExtensionKeys]; ok {
-				var keys []string
 				if key, ok := ext.(string); ok {
 				if key, ok := ext.(string); ok {
 					keys = append(keys, key)
 					keys = append(keys, key)
 				} else {
 				} else {
@@ -91,14 +94,20 @@ func Convert(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefi
 						keys = append(keys, k.(string))
 						keys = append(keys, k.(string))
 					}
 					}
 				}
 				}
-				name = fmt.Sprintf("%s:%s", s.Target, strings.Join(keys, ","))
 			}
 			}
-			names = append(names, name)
+			args = append(args, secrets.Secret{
+				Name: s.Target,
+				Keys: keys,
+			})
+		}
+		command, err := json.Marshal(args)
+		if err != nil {
+			return nil, err
 		}
 		}
 		containers = append(containers, ecs.TaskDefinition_ContainerDefinition{
 		containers = append(containers, ecs.TaskDefinition_ContainerDefinition{
-			Name:             fmt.Sprintf("%s_Secrets_InitContainer", normalizeResourceName(service.Name)),
+			Name:             initContainerName,
 			Image:            secretsInitContainerImage,
 			Image:            secretsInitContainerImage,
-			Command:          names,
+			Command:          []string{string(command)},
 			Essential:        false, // FIXME this will be ignored, see https://github.com/awslabs/goformation/issues/61#issuecomment-625139607
 			Essential:        false, // FIXME this will be ignored, see https://github.com/awslabs/goformation/issues/61#issuecomment-625139607
 			LogConfiguration: logConfiguration,
 			LogConfiguration: logConfiguration,
 			MountPoints: []ecs.TaskDefinition_MountPoint{
 			MountPoints: []ecs.TaskDefinition_MountPoint{
@@ -108,7 +117,7 @@ func Convert(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefi
 					SourceVolume:  "secrets",
 					SourceVolume:  "secrets",
 				},
 				},
 			},
 			},
-			Secrets: secrets,
+			Secrets: taskSecrets,
 		})
 		})
 	}
 	}
 
 

+ 2 - 2
ecs/secrets/Dockerfile

@@ -1,7 +1,7 @@
 FROM golang:1.14.4-alpine AS builder
 FROM golang:1.14.4-alpine AS builder
-WORKDIR $GOPATH/src/github.com/docker/ecs-secrets
+WORKDIR $GOPATH/src/github.com/docker/ecs-plugin/secrets
 COPY . .
 COPY . .
-RUN GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /go/bin/secrets
+RUN GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /go/bin/secrets main/main.go
 
 
 FROM scratch
 FROM scratch
 COPY --from=builder /go/bin/secrets /secrets
 COPY --from=builder /go/bin/secrets /secrets

+ 87 - 0
ecs/secrets/init.go

@@ -0,0 +1,87 @@
+package secrets
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+)
+
+type Secret struct {
+	Name string
+	Keys []string
+}
+
+func CreateSecretFiles(secret Secret, path string) error {
+	value, ok := os.LookupEnv(secret.Name)
+	if !ok {
+		return fmt.Errorf("%q variable not set", secret.Name)
+	}
+
+	secrets := filepath.Join(path, secret.Name)
+
+	if len(secret.Keys) == 0 {
+		// raw Secret
+		fmt.Printf("inject Secret %q info %s\n", secret.Name, secrets)
+		return ioutil.WriteFile(secrets, []byte(value), 0444)
+	}
+
+	var unmarshalled interface{}
+	err := json.Unmarshal([]byte(value), &unmarshalled)
+	if err != nil {
+		return fmt.Errorf("%q Secret is not a valid JSON document: %w", secret.Name, err)
+	}
+
+	dict, ok := unmarshalled.(map[string]interface{})
+	if !ok {
+		return fmt.Errorf("%q Secret is not a JSON dictionary: %w", secret.Name, err)
+	}
+	err = os.MkdirAll(secrets, 0755)
+	if err != nil {
+		return err
+	}
+
+	if contains(secret.Keys, "*") {
+		var keys []string
+		for k := range dict {
+			keys = append(keys, k)
+		}
+		secret.Keys = keys
+	}
+
+	for _, k := range secret.Keys {
+		path := filepath.Join(secrets, k)
+		fmt.Printf("inject Secret %q info %s\n", k, path)
+
+		v, ok := dict[k]
+		if !ok {
+			return fmt.Errorf("%q Secret has no %q key", secret.Name, k)
+		}
+
+		var raw []byte
+		if s, ok := v.(string); ok {
+			raw = []byte(s)
+		} else {
+			raw, err = json.Marshal(v)
+			if err != nil {
+				return err
+			}
+		}
+
+		err = ioutil.WriteFile(path, raw, 0444)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func contains(keys []string, s string) bool {
+	for _, k := range keys {
+		if k == s {
+			return true
+		}
+	}
+	return false
+}

+ 13 - 33
ecs/secrets/main_test.go → ecs/secrets/init_test.go

@@ -1,4 +1,4 @@
-package main
+package secrets
 
 
 import (
 import (
 	"io/ioutil"
 	"io/ioutil"
@@ -10,34 +10,14 @@ import (
 	"gotest.tools/v3/fs"
 	"gotest.tools/v3/fs"
 )
 )
 
 
-func TestParseSecrets(t *testing.T) {
-	secrets := parseInput([]string{
-		"foo",
-		"bar:*",
-		"zot:key0,key1",
-	})
-	assert.Check(t, len(secrets) == 3)
-	assert.Check(t, secrets[0].name == "foo")
-	assert.Check(t, secrets[0].keys == nil)
-
-	assert.Check(t, secrets[1].name == "bar")
-	assert.Check(t, len(secrets[1].keys) == 1)
-	assert.Check(t, secrets[1].keys[0] == "*")
-
-	assert.Check(t, secrets[2].name == "zot")
-	assert.Check(t, len(secrets[2].keys) == 2)
-	assert.Check(t, secrets[2].keys[0] == "key0")
-	assert.Check(t, secrets[2].keys[1] == "key1")
-}
-
 func TestRawSecret(t *testing.T) {
 func TestRawSecret(t *testing.T) {
 	dir := fs.NewDir(t, "secrets").Path()
 	dir := fs.NewDir(t, "secrets").Path()
 	os.Setenv("raw", "something_secret")
 	os.Setenv("raw", "something_secret")
 	defer os.Unsetenv("raw")
 	defer os.Unsetenv("raw")
 
 
-	err := createSecretFiles(secret{
-		name: "raw",
-		keys: nil,
+	err := CreateSecretFiles(Secret{
+		Name: "raw",
+		Keys: nil,
 	}, dir)
 	}, dir)
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 	file, err := ioutil.ReadFile(filepath.Join(dir, "raw"))
 	file, err := ioutil.ReadFile(filepath.Join(dir, "raw"))
@@ -55,9 +35,9 @@ func TestSelectedKeysSecret(t *testing.T) {
 }`)
 }`)
 	defer os.Unsetenv("json")
 	defer os.Unsetenv("json")
 
 
-	err := createSecretFiles(secret{
-		name: "json",
-		keys: []string{"foo"},
+	err := CreateSecretFiles(Secret{
+		Name: "json",
+		Keys: []string{"foo"},
 	}, dir)
 	}, dir)
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 	file, err := ioutil.ReadFile(filepath.Join(dir, "json", "foo"))
 	file, err := ioutil.ReadFile(filepath.Join(dir, "json", "foo"))
@@ -78,9 +58,9 @@ func TestAllKeysSecret(t *testing.T) {
 }`)
 }`)
 	defer os.Unsetenv("json")
 	defer os.Unsetenv("json")
 
 
-	err := createSecretFiles(secret{
-		name: "json",
-		keys: []string{"*"},
+	err := CreateSecretFiles(Secret{
+		Name: "json",
+		Keys: []string{"*"},
 	}, dir)
 	}, dir)
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 	file, err := ioutil.ReadFile(filepath.Join(dir, "json", "foo"))
 	file, err := ioutil.ReadFile(filepath.Join(dir, "json", "foo"))
@@ -97,9 +77,9 @@ func TestAllKeysSecret(t *testing.T) {
 func TestUnknownSecret(t *testing.T) {
 func TestUnknownSecret(t *testing.T) {
 	dir := fs.NewDir(t, "secrets").Path()
 	dir := fs.NewDir(t, "secrets").Path()
 
 
-	err := createSecretFiles(secret{
-		name: "not_set",
-		keys: nil,
+	err := CreateSecretFiles(Secret{
+		Name: "not_set",
+		Keys: nil,
 	}, dir)
 	}, dir)
 	assert.Check(t, err != nil)
 	assert.Check(t, err != nil)
 }
 }

+ 0 - 122
ecs/secrets/main.go

@@ -1,122 +0,0 @@
-package main
-
-import (
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"strings"
-
-	"github.com/pkg/errors"
-)
-
-type secret struct {
-	name string
-	keys []string
-}
-
-const secretsFolder = "/run/secrets"
-
-func main() {
-	secrets := parseInput(os.Args[1:])
-
-	for _, secret := range secrets {
-		err := createSecretFiles(secret, secretsFolder)
-		if err != nil {
-			fmt.Fprintf(os.Stderr, err.Error())
-			os.Exit(1)
-		}
-	}
-}
-
-func createSecretFiles(secret secret, path string) error {
-	value, ok := os.LookupEnv(secret.name)
-	if !ok {
-		return fmt.Errorf("%q variable not set", secret.name)
-	}
-
-	secrets := filepath.Join(path, secret.name)
-
-	if len(secret.keys) == 0 {
-		// raw secret
-		fmt.Printf("inject secret %q info %s\n", secret.name, secrets)
-		return ioutil.WriteFile(secrets, []byte(value), 0444)
-	}
-
-	var unmarshalled interface{}
-	err := json.Unmarshal([]byte(value), &unmarshalled)
-	if err != nil {
-		return errors.Wrapf(err, "%q secret is not a valid JSON document", secret.name)
-	}
-
-	dict, ok := unmarshalled.(map[string]interface{})
-	if !ok {
-		return errors.Wrapf(err, "%q secret is not a JSON dictionary", secret.name)
-	}
-	err = os.MkdirAll(secrets, 0755)
-	if err != nil {
-		return err
-	}
-
-	if contains(secret.keys, "*") {
-		var keys []string
-		for k := range dict {
-			keys = append(keys, k)
-		}
-		secret.keys = keys
-	}
-
-	for _, k := range secret.keys {
-		path := filepath.Join(secrets, k)
-		fmt.Printf("inject secret %q info %s\n", k, path)
-
-		v, ok := dict[k]
-		if !ok {
-			return fmt.Errorf("%q secret has no %q key", secret.name, k)
-		}
-
-		var raw []byte
-		if s, ok := v.(string); ok {
-			raw = []byte(s)
-		} else {
-			raw, err = json.Marshal(v)
-			if err != nil {
-				return err
-			}
-		}
-
-		err = ioutil.WriteFile(path, raw, 0444)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// parseInput parse secret to be dumped into secret files with syntax `VARIABLE_NAME[:COMA_SEPARATED_KEYS]`
-func parseInput(input []string) []secret {
-	var secrets []secret
-	for _, name := range input {
-		i := strings.Index(name, ":")
-		var keys []string
-		if i > 0 {
-			keys = strings.Split(name[i+1:], ",")
-			name = name[:i]
-		}
-		secrets = append(secrets, secret{
-			name: name,
-			keys: keys,
-		})
-	}
-	return secrets
-}
-
-func contains(keys []string, s string) bool {
-	for _, k := range keys {
-		if k == s {
-			return true
-		}
-	}
-	return false
-}

+ 33 - 0
ecs/secrets/main/main.go

@@ -0,0 +1,33 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+
+	"github.com/docker/ecs-plugin/secrets"
+)
+
+const secretsFolder = "/run/secrets"
+
+func main() {
+	if len(os.Args) != 2 {
+		fmt.Fprintf(os.Stderr, "usage: secrets <json encoded []Secret>")
+		os.Exit(1)
+	}
+
+	var input []secrets.Secret
+	err := json.Unmarshal([]byte(os.Args[1]), &input)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, err.Error())
+		os.Exit(1)
+	}
+
+	for _, secret := range input {
+		err := secrets.CreateSecretFiles(secret, secretsFolder)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, err.Error())
+			os.Exit(1)
+		}
+	}
+}