forked from toolshed/abra
		
	@ -1,13 +1,10 @@
 | 
			
		||||
package app
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/autocomplete"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/formatter"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
@ -15,9 +12,21 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var appCheckCommand = cli.Command{
 | 
			
		||||
	Name:      "check",
 | 
			
		||||
	Aliases:   []string{"chk"},
 | 
			
		||||
	Usage:     "Check if an app is configured correctly",
 | 
			
		||||
	Name:    "check",
 | 
			
		||||
	Aliases: []string{"chk"},
 | 
			
		||||
	Usage:   "Ensure an app is well configured",
 | 
			
		||||
	Description: `
 | 
			
		||||
This command compares env vars in both the app ".env" and recipe ".env.sample"
 | 
			
		||||
file.
 | 
			
		||||
 | 
			
		||||
The goal is to ensure that recipe ".env.sample" env vars are defined in your
 | 
			
		||||
app ".env" file. Only env var definitions in the ".env.sample" which are
 | 
			
		||||
uncommented, e.g. "FOO=bar" are checked. If an app ".env" file does not include
 | 
			
		||||
these env vars, then "check" will complain.
 | 
			
		||||
 | 
			
		||||
Recipe maintainers may or may not provide defaults for env vars within their
 | 
			
		||||
recipes regardless of commenting or not (e.g. through the use of
 | 
			
		||||
${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
 | 
			
		||||
	ArgsUsage: "<domain>",
 | 
			
		||||
	Flags: []cli.Flag{
 | 
			
		||||
		internal.DebugFlag,
 | 
			
		||||
@ -49,32 +58,23 @@ var appCheckCommand = cli.Command{
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		envSamplePath := path.Join(config.RECIPES_DIR, app.Recipe, ".env.sample")
 | 
			
		||||
		if _, err := os.Stat(envSamplePath); err != nil {
 | 
			
		||||
			if os.IsNotExist(err) {
 | 
			
		||||
				logrus.Fatalf("%s does not exist?", envSamplePath)
 | 
			
		||||
			}
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		tableCol := []string{"recipe env sample", "app env"}
 | 
			
		||||
		table := formatter.CreateTable(tableCol)
 | 
			
		||||
 | 
			
		||||
		envSample, err := config.ReadEnv(envSamplePath)
 | 
			
		||||
		envVars, err := config.CheckEnv(app)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var missing []string
 | 
			
		||||
		for k := range envSample {
 | 
			
		||||
			if _, ok := app.Env[k]; !ok {
 | 
			
		||||
				missing = append(missing, k)
 | 
			
		||||
		for _, envVar := range envVars {
 | 
			
		||||
			if envVar.Present {
 | 
			
		||||
				table.Append([]string{envVar.Name, "✅"})
 | 
			
		||||
			} else {
 | 
			
		||||
				table.Append([]string{envVar.Name, "❌"})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(missing) > 0 {
 | 
			
		||||
			missingEnvVars := strings.Join(missing, ", ")
 | 
			
		||||
			logrus.Fatalf("%s is missing %s", app.Path, missingEnvVars)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logrus.Infof("all necessary environment variables defined for %s", app.Name)
 | 
			
		||||
		table.Render()
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@ -200,6 +200,17 @@ recipes.
 | 
			
		||||
		config.SetChaosVersionLabel(compose, stackName, version)
 | 
			
		||||
		config.SetUpdateLabel(compose, stackName, app.Env)
 | 
			
		||||
 | 
			
		||||
		envVars, err := config.CheckEnv(app)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, envVar := range envVars {
 | 
			
		||||
			if !envVar.Present {
 | 
			
		||||
				logrus.Warnf("env var %s missing from %s.env, present in recipe .env.sample", envVar.Name, app.Domain)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := internal.DeployOverview(app, version, "continue with deployment?"); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -254,6 +254,17 @@ recipes.
 | 
			
		||||
		config.SetChaosVersionLabel(compose, stackName, chosenUpgrade)
 | 
			
		||||
		config.SetUpdateLabel(compose, stackName, app.Env)
 | 
			
		||||
 | 
			
		||||
		envVars, err := config.CheckEnv(app)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, envVar := range envVars {
 | 
			
		||||
			if !envVar.Present {
 | 
			
		||||
				logrus.Warnf("env var %s missing from %s.env, present in recipe .env.sample", envVar.Name, app.Domain)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := internal.NewVersionOverview(app, deployedVersion, chosenUpgrade, releaseNotes); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import (
 | 
			
		||||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/Autonomic-Cooperative/godotenv"
 | 
			
		||||
@ -179,3 +180,42 @@ func ReadAbraShEnvVars(abraSh string) (map[string]string, error) {
 | 
			
		||||
 | 
			
		||||
	return envVars, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EnvVar struct {
 | 
			
		||||
	Name    string
 | 
			
		||||
	Present bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CheckEnv(app App) ([]EnvVar, error) {
 | 
			
		||||
	var envVars []EnvVar
 | 
			
		||||
 | 
			
		||||
	envSamplePath := path.Join(RECIPES_DIR, app.Recipe, ".env.sample")
 | 
			
		||||
	if _, err := os.Stat(envSamplePath); err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return envVars, fmt.Errorf("%s does not exist?", envSamplePath)
 | 
			
		||||
		}
 | 
			
		||||
		return envVars, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	envSample, err := ReadEnv(envSamplePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return envVars, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var keys []string
 | 
			
		||||
	for key := range envSample {
 | 
			
		||||
		keys = append(keys, key)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Strings(keys)
 | 
			
		||||
 | 
			
		||||
	for _, key := range keys {
 | 
			
		||||
		if _, ok := app.Env[key]; ok {
 | 
			
		||||
			envVars = append(envVars, EnvVar{Name: key, Present: true})
 | 
			
		||||
		} else {
 | 
			
		||||
			envVars = append(envVars, EnvVar{Name: key, Present: false})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return envVars, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -114,3 +114,73 @@ func TestReadAbraShEnvVars(t *testing.T) {
 | 
			
		||||
		t.Error("OUTER_FOO should be exported")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCheckEnv(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)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	app := config.App{
 | 
			
		||||
		Name:   "test-app",
 | 
			
		||||
		Recipe: r.Name,
 | 
			
		||||
		Domain: "example.com",
 | 
			
		||||
		Env:    envSample,
 | 
			
		||||
		Path:   "example.com.env",
 | 
			
		||||
		Server: "example.com",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	envVars, err := config.CheckEnv(app)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, envVar := range envVars {
 | 
			
		||||
		if !envVar.Present {
 | 
			
		||||
			t.Fatalf("%s should be present", envVar.Name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCheckEnvError(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)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	delete(envSample, "DOMAIN")
 | 
			
		||||
 | 
			
		||||
	app := config.App{
 | 
			
		||||
		Name:   "test-app",
 | 
			
		||||
		Recipe: r.Name,
 | 
			
		||||
		Domain: "example.com",
 | 
			
		||||
		Env:    envSample,
 | 
			
		||||
		Path:   "example.com.env",
 | 
			
		||||
		Server: "example.com",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	envVars, err := config.CheckEnv(app)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, envVar := range envVars {
 | 
			
		||||
		if envVar.Name == "DOMAIN" && envVar.Present {
 | 
			
		||||
			t.Fatalf("%s should not be present", envVar.Name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user