소스 검색

TestCase for the secrets init container

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 5 년 전
부모
커밋
4bfab35007
5개의 변경된 파일199개의 추가작업 그리고 60개의 파일을 삭제
  1. 3 6
      ecs/cmd/commands/compose.go
  2. 1 0
      ecs/go.mod
  3. 1 2
      ecs/pkg/amazon/backend/cloudformation.go
  4. 89 52
      ecs/secrets/main.go
  5. 105 0
      ecs/secrets/main_test.go

+ 3 - 6
ecs/cmd/commands/compose.go

@@ -7,11 +7,10 @@ import (
 	"os"
 	"strings"
 
-	"github.com/docker/ecs-plugin/pkg/amazon/cloudformation"
-
 	"github.com/compose-spec/compose-go/cli"
 	"github.com/docker/cli/cli/command"
 	amazon "github.com/docker/ecs-plugin/pkg/amazon/backend"
+	"github.com/docker/ecs-plugin/pkg/amazon/cloudformation"
 	"github.com/docker/ecs-plugin/pkg/docker"
 	"github.com/spf13/cobra"
 )
@@ -60,13 +59,11 @@ func ConvertCommand(dockerCli command.Cli, options *composeOptions) *cobra.Comma
 			if err != nil {
 				return err
 			}
-
 			json, err := cloudformation.Marshall(template)
 			if err != nil {
-				fmt.Printf("Failed to generate JSON: %s\n", err)
-			} else {
-				fmt.Printf("%s\n", string(json))
+				return err
 			}
+			fmt.Printf("%s\n", string(json))
 			return nil
 		}),
 	}

+ 1 - 0
ecs/go.mod

