fix: trim comments that are not modifers #371
@ -2,6 +2,7 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
@ -96,7 +97,7 @@ var appNewCommand = cli.Command{
|
|||||||
var secrets AppSecrets
|
var secrets AppSecrets
|
||||||
var secretTable *jsontable.JSONTable
|
var secretTable *jsontable.JSONTable
|
||||||
if internal.Secrets {
|
if internal.Secrets {
|
||||||
sampleEnv, err := recipe.SampleEnv()
|
sampleEnv, err := recipe.SampleEnv(config.ReadEnvOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -106,7 +107,8 @@ var appNewCommand = cli.Command{
|
|||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsConfig, err := secret.ReadSecretsConfig(sampleEnv, composeFiles, recipe.Name)
|
envSamplePath := path.Join(config.RECIPES_DIR, recipe.Name, ".env.sample")
|
||||||
|
secretsConfig, err := secret.ReadSecretsConfig(envSamplePath, composeFiles, recipe.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ var appSecretGenerateCommand = cli.Command{
|
|||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsConfig, err := secret.ReadSecretsConfig(app.Env, composeFiles, app.Recipe)
|
secretsConfig, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.Recipe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -276,7 +276,7 @@ Example:
|
|||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsConfig, err := secret.ReadSecretsConfig(app.Env, composeFiles, app.Recipe)
|
secretsConfig, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.Recipe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ func UpdateTag(pattern, image, tag, recipeName string) (bool, error) {
|
|||||||
opts := stack.Deploy{Composefiles: []string{composeFile}}
|
opts := stack.Deploy{Composefiles: []string{composeFile}}
|
||||||
|
|
||||||
envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample")
|
envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample")
|
||||||
sampleEnv, err := config.ReadEnv(envSamplePath)
|
sampleEnv, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -97,7 +97,7 @@ func UpdateLabel(pattern, serviceName, label, recipeName string) error {
|
|||||||
opts := stack.Deploy{Composefiles: []string{composeFile}}
|
opts := stack.Deploy{Composefiles: []string{composeFile}}
|
||||||
|
|
||||||
envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample")
|
envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample")
|
||||||
sampleEnv, err := config.ReadEnv(envSamplePath)
|
sampleEnv, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ func (a ByName) Less(i, j int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReadAppEnvFile(appFile AppFile, name AppName) (App, error) {
|
func ReadAppEnvFile(appFile AppFile, name AppName) (App, error) {
|
||||||
env, err := ReadEnv(appFile.Path)
|
env, err := ReadEnv(appFile.Path, ReadEnvOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return App{}, fmt.Errorf("env file for %s couldn't be read: %s", name, err.Error())
|
return App{}, fmt.Errorf("env file for %s couldn't be read: %s", name, err.Error())
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,11 @@ var REPOS_BASE_URL = "https://git.coopcloud.tech/coop-cloud"
|
|||||||
var CATALOGUE_JSON_REPO_NAME = "recipes-catalogue-json"
|
var CATALOGUE_JSON_REPO_NAME = "recipes-catalogue-json"
|
||||||
var SSH_URL_TEMPLATE = "ssh://git@git.coopcloud.tech:2222/coop-cloud/%s.git"
|
var SSH_URL_TEMPLATE = "ssh://git@git.coopcloud.tech:2222/coop-cloud/%s.git"
|
||||||
|
|
||||||
|
// envVarModifiers is a list of env var modifier strings. These are added to
|
||||||
|
// env vars as comments and modify their processing by Abra, e.g. determining
|
||||||
|
// how long secrets should be.
|
||||||
|
var envVarModifiers = []string{"length"}
|
||||||
|
|
||||||
// GetServers retrieves all servers.
|
// GetServers retrieves all servers.
|
||||||
func GetServers() ([]string, error) {
|
func GetServers() ([]string, error) {
|
||||||
var servers []string
|
var servers []string
|
||||||
@ -50,18 +55,43 @@ func GetServers() ([]string, error) {
|
|||||||
return servers, nil
|
return servers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadEnv loads an app envivornment into a map.
|
// ReadEnvOptions modifies the ReadEnv processing of env vars.
|
||||||
func ReadEnv(filePath string) (AppEnv, error) {
|
type ReadEnvOptions struct {
|
||||||
var envFile AppEnv
|
IncludeModifiers bool
|
||||||
|
}
|
||||||
|
|
||||||
envFile, err := godotenv.Read(filePath)
|
// 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) {
|
||||||
|
var envVars AppEnv
|
||||||
|
|
||||||
|
envVars, err := godotenv.Read(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("read %s from %s", envFile, filePath)
|
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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return envFile, nil
|
logrus.Debugf("read %s from %s", envVars, filePath)
|
||||||
|
|
||||||
|
return envVars, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadServerNames retrieves all server names.
|
// ReadServerNames retrieves all server names.
|
||||||
@ -197,7 +227,7 @@ func CheckEnv(app App) ([]EnvVar, error) {
|
|||||||
return envVars, err
|
return envVars, err
|
||||||
}
|
}
|
||||||
|
|
||||||
envSample, err := ReadEnv(envSamplePath)
|
envSample, err := ReadEnv(envSamplePath, ReadEnvOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return envVars, err
|
return envVars, err
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ func TestGetAllFilesInDirectory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReadEnv(t *testing.T) {
|
func TestReadEnv(t *testing.T) {
|
||||||
env, err := config.ReadEnv(ExpectedAppFile.Path)
|
env, err := config.ReadEnv(ExpectedAppFile.Path, config.ReadEnvOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -123,7 +123,7 @@ func TestCheckEnv(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample")
|
envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample")
|
||||||
envSample, err := config.ReadEnv(envSamplePath)
|
envSample, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -157,7 +157,7 @@ func TestCheckEnvError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample")
|
envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample")
|
||||||
envSample, err := config.ReadEnv(envSamplePath)
|
envSample, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -184,3 +184,63 @@ 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) {
|
||||||
|
offline := true
|
||||||
|
r, err := recipe.Get("abra-test-recipe", offline)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample")
|
||||||
|
envSample, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
envVar, exists := envSample["WITH_COMMENT"]
|
||||||
|
if !exists {
|
||||||
|
t.Fatal("WITH_COMMENT env var should be present in .env.sample")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(envVar, "should be removed") {
|
||||||
|
t.Fatalf("comment from '%s' should be removed", envVar)
|
||||||
|
}
|
||||||
|
|
||||||
|
envVar, exists = envSample["SECRET_TEST_PASS_TWO_VERSION"]
|
||||||
|
if !exists {
|
||||||
|
t.Fatal("WITH_COMMENT env var should be present in .env.sample")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(envVar, "length") {
|
||||||
|
t.Fatal("comment from env var SECRET_TEST_PASS_TWO_VERSION should have been removed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvVarModifiersIncluded(t *testing.T) {
|
||||||
|
offline := true
|
||||||
|
r, err := recipe.Get("abra-test-recipe", offline)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample")
|
||||||
|
envSample, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{IncludeModifiers: true})
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -227,7 +227,7 @@ func LintAppService(recipe recipe.Recipe) (bool, error) {
|
|||||||
// therefore no matching traefik deploy label will be present.
|
// therefore no matching traefik deploy label will be present.
|
||||||
func LintTraefikEnabledSkipCondition(recipe recipe.Recipe) (bool, error) {
|
func LintTraefikEnabledSkipCondition(recipe recipe.Recipe) (bool, error) {
|
||||||
envSamplePath := path.Join(config.RECIPES_DIR, recipe.Name, ".env.sample")
|
envSamplePath := path.Join(config.RECIPES_DIR, recipe.Name, ".env.sample")
|
||||||
sampleEnv, err := config.ReadEnv(envSamplePath)
|
sampleEnv, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("Unable to discover .env.sample for %s", recipe.Name)
|
return false, fmt.Errorf("Unable to discover .env.sample for %s", recipe.Name)
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,7 @@ func Get(recipeName string, offline bool) (Recipe, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample")
|
envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample")
|
||||||
sampleEnv, err := config.ReadEnv(envSamplePath)
|
sampleEnv, err := config.ReadEnv(envSamplePath, config.ReadEnvOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Recipe{}, err
|
return Recipe{}, err
|
||||||
}
|
}
|
||||||
@ -249,9 +249,9 @@ func Get(recipeName string, offline bool) (Recipe, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Recipe) SampleEnv() (map[string]string, error) {
|
func (r Recipe) SampleEnv(opts config.ReadEnvOptions) (map[string]string, error) {
|
||||||
envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample")
|
envSamplePath := path.Join(config.RECIPES_DIR, r.Name, ".env.sample")
|
||||||
sampleEnv, err := config.ReadEnv(envSamplePath)
|
sampleEnv, err := config.ReadEnv(envSamplePath, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sampleEnv, fmt.Errorf("unable to discover .env.sample for %s", r.Name)
|
return sampleEnv, fmt.Errorf("unable to discover .env.sample for %s", r.Name)
|
||||||
}
|
}
|
||||||
|
@ -69,9 +69,14 @@ func GeneratePassphrases(count uint) ([]string, error) {
|
|||||||
// and some times you don't (as the caller). We need to be able to handle the
|
// 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"
|
// "app new" case where we pass in the .env.sample and the "secret generate"
|
||||||
// case where the app is created.
|
// case where the app is created.
|
||||||
func ReadSecretsConfig(appEnv map[string]string, composeFiles []string, recipeName string) (map[string]string, error) {
|
func ReadSecretsConfig(appEnvPath string, composeFiles []string, recipeName string) (map[string]string, error) {
|
||||||
secretConfigs := make(map[string]string)
|
secretConfigs := make(map[string]string)
|
||||||
|
|
||||||
|
appEnv, err := config.ReadEnv(appEnvPath, config.ReadEnvOptions{IncludeModifiers: true})
|
||||||
|
if err != nil {
|
||||||
|
return secretConfigs, err
|
||||||
|
}
|
||||||
|
|
||||||
opts := stack.Deploy{Composefiles: composeFiles}
|
opts := stack.Deploy{Composefiles: composeFiles}
|
||||||
config, err := loader.LoadComposefile(opts, appEnv)
|
config, err := loader.LoadComposefile(opts, appEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -232,7 +237,7 @@ func PollSecretsStatus(cl *dockerClient.Client, app config.App) (secretStatuses,
|
|||||||
return secStats, err
|
return secStats, err
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsConfig, err := ReadSecretsConfig(app.Env, composeFiles, app.Recipe)
|
secretsConfig, err := ReadSecretsConfig(app.Path, composeFiles, app.Recipe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return secStats, err
|
return secStats, err
|
||||||
}
|
}
|
||||||
|
@ -18,13 +18,14 @@ func TestReadSecretsConfig(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sampleEnv, err := recipe.SampleEnv()
|
sampleEnv, err := recipe.SampleEnv(config.ReadEnvOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
composeFiles := []string{path.Join(config.RECIPES_DIR, recipe.Name, "compose.yml")}
|
composeFiles := []string{path.Join(config.RECIPES_DIR, recipe.Name, "compose.yml")}
|
||||||
secretsFromConfig, err := ReadSecretsConfig(sampleEnv, composeFiles, recipe.Name)
|
envSamplePath := path.Join(config.RECIPES_DIR, recipe.Name, ".env.sample")
|
||||||
|
secretsFromConfig, err := ReadSecretsConfig(envSamplePath, composeFiles, recipe.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -420,7 +420,7 @@ func convertServiceSecrets(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE(d1): strip # length=... modifiers
|
// NOTE(d1): strip all comments
|
||||||
if strings.Contains(obj.Name, "#") {
|
if strings.Contains(obj.Name, "#") {
|
||||||
vals := strings.Split(obj.Name, "#")
|
vals := strings.Split(obj.Name, "#")
|
||||||
obj.Name = strings.TrimSpace(vals[0])
|
obj.Name = strings.TrimSpace(vals[0])
|
||||||
|
@ -344,3 +344,12 @@ teardown(){
|
|||||||
|
|
||||||
_reset_app
|
_reset_app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "recipe config comments not present in values" {
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input
|
||||||
|
assert_success
|
||||||
|
|
||||||
|
run $ABRA app run "$TEST_APP_DOMAIN" app env
|
||||||
|
assert_success
|
||||||
|
refute_output --partial 'should be removed'
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user