From 3957b7c965bb973d6f254599350d817053332e95 Mon Sep 17 00:00:00 2001 From: p4u1 Date: Wed, 29 Nov 2023 18:35:01 +0100 Subject: [PATCH] proper env modifiers support This implements proper modifier support in the env file using this new fork of the godotenv library. The modifier implementation is quite basic for but can be improved later if needed. See this commit for the actual implementation. Because we are now using proper modifer parsing, it does not affect the parsing of value, so this is possible again: ``` MY_VAR="#foo" ``` Closes coop-cloud/organising#535 --- cli/app/new.go | 6 +- cli/app/secret.go | 61 +++++++++---------- go.mod | 2 +- go.sum | 4 +- pkg/compose/compose.go | 4 +- pkg/config/app.go | 5 +- pkg/config/env.go | 47 ++++++--------- pkg/config/env_test.go | 51 ++++++++-------- pkg/lint/recipe.go | 2 +- pkg/recipe/recipe.go | 6 +- pkg/secret/secret.go | 111 +++++++++++++++-------------------- pkg/secret/secret_test.go | 2 +- pkg/upstream/stack/loader.go | 13 +++- 13 files changed, 146 insertions(+), 168 deletions(-) diff --git a/cli/app/new.go b/cli/app/new.go index 6ff12e6e..975ca502 100644 --- a/cli/app/new.go +++ b/cli/app/new.go @@ -97,7 +97,7 @@ var appNewCommand = cli.Command{ var secrets AppSecrets var secretTable *jsontable.JSONTable if internal.Secrets { - sampleEnv, err := recipe.SampleEnv(config.ReadEnvOptions{}) + sampleEnv, err := recipe.SampleEnv() if err != nil { logrus.Fatal(err) } @@ -168,7 +168,7 @@ var appNewCommand = cli.Command{ type AppSecrets map[string]string // createSecrets creates all secrets for a new app. -func createSecrets(cl *dockerClient.Client, secretsConfig map[string]string, sanitisedAppName string) (AppSecrets, error) { +func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.SecretValue, sanitisedAppName string) (AppSecrets, error) { // NOTE(d1): trim to match app.StackName() implementation if len(sanitisedAppName) > 45 { logrus.Debugf("trimming %s to %s to avoid runtime limits", sanitisedAppName, sanitisedAppName[:45]) @@ -217,7 +217,7 @@ func ensureDomainFlag(recipe recipe.Recipe, server string) error { } // promptForSecrets asks if we should generate secrets for a new app. -func promptForSecrets(recipeName string, secretsConfig map[string]string) error { +func promptForSecrets(recipeName string, secretsConfig map[string]secret.SecretValue) error { if len(secretsConfig) == 0 { logrus.Debugf("%s has no secrets to generate, skipping...", recipeName) return nil diff --git a/cli/app/secret.go b/cli/app/secret.go index c0bf460b..2b2f7fd4 100644 --- a/cli/app/secret.go +++ b/cli/app/secret.go @@ -20,19 +20,23 @@ import ( "github.com/urfave/cli" ) -var allSecrets bool -var allSecretsFlag = &cli.BoolFlag{ - Name: "all, a", - Destination: &allSecrets, - Usage: "Generate all secrets", -} +var ( + allSecrets bool + allSecretsFlag = &cli.BoolFlag{ + Name: "all, a", + Destination: &allSecrets, + Usage: "Generate all secrets", + } +) -var rmAllSecrets bool -var rmAllSecretsFlag = &cli.BoolFlag{ - Name: "all, a", - Destination: &rmAllSecrets, - Usage: "Remove all secrets", -} +var ( + rmAllSecrets bool + rmAllSecretsFlag = &cli.BoolFlag{ + Name: "all, a", + Destination: &rmAllSecrets, + Usage: "Remove all secrets", + } +) var appSecretGenerateCommand = cli.Command{ Name: "generate", @@ -87,28 +91,22 @@ var appSecretGenerateCommand = cli.Command{ logrus.Fatal(err) } - secretsConfig, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.Recipe) + secrets, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.Recipe) if err != nil { logrus.Fatal(err) } - secretsToCreate := make(map[string]string) - if allSecrets { - secretsToCreate = secretsConfig - } else { + if !allSecrets { secretName := c.Args().Get(1) secretVersion := c.Args().Get(2) - matches := false - for name := range secretsConfig { - if secretName == name { - secretsToCreate[name] = secretVersion - matches = true - } - } - - if !matches { + s, ok := secrets[secretName] + if !ok { logrus.Fatalf("%s doesn't exist in the env config?", secretName) } + s.Version = secretVersion + secrets = map[string]secret.SecretValue{ + secretName: s, + } } cl, err := client.New(app.Server) @@ -116,7 +114,7 @@ var appSecretGenerateCommand = cli.Command{ logrus.Fatal(err) } - secretVals, err := secret.GenerateSecrets(cl, secretsToCreate, app.StackName(), app.Server) + secretVals, err := secret.GenerateSecrets(cl, secrets, app.StackName(), app.Server) if err != nil { logrus.Fatal(err) } @@ -276,7 +274,7 @@ Example: logrus.Fatal(err) } - secretsConfig, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.Recipe) + secrets, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.Recipe) if err != nil { logrus.Fatal(err) } @@ -311,12 +309,7 @@ Example: match := false secretToRm := c.Args().Get(1) - for secretName, secretValue := range secretsConfig { - val, err := secret.ParseSecretValue(secretValue) - if err != nil { - logrus.Fatal(err) - } - + for secretName, val := range secrets { secretRemoteName := fmt.Sprintf("%s_%s_%s", app.StackName(), secretName, val.Version) if _, ok := remoteSecretNames[secretRemoteName]; ok { if secretToRm != "" { diff --git a/go.mod b/go.mod index cda0c18f..357d57dc 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.21 require ( coopcloud.tech/tagcmp v0.0.0-20211103052201-885b22f77d52 + git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100106-7462d91acefd github.com/AlecAivazis/survey/v2 v2.3.7 - github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731094149-b031ea1211e7 github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4 github.com/docker/cli v24.0.7+incompatible github.com/docker/distribution v2.8.3+incompatible diff --git a/go.sum b/go.sum index ac4beb16..92d767ac 100644 --- a/go.sum +++ b/go.sum @@ -51,12 +51,12 @@ coopcloud.tech/tagcmp v0.0.0-20211103052201-885b22f77d52/go.mod h1:ESVm0wQKcbcFi dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100106-7462d91acefd h1:dctCkMhcsgIWMrkB1Br8S0RJF17eG+LKiqcXXVr3mdU= +git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100106-7462d91acefd/go.mod h1:Q8V1zbtPAlzYSr/Dvky3wS6x58IQAl3rot2me1oSO2Q= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1/go.mod h1:VzwV+t+dZ9j/H867F1M2ziD+yLHtB46oM35FxxMJ4d0= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= -github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731094149-b031ea1211e7 h1:asQtdXYbxEYWcwAQqJTVYC/RltB4eqoWKvqWg/LFPOg= -github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731094149-b031ea1211e7/go.mod h1:oZRCMMRS318l07ei4DTqbZoOawfJlJ4yyo8juk2v4Rk= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go index 86c4dfb4..3fd98191 100644 --- a/pkg/compose/compose.go +++ b/pkg/compose/compose.go @@ -29,7 +29,7 @@ func UpdateTag(pattern, image, tag, recipeName string) (bool, error) { opts := stack.Deploy{Composefiles: []string{composeFile}} envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample") - sampleEnv, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{}) + sampleEnv, err := config.ReadEnv(envSamplePath) if err != nil { return false, err } @@ -97,7 +97,7 @@ func UpdateLabel(pattern, serviceName, label, recipeName string) error { opts := stack.Deploy{Composefiles: []string{composeFile}} envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample") - sampleEnv, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{}) + sampleEnv, err := config.ReadEnv(envSamplePath) if err != nil { return err } diff --git a/pkg/config/app.go b/pkg/config/app.go index c5426786..996c5357 100644 --- a/pkg/config/app.go +++ b/pkg/config/app.go @@ -25,6 +25,9 @@ import ( // AppEnv is a map of the values in an apps env config type AppEnv = map[string]string +// AppModifiers is a map of modifiers in an apps env config +type AppModifiers = map[string]map[string]string + // AppName is AppName type AppName = string @@ -150,7 +153,7 @@ func (a ByName) Less(i, j int) bool { } func ReadAppEnvFile(appFile AppFile, name AppName) (App, error) { - env, err := ReadEnv(appFile.Path, ReadEnvOptions{}) + env, err := ReadEnv(appFile.Path) if err != nil { return App{}, fmt.Errorf("env file for %s couldn't be read: %s", name, err.Error()) } diff --git a/pkg/config/env.go b/pkg/config/env.go index 202c29a8..62f6a71d 100644 --- a/pkg/config/env.go +++ b/pkg/config/env.go @@ -12,7 +12,7 @@ import ( "sort" "strings" - "github.com/Autonomic-Cooperative/godotenv" + "git.coopcloud.tech/coop-cloud/godotenv" "github.com/sirupsen/logrus" ) @@ -55,45 +55,34 @@ func GetServers() ([]string, error) { return servers, nil } -// ReadEnvOptions modifies the ReadEnv processing of env vars. -type ReadEnvOptions struct { - IncludeModifiers bool -} - -// ContainsEnvVarModifier determines if an env var contains a modifier. -func ContainsEnvVarModifier(envVar string) bool { - for _, mod := range envVarModifiers { - if strings.Contains(envVar, fmt.Sprintf("%s=", mod)) { - return true - } - } - return false -} - // ReadEnv loads an app envivornment into a map. -func ReadEnv(filePath string, opts ReadEnvOptions) (AppEnv, error) { +func ReadEnv(filePath string) (AppEnv, error) { var envVars AppEnv - envVars, err := godotenv.Read(filePath) + envVars, _, err := godotenv.Read(filePath) if err != nil { return nil, err } - // for idx, envVar := range envVars { - // if strings.Contains(envVar, "#") { - // if opts.IncludeModifiers && ContainsEnvVarModifier(envVar) { - // continue - // } - // vals := strings.Split(envVar, "#") - // envVars[idx] = strings.TrimSpace(vals[0]) - // } - // } - logrus.Debugf("read %s from %s", envVars, filePath) return envVars, nil } +// ReadEnv loads an app envivornment and their modifiers in two different maps. +func ReadEnvWithModifiers(filePath string) (AppEnv, AppModifiers, error) { + var envVars AppEnv + + envVars, mods, err := godotenv.Read(filePath) + if err != nil { + return nil, mods, err + } + + logrus.Debugf("read %s from %s", envVars, filePath) + + return envVars, mods, nil +} + // ReadServerNames retrieves all server names. func ReadServerNames() ([]string, error) { serverNames, err := GetAllFoldersInDirectory(SERVERS_DIR) @@ -227,7 +216,7 @@ func CheckEnv(app App) ([]EnvVar, error) { return envVars, err } - envSample, err := ReadEnv(envSamplePath, ReadEnvOptions{}) + envSample, err := ReadEnv(envSamplePath) if err != nil { return envVars, err } diff --git a/pkg/config/env_test.go b/pkg/config/env_test.go index 86fc4cb7..f0626e6b 100644 --- a/pkg/config/env_test.go +++ b/pkg/config/env_test.go @@ -13,15 +13,21 @@ import ( "coopcloud.tech/abra/pkg/recipe" ) -var TestFolder = os.ExpandEnv("$PWD/../../tests/resources/test_folder") -var ValidAbraConf = os.ExpandEnv("$PWD/../../tests/resources/valid_abra_config") +var ( + TestFolder = os.ExpandEnv("$PWD/../../tests/resources/test_folder") + ValidAbraConf = os.ExpandEnv("$PWD/../../tests/resources/valid_abra_config") +) // make sure these are in alphabetical order -var TFolders = []string{"folder1", "folder2"} -var TFiles = []string{"bar.env", "foo.env"} +var ( + TFolders = []string{"folder1", "folder2"} + TFiles = []string{"bar.env", "foo.env"} +) -var AppName = "ecloud" -var ServerName = "evil.corp" +var ( + AppName = "ecloud" + ServerName = "evil.corp" +) var ExpectedAppEnv = config.AppEnv{ "DOMAIN": "ecloud.evil.corp", @@ -71,7 +77,7 @@ func TestGetAllFilesInDirectory(t *testing.T) { } func TestReadEnv(t *testing.T) { - env, err := config.ReadEnv(ExpectedAppFile.Path, config.ReadEnvOptions{}) + env, err := config.ReadEnv(ExpectedAppFile.Path) if err != nil { t.Fatal(err) } @@ -149,7 +155,7 @@ func TestCheckEnv(t *testing.T) { } envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample") - envSample, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{}) + envSample, err := config.ReadEnv(envSamplePath) if err != nil { t.Fatal(err) } @@ -183,7 +189,7 @@ func TestCheckEnvError(t *testing.T) { } envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample") - envSample, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{}) + envSample, err := config.ReadEnv(envSamplePath) if err != nil { t.Fatal(err) } @@ -211,19 +217,7 @@ func TestCheckEnvError(t *testing.T) { } } -func TestContainsEnvVarModifier(t *testing.T) { - if ok := config.ContainsEnvVarModifier("FOO=bar # bing"); ok { - t.Fatal("FOO contains no env var modifier") - } - - if ok := config.ContainsEnvVarModifier("FOO=bar # length=3"); !ok { - t.Fatal("FOO contains an env var modifier (length)") - } -} - func TestEnvVarCommentsRemoved(t *testing.T) { - t.Skip("https://git.coopcloud.tech/coop-cloud/organising/issues/535") - offline := true r, err := recipe.Get("abra-test-recipe", offline) if err != nil { @@ -231,7 +225,7 @@ func TestEnvVarCommentsRemoved(t *testing.T) { } envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample") - envSample, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{}) + envSample, err := config.ReadEnv(envSamplePath) if err != nil { t.Fatal(err) } @@ -263,12 +257,19 @@ func TestEnvVarModifiersIncluded(t *testing.T) { } envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample") - envSample, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{IncludeModifiers: true}) + envSample, modifiers, err := config.ReadEnvWithModifiers(envSamplePath) if err != nil { t.Fatal(err) } - if !strings.Contains(envSample["SECRET_TEST_PASS_TWO_VERSION"], "length") { - t.Fatal("comment from env var SECRET_TEST_PASS_TWO_VERSION should not be removed") + if !strings.Contains(envSample["SECRET_TEST_PASS_TWO_VERSION"], "v1") { + t.Errorf("value should be 'v1', got: '%s'", envSample["SECRET_TEST_PASS_TWO_VERSION"]) + } + if modifiers == nil || modifiers["SECRET_TEST_PASS_TWO_VERSION"] == nil { + t.Errorf("no modifiers included") + } else { + if modifiers["SECRET_TEST_PASS_TWO_VERSION"]["length"] != "10" { + t.Errorf("length modifier should be '10', got: '%s'", modifiers["SECRET_TEST_PASS_TWO_VERSION"]["length"]) + } } } diff --git a/pkg/lint/recipe.go b/pkg/lint/recipe.go index e9214104..cae797f3 100644 --- a/pkg/lint/recipe.go +++ b/pkg/lint/recipe.go @@ -227,7 +227,7 @@ func LintAppService(recipe recipe.Recipe) (bool, error) { // therefore no matching traefik deploy label will be present. func LintTraefikEnabledSkipCondition(recipe recipe.Recipe) (bool, error) { envSamplePath := path.Join(config.RECIPES_DIR, recipe.Name, ".env.sample") - sampleEnv, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{}) + sampleEnv, err := config.ReadEnv(envSamplePath) if err != nil { return false, fmt.Errorf("Unable to discover .env.sample for %s", recipe.Name) } diff --git a/pkg/recipe/recipe.go b/pkg/recipe/recipe.go index 6e47e029..dbd4a520 100644 --- a/pkg/recipe/recipe.go +++ b/pkg/recipe/recipe.go @@ -227,7 +227,7 @@ func Get(recipeName string, offline bool) (Recipe, error) { } envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample") - sampleEnv, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{}) + sampleEnv, err := config.ReadEnv(envSamplePath) if err != nil { return Recipe{}, err } @@ -255,9 +255,9 @@ func Get(recipeName string, offline bool) (Recipe, error) { }, nil } -func (r Recipe) SampleEnv(opts config.ReadEnvOptions) (map[string]string, error) { +func (r Recipe) SampleEnv() (map[string]string, error) { envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample") - sampleEnv, err := config.ReadEnv(envSamplePath, opts) + sampleEnv, err := config.ReadEnv(envSamplePath) if err != nil { return sampleEnv, fmt.Errorf("unable to discover .env.sample for %s", r.Name) } diff --git a/pkg/secret/secret.go b/pkg/secret/secret.go index aa5262d2..947808e7 100644 --- a/pkg/secret/secret.go +++ b/pkg/secret/secret.go @@ -21,9 +21,9 @@ import ( "github.com/sirupsen/logrus" ) -// secretValue represents a parsed `SECRET_FOO=v1 # length=bar` env var config +// SecretValue represents a parsed `SECRET_FOO=v1 # length=bar` env var config // secret definition. -type secretValue struct { +type SecretValue struct { Version string Length int } @@ -35,7 +35,6 @@ func GeneratePasswords(count, length uint) ([]string, error) { length, passgen.AlphabetDefault, ) - if err != nil { return nil, err } @@ -54,7 +53,6 @@ func GeneratePassphrases(count uint) ([]string, error) { passgen.PassphraseCasingDefault, passgen.WordListDefault, ) - if err != nil { return nil, err } @@ -69,18 +67,20 @@ func GeneratePassphrases(count uint) ([]string, error) { // and some times you don't (as the caller). We need to be able to handle the // "app new" case where we pass in the .env.sample and the "secret generate" // case where the app is created. -func ReadSecretsConfig(appEnvPath string, composeFiles []string, recipeName string) (map[string]string, error) { - secretConfigs := make(map[string]string) - - appEnv, err := config.ReadEnv(appEnvPath, config.ReadEnvOptions{IncludeModifiers: true}) +func ReadSecretsConfig(appEnvPath string, composeFiles []string, recipeName string) (map[string]SecretValue, error) { + appEnv, appModifiers, err := config.ReadEnvWithModifiers(appEnvPath) if err != nil { - return secretConfigs, err + return nil, err } opts := stack.Deploy{Composefiles: composeFiles} config, err := loader.LoadComposefile(opts, appEnv) if err != nil { - return secretConfigs, err + return nil, err + } + configWithoutEnv, err := loader.LoadComposefile(opts, map[string]string{}, loader.SkipInterpolation) + if err != nil { + return nil, err } var enabledSecrets []string @@ -92,12 +92,13 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, recipeName stri if len(enabledSecrets) == 0 { logrus.Debugf("not generating app secrets, none enabled in recipe config") - return secretConfigs, nil + return nil, nil } + secretValues := map[string]SecretValue{} for secretId, secretConfig := range config.Secrets { if string(secretConfig.Name[len(secretConfig.Name)-1]) == "_" { - return secretConfigs, fmt.Errorf("missing version for secret? (%s)", secretId) + return nil, fmt.Errorf("missing version for secret? (%s)", secretId) } if !(slices.Contains(enabledSecrets, secretId)) { @@ -107,60 +108,47 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, recipeName stri lastIdx := strings.LastIndex(secretConfig.Name, "_") secretVersion := secretConfig.Name[lastIdx+1:] - secretConfigs[secretId] = secretVersion + value := SecretValue{Version: secretVersion} + + // Check if the length modifier is set for this secret. + for k, v := range appModifiers { + // configWithoutEnv contains the raw name as defined in the compose.yaml + if !strings.Contains(configWithoutEnv.Secrets[secretId].Name, k) { + continue + } + lengthRaw, ok := v["length"] + if ok { + length, err := strconv.Atoi(lengthRaw) + if err != nil { + return nil, err + } + value.Length = length + } + break + } + secretValues[secretId] = value } - return secretConfigs, nil -} - -func ParseSecretValue(secret string) (secretValue, error) { - values := strings.Split(secret, "#") - if len(values) == 0 { - return secretValue{}, fmt.Errorf("unable to parse %s", secret) - } - - if len(values) == 1 { - return secretValue{Version: values[0], Length: 0}, nil - } - - split := strings.Split(values[1], "=") - parsed := split[len(split)-1] - stripped := strings.ReplaceAll(parsed, " ", "") - length, err := strconv.Atoi(stripped) - if err != nil { - return secretValue{}, err - } - version := strings.ReplaceAll(values[0], " ", "") - - logrus.Debugf("parsed version %s and length '%v' from %s", version, length, secret) - - return secretValue{Version: version, Length: length}, nil + return secretValues, nil } // GenerateSecrets generates secrets locally and sends them to a remote server for storage. -func GenerateSecrets(cl *dockerClient.Client, secretsFromConfig map[string]string, appName, server string) (map[string]string, error) { - secrets := make(map[string]string) - +func GenerateSecrets(cl *dockerClient.Client, secrets map[string]SecretValue, appName, server string) (map[string]string, error) { + secretsGenerated := map[string]string{} var mutex sync.Mutex var wg sync.WaitGroup - ch := make(chan error, len(secretsFromConfig)) - for n, v := range secretsFromConfig { + ch := make(chan error, len(secrets)) + for n, v := range secrets { wg.Add(1) - go func(secretName, secretValue string) { + go func(secretName string, secret SecretValue) { defer wg.Done() - parsedSecretValue, err := ParseSecretValue(secretValue) - if err != nil { - ch <- err - return - } - - secretRemoteName := fmt.Sprintf("%s_%s_%s", appName, secretName, parsedSecretValue.Version) + secretRemoteName := fmt.Sprintf("%s_%s_%s", appName, secretName, secret.Version) logrus.Debugf("attempting to generate and store %s on %s", secretRemoteName, server) - if parsedSecretValue.Length > 0 { - passwords, err := GeneratePasswords(1, uint(parsedSecretValue.Length)) + if secret.Length > 0 { + passwords, err := GeneratePasswords(1, uint(secret.Length)) if err != nil { ch <- err return @@ -178,7 +166,7 @@ func GenerateSecrets(cl *dockerClient.Client, secretsFromConfig map[string]strin mutex.Lock() defer mutex.Unlock() - secrets[secretName] = passwords[0] + secretsGenerated[secretName] = passwords[0] } else { passphrases, err := GeneratePassphrases(1) if err != nil { @@ -198,7 +186,7 @@ func GenerateSecrets(cl *dockerClient.Client, secretsFromConfig map[string]strin mutex.Lock() defer mutex.Unlock() - secrets[secretName] = passphrases[0] + secretsGenerated[secretName] = passphrases[0] } ch <- nil }(n, v) @@ -206,16 +194,16 @@ func GenerateSecrets(cl *dockerClient.Client, secretsFromConfig map[string]strin wg.Wait() - for range secretsFromConfig { + for range secrets { err := <-ch if err != nil { return nil, err } } - logrus.Debugf("generated and stored %s on %s", secrets, server) + logrus.Debugf("generated and stored %v on %s", secrets, server) - return secrets, nil + return secretsGenerated, nil } type secretStatus struct { @@ -257,14 +245,9 @@ func PollSecretsStatus(cl *dockerClient.Client, app config.App) (secretStatuses, remoteSecretNames[cont.Spec.Annotations.Name] = true } - for secretName, secretValue := range secretsConfig { + for secretName, val := range secretsConfig { createdRemote := false - val, err := ParseSecretValue(secretValue) - if err != nil { - return secStats, err - } - secretRemoteName := fmt.Sprintf("%s_%s_%s", app.StackName(), secretName, val.Version) if _, ok := remoteSecretNames[secretRemoteName]; ok { createdRemote = true diff --git a/pkg/secret/secret_test.go b/pkg/secret/secret_test.go index a4b0fc0c..bb462a8a 100644 --- a/pkg/secret/secret_test.go +++ b/pkg/secret/secret_test.go @@ -18,7 +18,7 @@ func TestReadSecretsConfig(t *testing.T) { t.Fatal(err) } - sampleEnv, err := recipe.SampleEnv(config.ReadEnvOptions{}) + sampleEnv, err := recipe.SampleEnv() if err != nil { t.Fatal(err) } diff --git a/pkg/upstream/stack/loader.go b/pkg/upstream/stack/loader.go index 51b678b0..9790903f 100644 --- a/pkg/upstream/stack/loader.go +++ b/pkg/upstream/stack/loader.go @@ -18,15 +18,24 @@ func DontSkipValidation(opts *loader.Options) { opts.SkipValidation = false } +// SkipInterpolation skip interpolating environment variables. +func SkipInterpolation(opts *loader.Options) { + opts.SkipInterpolation = true +} + // LoadComposefile parse the composefile specified in the cli and returns its Config and version. -func LoadComposefile(opts Deploy, appEnv map[string]string) (*composetypes.Config, error) { +func LoadComposefile(opts Deploy, appEnv map[string]string, options ...func(*loader.Options)) (*composetypes.Config, error) { configDetails, err := getConfigDetails(opts.Composefiles, appEnv) if err != nil { return nil, err } + if options == nil { + options = []func(*loader.Options){DontSkipValidation} + } + dicts := getDictsFrom(configDetails.ConfigFiles) - config, err := loader.Load(configDetails, DontSkipValidation) + config, err := loader.Load(configDetails, options...) if err != nil { if fpe, ok := err.(*loader.ForbiddenPropertiesError); ok { return nil, fmt.Errorf("compose file contains unsupported options: %s",