From 325a6c407e3fcf429ad66188f511cb66022b1b6d Mon Sep 17 00:00:00 2001 From: dtoki <7953527+dtoki@users.noreply.github.com> Date: Tue, 23 Jun 2020 12:51:18 -0700 Subject: [PATCH] Marshal zero-project.yaml file (#165) template zero-project config to file. --- cmd/init.go | 9 ++- internal/config/projectconfig/init.go | 81 +++++++++++-------- internal/config/projectconfig/init_test.go | 29 ++++++- .../config/projectconfig/project_config.go | 2 +- .../projectconfig/project_config_test.go | 45 ++++++----- internal/util/util.go | 11 +++ 6 files changed, 118 insertions(+), 59 deletions(-) diff --git a/cmd/init.go b/cmd/init.go index 9773cfa7f..49471bd37 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -1,8 +1,11 @@ package cmd import ( + "fmt" + "github.com/commitdev/zero/internal/config/projectconfig" initPrompts "github.com/commitdev/zero/internal/init" + "github.com/commitdev/zero/pkg/util/exit" "github.com/spf13/cobra" ) @@ -15,6 +18,10 @@ var initCmd = &cobra.Command{ Short: "Create new project with provided name and initialize configuration based on user input.", Run: func(cmd *cobra.Command, args []string) { projectContext := initPrompts.Init(projectconfig.RootDir) - projectconfig.Init(projectconfig.RootDir, projectContext.Name, projectContext) + projectConfigErr := projectconfig.CreateProjectConfigFile(projectconfig.RootDir, projectContext.Name, projectContext) + + if projectConfigErr != nil { + exit.Fatal(fmt.Sprintf(" Init failed while creating the zero project config file %s", projectConfigErr.Error())) + } }, } diff --git a/internal/config/projectconfig/init.go b/internal/config/projectconfig/init.go index 783c897c7..76dcdc7ac 100644 --- a/internal/config/projectconfig/init.go +++ b/internal/config/projectconfig/init.go @@ -1,43 +1,25 @@ package projectconfig import ( + "bytes" "fmt" "io/ioutil" "path" + "text/template" "github.com/commitdev/zero/internal/constants" - "github.com/commitdev/zero/pkg/util/exit" + "github.com/commitdev/zero/internal/util" "gopkg.in/yaml.v2" ) -const exampleConfig = `name: %s +const zeroProjectConfigTemplate = ` +# Templated zero-project.yml file +name: {{.Name}} -# Context is normally populated automatically but could be used to inject global params -context: +shouldPushRepositories: {{.ShouldPushRepositories | printf "%v"}} -# module can be in any format the go-getter supports (path, github, url, etc.) -# supports https://github.com/hashicorp/go-getter#url-format -# Example: -# - repo: "../development/modules/ci" -# - dir: "github-actions" modules: - aws-eks-stack: - parameters: - repoName: infrastructure - region: us-east-1 - accountId: 12345 - productionHost: something.com - files: - dir: infrastructure - repo: https://github.com/myorg/infrastructure - some-other-module: - parameters: - repoName: api - files: - dir: api - repo: https://github.com/myorg/api - - +{{.Modules}} ` var RootDir = "./" @@ -46,16 +28,49 @@ func SetRootDir(dir string) { RootDir = dir } -func Init(dir string, projectName string, projectContext *ZeroProjectConfig) { - // TODO: template the zero-project.yml with projectContext - // content := []byte(fmt.Sprintf(exampleConfig, projectName)) - content, err := yaml.Marshal(projectContext) +// CreateProjectConfigFile extracts the required content for zero project config file then write to disk. +func CreateProjectConfigFile(dir string, projectName string, projectContext *ZeroProjectConfig) error { + content, err := getProjectFileContent(*projectContext) if err != nil { - exit.Fatal(fmt.Sprintf("Failed to serialize configuration file %s", constants.ZeroProjectYml)) + return err } - writeErr := ioutil.WriteFile(path.Join(dir, projectName, constants.ZeroProjectYml), content, 0644) + writeErr := ioutil.WriteFile(path.Join(dir, projectName, constants.ZeroProjectYml), []byte(content), 0644) if writeErr != nil { - exit.Fatal(fmt.Sprintf("Failed to create config file %s", constants.ZeroProjectYml)) + return err + } + + return nil +} + +func getProjectFileContent(projectConfig ZeroProjectConfig) (string, error) { + var tplBuffer bytes.Buffer + tmpl, err := template.New("projectConfig").Parse(zeroProjectConfigTemplate) + if err != nil { + return "", err + } + + if len(projectConfig.Modules) == 0 { + return "", fmt.Errorf("Invalid project config, expected config modules to be non-empty") + } + + pConfigModules, err := yaml.Marshal(projectConfig.Modules) + if err != nil { + return "", err + } + + t := struct { + Name string + ShouldPushRepositories bool + Modules string + }{ + Name: projectConfig.Name, + ShouldPushRepositories: projectConfig.ShouldPushRepositories, + Modules: util.IndentString(string(pConfigModules), 2), + } + + if err := tmpl.Execute(&tplBuffer, t); err != nil { + return "", err } + return tplBuffer.String(), nil } diff --git a/internal/config/projectconfig/init_test.go b/internal/config/projectconfig/init_test.go index a34c8d464..a5cd484df 100644 --- a/internal/config/projectconfig/init_test.go +++ b/internal/config/projectconfig/init_test.go @@ -7,9 +7,12 @@ import ( "github.com/commitdev/zero/internal/config/projectconfig" "github.com/commitdev/zero/internal/constants" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" ) -func TestInit(t *testing.T) { +func TestCreateProjectConfigFile(t *testing.T) { const testDir = "../../test-sandbox" projectName := "test-project" @@ -17,16 +20,36 @@ func TestInit(t *testing.T) { defer os.RemoveAll(testDir) testDirPath := path.Join(projectconfig.RootDir, projectName) + // create sandbox dir err := os.MkdirAll(testDirPath, os.ModePerm) if err != nil { t.Fatal(err) } - config := projectconfig.ZeroProjectConfig{} - projectconfig.Init(projectconfig.RootDir, projectName, &config) + expectedConfig := &projectconfig.ZeroProjectConfig{ + Name: projectName, + ShouldPushRepositories: false, + Modules: eksGoReactSampleModules(), + } + assert.NoError(t, projectconfig.CreateProjectConfigFile(projectconfig.RootDir, projectName, expectedConfig)) + // make sure the file exists if _, err := os.Stat(path.Join(testDirPath, constants.ZeroProjectYml)); err != nil { t.Fatal(err) } + + t.Run("Should return a valid project config", func(t *testing.T) { + resultConfig := projectconfig.LoadConfig(path.Join(testDirPath, constants.ZeroProjectYml)) + + if !cmp.Equal(expectedConfig, resultConfig, cmpopts.EquateEmpty()) { + t.Errorf("projectconfig.ZeroProjectConfig.Unmarshal mismatch (-expected +result):\n%s", cmp.Diff(expectedConfig, resultConfig)) + } + }) + + t.Run("Should fail if modules are missing from project config", func(t *testing.T) { + expectedConfig.Modules = nil + assert.Error(t, projectconfig.CreateProjectConfigFile(projectconfig.RootDir, projectName, expectedConfig)) + }) + } diff --git a/internal/config/projectconfig/project_config.go b/internal/config/projectconfig/project_config.go index b416a5da5..7bc716b67 100644 --- a/internal/config/projectconfig/project_config.go +++ b/internal/config/projectconfig/project_config.go @@ -34,7 +34,7 @@ type Modules map[string]Module type Module struct { Parameters Parameters `yaml:"parameters,omitempty"` - Files Files + Files Files `yaml:"files,omitempty"` } type Parameters map[string]string diff --git a/internal/config/projectconfig/project_config_test.go b/internal/config/projectconfig/project_config_test.go index 3c94d0242..22e76908c 100644 --- a/internal/config/projectconfig/project_config_test.go +++ b/internal/config/projectconfig/project_config_test.go @@ -45,29 +45,32 @@ func eksGoReactSampleModules() projectconfig.Modules { func validConfigContent() string { return ` +# Templated zero-project.yml file name: abc +shouldPushRepositories: false + modules: - aws-eks-stack: - parameters: - a: b - files: - dir: zero-aws-eks-stack - repo: github.com/something/repo1 - source: github.com/commitdev/zero-aws-eks-stack - deployable-backend: - parameters: - a: b - files: - dir: zero-deployable-backend - repo: github.com/something/repo2 - source: github.com/commitdev/zero-deployable-backend - deployable-react-frontend: - parameters: - a: b - files: - dir: zero-deployable-react-frontend - repo: github.com/something/repo3 - source: github.com/commitdev/zero-deployable-react-frontend + aws-eks-stack: + parameters: + a: b + files: + dir: zero-aws-eks-stack + repo: github.com/something/repo1 + source: github.com/commitdev/zero-aws-eks-stack + deployable-backend: + parameters: + a: b + files: + dir: zero-deployable-backend + repo: github.com/something/repo2 + source: github.com/commitdev/zero-deployable-backend + deployable-react-frontend: + parameters: + a: b + files: + dir: zero-deployable-react-frontend + repo: github.com/something/repo3 + source: github.com/commitdev/zero-deployable-react-frontend ` } diff --git a/internal/util/util.go b/internal/util/util.go index cb754bdff..bc24f5edc 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "path" + "strconv" "strings" "text/template" @@ -113,3 +114,13 @@ func AppendProjectEnvToCmdEnv(envMap map[string]string, envList []string) []stri } return envList } + +// IndentString will Add x space char padding at the beginging of each line. +func IndentString(content string, spaces int) string { + var result string + subStr := strings.Split(content, "\n") + for _, s := range subStr { + result += fmt.Sprintf("%"+strconv.Itoa(spaces)+"s%s\n", "", s) + } + return result +}