From 9c6c8551a06bee31a82cb10b9837f2b766be8ab4 Mon Sep 17 00:00:00 2001 From: Diogo Simoes Date: Mon, 8 Jun 2020 00:07:06 +0100 Subject: [PATCH] major refactor and cleanup --- cmd/envgen.go | 183 ++--------------------------------------- cmd/log.go | 34 -------- generator/generator.go | 178 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+), 211 deletions(-) delete mode 100644 cmd/log.go create mode 100644 generator/generator.go diff --git a/cmd/envgen.go b/cmd/envgen.go index fa6a0d7..08784dc 100644 --- a/cmd/envgen.go +++ b/cmd/envgen.go @@ -1,46 +1,15 @@ package cmd import ( + "envgen/generator" "fmt" - "gopkg.in/yaml.v2" - "io/ioutil" - "os" - "strings" - "sync" - "github.com/joho/godotenv" "github.com/spf13/cobra" + "os" ) -const DefaultEnvFileName = ".env" - -type ConfBranch struct { - Name string `yaml:"name"` - Suffix string `yaml:"suffix"` -} - -type ConfPackage struct { - Package string `yaml:"package"` - EnvFile string `yaml:"envFile"` - Variables []string `yaml:"variables"` -} - -type GeneratorConfig struct { - BranchVarName string `yaml:"branchVarName"` - BranchVarDefault string `yaml:"branchVarDefault"` - Branches []ConfBranch `yaml:"branches"` - Packages []ConfPackage `yaml:"packages"` - Globals []string `yaml:"globals"` -} - -type Generator struct { - conf GeneratorConfig - branchSuffix string - globals []string -} - var rootCmd = &cobra.Command{ - Version: "2.1.0", + Version: "2.2.0", SilenceErrors: true, Use: "envgen [envFile1] ... [envFileN]", Short: "envgen generates env files for sub packages", @@ -50,8 +19,6 @@ var rootCmd = &cobra.Command{ } func Execute() { - rootCmd.SetErr(errorWriter{}) - envFiles := os.Args[2:] if len(envFiles) > 0 { err := godotenv.Load(envFiles...) @@ -67,151 +34,13 @@ func Execute() { } func runGenerator(cmd *cobra.Command, args []string) error { - gen, err := newGenerator(args[0]) + gen, err := generator.New(args[0]) if err != nil { return err } - logInfo("Starting env files generation\n") - gen.loadGlobals() - gen.generateEnvFiles() - logInfo("Finished env files generation!\n") - - return nil -} - -func newGenerator(filepath string) (*Generator, error) { - gen := &Generator{} - err := gen.loadConfig(filepath) - if err != nil { - return nil, err - } - - return gen, nil -} - -func (g *Generator) generateEnvFiles() { - var wg sync.WaitGroup - for _, p := range g.conf.Packages { - wg.Add(1) - go g.generatePackageEnvFile(p, &wg) - } - - wg.Wait() -} - -func (g *Generator) generatePackageEnvFile(pckg ConfPackage, wg *sync.WaitGroup) { - defer wg.Done() - logInfo(fmt.Sprintf("[%s] loading variables", pckg.Package)) - - packageVars, missing := getVariablesValues(pckg.Variables, g.branchSuffix) - if len(missing) > 0 { - // TODO create flag to break execution as error - logWarn(fmt.Sprintf("[%s] missing env vars: %s\n", pckg.Package, strings.Join(missing, ", "))) - } - - logInfo(fmt.Sprintf("[%s] writing env file", pckg.Package)) - - envFile := pckg.EnvFile - if envFile == "" { - envFile = DefaultEnvFileName - } - - genEnvFilePath := fmt.Sprintf("%s/%s", pckg.Package, envFile) - err := writeFile(genEnvFilePath, append(packageVars, g.globals...)) - if err != nil { - logError(err) - } - - logInfo(fmt.Sprintf("[%s] generated env file\n", pckg.Package)) -} - -// loadConfig loads the configuration from the provided yaml file -// into the instance of the Generator. It also determines the branch suffix property. -func (g *Generator) loadConfig(filepath string) error { - config := &GeneratorConfig{} - file, err := ioutil.ReadFile(filepath) - if err != nil { - return err - } - - err = yaml.Unmarshal(file, config) - if err != nil { - return err - } - - g.conf = *config - - g.branchSuffix = g.findBranchSuffix() - - return nil -} - -// findBranchSuffix determines the branch suffix to use, depending on the current CI branch -func (g *Generator) findBranchSuffix() string { - branch := getEnv(g.conf.BranchVarName, "", g.conf.BranchVarDefault) - - for _, b := range g.conf.Branches { - if b.Name == branch { - return b.Suffix - } - } - - return "" -} - -// loads global env variables (to be added on all packages) -func (g *Generator) loadGlobals() { - globals, missing := getVariablesValues(g.conf.Globals, "") - if len(missing) > 0 { - logWarn(fmt.Sprintf("[globals] missing env vars: %s\n", strings.Join(missing, ", "))) - } - - g.globals = globals -} - -// returns a string slice loaded with the env var declarations -// and an array with those not found in the environment -func getVariablesValues(envVars []string, suffix string) ([]string, []string) { - vars := []string{} - missing := []string{} - for _, v := range envVars { - val, ok := os.LookupEnv(v + suffix) - if !ok { - missing = append(missing, v) - } - - vars = append(vars, fmt.Sprintf("%s=%s", v, val)) - } - - return vars, missing -} - -// getEnv looks up for a loaded environment variable. -// An optional suffix may be passed, as well as a default value to return if the env var is not loaded. -func getEnv(key string, suffix string, defaultVal string) string { - if value, exists := os.LookupEnv(key + suffix); exists { - return value - } - - return defaultVal -} - -// writeFile writes a slice of strings into a file, separated by new lines -func writeFile(path string, vars []string) error { - file, err := os.Create(path) - if err != nil { - return err - } - - defer file.Close() - - sep := "\n" - for _, line := range vars { - if _, err = file.WriteString(line + sep); err != nil { - return err - } - } + gen.GenerateFiles() + fmt.Println("Finished env files generation!") return nil } diff --git a/cmd/log.go b/cmd/log.go deleted file mode 100644 index 36e531e..0000000 --- a/cmd/log.go +++ /dev/null @@ -1,34 +0,0 @@ -package cmd - -import ( - "fmt" - "os" -) - -const ( - InfoColor = "\033[1;34m%s\033[0m\n" - WarnColor = "\033[1;33m%s\033[0m" - ErrorColor = "\033[1;31m%s\033[0m\n" -) - -func printLog(color string, msg interface{}) { - fmt.Printf(color, msg) -} - -func logInfo(msg interface{}) { - printLog(InfoColor, msg) -} - -func logWarn(msg interface{}) { - printLog(WarnColor, msg) -} - -func logError(msg interface{}) { - printLog(ErrorColor, msg) -} - -type errorWriter struct{} - -func (w errorWriter) Write(p []byte) (n int, err error) { - return fmt.Fprintf(os.Stdout, ErrorColor, p) -} diff --git a/generator/generator.go b/generator/generator.go new file mode 100644 index 0000000..2a34dee --- /dev/null +++ b/generator/generator.go @@ -0,0 +1,178 @@ +package generator + +import ( + "fmt" + "gopkg.in/yaml.v2" + "io/ioutil" + "log" + "os" + "strings" + "sync" +) + +const DefaultEnvFileName = ".env" + +type branchConfig struct { + Name string `yaml:"name"` + Suffix string `yaml:"suffix"` +} + +type packageConfig struct { + Package string `yaml:"package"` + EnvFile string `yaml:"envFile"` + Variables []string `yaml:"variables"` +} + +type config struct { + BranchVarName string `yaml:"branchVarName"` + BranchVarDefault string `yaml:"branchVarDefault"` + Branches []branchConfig `yaml:"branches"` + Packages []packageConfig `yaml:"packages"` + Globals []string `yaml:"globals"` +} + +type Generator struct { + conf config + branchSuffix string + globals []string + logger *log.Logger + verbose bool +} + +// New creates a new env file generator, with loaded configurations. +func New(filepath string) (*Generator, error) { + gen := &Generator{ + // TODO: accept external logger? + logger: log.New(os.Stderr, "", log.LstdFlags), + } + err := gen.LoadConfig(filepath) + if err != nil { + return nil, err + } + + return gen, nil +} + +// LoadConfig loads the configuration from the provided yaml file. +// It also determines the branch suffix property. +func (g *Generator) LoadConfig(filepath string) error { + config := &config{} + file, err := ioutil.ReadFile(filepath) + if err != nil { + return err + } + + err = yaml.Unmarshal(file, config) + if err != nil { + return err + } + + g.conf = *config + g.loadBranchSuffix() + + return nil +} + +func (g *Generator) LoadGlobals() { + globals, missing := getVariablesValues(g.conf.Globals, "") + if len(missing) > 0 { + g.logger.Printf("[globals] missing env vars: %s\n", strings.Join(missing, ", ")) + } + + g.globals = globals +} + +// GenerateFiles creates all the env files as specified in the loaded configuration. +func (g *Generator) GenerateFiles() { + g.LoadGlobals() + + var wg sync.WaitGroup + for _, p := range g.conf.Packages { + wg.Add(1) + go g.GeneratePackageEnvFile(p, &wg) + } + + wg.Wait() +} + +// GeneratePackageEnvFile generates and writes the env file for a package +func (g *Generator) GeneratePackageEnvFile(pckg packageConfig, wg *sync.WaitGroup) { + defer wg.Done() + + packageVars, missing := getVariablesValues(pckg.Variables, g.branchSuffix) + if len(missing) > 0 { + // TODO create flag to break execution as error ? + g.logger.Printf("[%s] missing env vars: %s\n", pckg.Package, strings.Join(missing, ", ")) + } + + envFile := pckg.EnvFile + if envFile == "" { + envFile = DefaultEnvFileName + } + + genEnvFilePath := fmt.Sprintf("%s/%s", pckg.Package, envFile) + err := writeFile(genEnvFilePath, append(packageVars, g.globals...)) + if err != nil { + g.logger.Println(err) + } + + g.logger.Printf("[%s] generated env file\n", pckg.Package) +} + +// loadBranchSuffix finds and loads the branch suffix to use, depending on the current CI branch +func (g *Generator) loadBranchSuffix() { + branch := getEnv(g.conf.BranchVarName, "", g.conf.BranchVarDefault) + + for _, b := range g.conf.Branches { + if b.Name == branch { + g.branchSuffix = b.Suffix + return + } + } +} + +// returns a string slice loaded with the env var declarations +// and an array with those not found in the environment +func getVariablesValues(envVars []string, suffix string) ([]string, []string) { + vars := []string{} + missing := []string{} + for _, v := range envVars { + val, ok := os.LookupEnv(v + suffix) + if !ok { + missing = append(missing, v) + } + + vars = append(vars, fmt.Sprintf("%s=%s", v, val)) + } + + return vars, missing +} + +// getEnv looks up for a loaded environment variable. +// An optional suffix may be passed, as well as a default value to return if the env var is not loaded. +func getEnv(key string, suffix string, defaultVal string) string { + if value, exists := os.LookupEnv(key + suffix); exists { + return value + } + + return defaultVal +} + +// writeFile writes a slice of strings into a file, separated by new lines +func writeFile(path string, vars []string) error { + file, err := os.Create(path) + if err != nil { + return err + } + + defer file.Close() + + sep := "\n" + for _, line := range vars { + if _, err = file.WriteString(line + sep); err != nil { + return err + } + } + + return nil +} \ No newline at end of file