@@ -39,6 +39,7 @@ require (
 	github.com/morikuni/aec v1.0.0 // indirect
 	github.com/onsi/ginkgo v1.11.0 // indirect
 	github.com/opencontainers/image-spec v1.0.1 // indirect
+	github.com/pkg/errors v0.9.1
 	github.com/sirupsen/logrus v1.6.0
 	github.com/smartystreets/goconvey v1.6.4 // indirect
 	github.com/spf13/cobra v0.0.5

+ 1 - 2
ecs/pkg/amazon/backend/cloudformation.go

@@ -6,8 +6,6 @@ import (
 	"regexp"
 	"strings"
 
-	"github.com/awslabs/goformation/v4/cloudformation/secretsmanager"
-
 	ecsapi "github.com/aws/aws-sdk-go/service/ecs"
 	"github.com/aws/aws-sdk-go/service/elbv2"
 	cloudmapapi "github.com/aws/aws-sdk-go/service/servicediscovery"
@@ -17,6 +15,7 @@ import (
 	"github.com/awslabs/goformation/v4/cloudformation/elasticloadbalancingv2"
 	"github.com/awslabs/goformation/v4/cloudformation/iam"
 	"github.com/awslabs/goformation/v4/cloudformation/logs"
+	"github.com/awslabs/goformation/v4/cloudformation/secretsmanager"
 	cloudmap "github.com/awslabs/goformation/v4/cloudformation/servicediscovery"
 	"github.com/awslabs/goformation/v4/cloudformation/tags"
 	"github.com/compose-spec/compose-go/compatibility"

+ 89 - 52
ecs/secrets/main.go

@@ -7,72 +7,109 @@ import (
 	"os"
 	"path/filepath"
 	"strings"
+
+	"github.com/pkg/errors"
 )
 
-// return codes:
-// 1: failed to read secret from env
-// 2: failed to parse hierarchical secret
-// 3: failed to write secret content into file
+type secret struct {
+	name string
+	keys []string
+}
+
+const secretsFolder = "/run/secrets"
+
 func main() {
-	for _, name := range os.Args[1:] {
-		i := strings.Index(name, ":")
+	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
-		if i > 0 {
-			keys = strings.Split(name[i+1:], ",")
-			name = name[:i]
+		for k := range dict {
+			keys = append(keys, k)
 		}
-		value, ok := os.LookupEnv(name)
+		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 {
-			fmt.Fprintf(os.Stderr, "%q variable not set", name)
-			os.Exit(1)
+			return fmt.Errorf("%q secret has no %q key", secret.name, k)
 		}
 
-		secrets := filepath.Join("/run/secrets", name)
-
-		if len(keys) == 0 {
-			// raw secret
-			fmt.Printf("inject secret %q info %s\n", name, secrets)
-			err := ioutil.WriteFile(secrets, []byte(value), 0444)
+		var raw []byte
+		if s, ok := v.(string); ok {
+			raw = []byte(s)
+		} else {
+			raw, err = json.Marshal(v)
 			if err != nil {
-				fmt.Fprintf(os.Stderr, err.Error())
-				os.Exit(3)
+				return err
 			}
-			os.Exit(0)
 		}
 
-		var unmarshalled interface{}
-		err := json.Unmarshal([]byte(value), &unmarshalled)
-		if err == nil {
-			if dict, ok := unmarshalled.(map[string]interface{}); ok {
-				os.MkdirAll(secrets, 0555)
-				for k, v := range dict {
-					if !contains(keys, k) && !contains(keys, "*") {
-						continue
-					}
-					path := filepath.Join(secrets, k)
-					fmt.Printf("inject secret %q info %s\n", k, path)
-
-					var raw []byte
-					if s, ok := v.(string); ok {
-						raw = []byte(s)
-					} else {
-						raw, err = json.Marshal(v)
-						if err != nil {
-							fmt.Fprintf(os.Stderr, err.Error())
-							os.Exit(2)
-						}
-					}
-
-					err = ioutil.WriteFile(path, raw, 0444)
-					if err != nil {
-						fmt.Fprintf(os.Stderr, err.Error())
-						os.Exit(3)
-					}
-				}
-				os.Exit(0)
-			}
+		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 {

+ 105 - 0
ecs/secrets/main_test.go

@@ -0,0 +1,105 @@
+package main
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"gotest.tools/v3/assert"
+	"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) {
+	dir := fs.NewDir(t, "secrets").Path()
+	os.Setenv("raw", "something_secret")
+	defer os.Unsetenv("raw")
+
+	err := createSecretFiles(secret{
+		name: "raw",
+		keys: nil,
+	}, dir)
+	assert.NilError(t, err)
+	file, err := ioutil.ReadFile(filepath.Join(dir, "raw"))
+	assert.NilError(t, err)
+	content := string(file)
+	assert.Equal(t, content, "something_secret")
+}
+
+func TestSelectedKeysSecret(t *testing.T) {
+	dir := fs.NewDir(t, "secrets").Path()
+	os.Setenv("json", `
+{
+   "foo": "bar",
+   "zot": "qix"
+}`)
+	defer os.Unsetenv("json")
+
+	err := createSecretFiles(secret{
+		name: "json",
+		keys: []string{"foo"},
+	}, dir)
+	assert.NilError(t, err)
+	file, err := ioutil.ReadFile(filepath.Join(dir, "json", "foo"))
+	assert.NilError(t, err)
+	content := string(file)
+	assert.Equal(t, content, "bar")
+
+	_, err = os.Stat(filepath.Join(dir, "json", "zot"))
+	assert.Check(t, os.IsNotExist(err))
+}
+
+func TestAllKeysSecret(t *testing.T) {
+	dir := fs.NewDir(t, "secrets").Path()
+	os.Setenv("json", `
+{
+   "foo": "bar",
+   "zot": "qix"
+}`)
+	defer os.Unsetenv("json")
+
+	err := createSecretFiles(secret{
+		name: "json",
+		keys: []string{"*"},
+	}, dir)
+	assert.NilError(t, err)
+	file, err := ioutil.ReadFile(filepath.Join(dir, "json", "foo"))
+	assert.NilError(t, err)
+	content := string(file)
+	assert.Equal(t, content, "bar")
+
+	file, err = ioutil.ReadFile(filepath.Join(dir, "json", "zot"))
+	assert.NilError(t, err)
+	content = string(file)
+	assert.Equal(t, content, "qix")
+}
+
+func TestUnknownSecret(t *testing.T) {
+	dir := fs.NewDir(t, "secrets").Path()
+
+	err := createSecretFiles(secret{
+		name: "not_set",
+		keys: nil,
+	}, dir)
+	assert.Check(t, err != nil)
+}