diff --git a/cmd/apply.go b/cmd/apply.go index 434eb0f00..32d86ec48 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -1,6 +1,9 @@ package cmd import ( + "log" + "os" + "github.com/commitdev/zero/internal/apply" "github.com/commitdev/zero/internal/config/projectconfig" "github.com/commitdev/zero/internal/constants" @@ -21,7 +24,11 @@ var applyCmd = &cobra.Command{ Use: "apply", Short: "Execute modules to create projects, infrastructure, etc.", Run: func(cmd *cobra.Command, args []string) { - // @TODO rootdir? - apply.Apply(projectconfig.RootDir, applyConfigPath, applyEnvironments) + rootDir, err := os.Getwd() + if err != nil { + log.Println(err) + rootDir = projectconfig.RootDir + } + apply.Apply(rootDir, applyConfigPath, applyEnvironments) }, } diff --git a/cmd/apply_test.go b/cmd/apply_test.go deleted file mode 100644 index d72b42cc4..000000000 --- a/cmd/apply_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package cmd_test - -import ( - "testing" -) - -func TestApply(t *testing.T) { - // if false { - // t.Fatalf("apply failed!") - // } -} diff --git a/cmd/init_test.go b/cmd/init_test.go deleted file mode 100644 index dfde5e131..000000000 --- a/cmd/init_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package cmd_test - -import ( - "testing" -) - -func TestInitWorks(t *testing.T) { - // @TODO : Figure out a way to test this - // tmpdir, err := ioutil.TempDir("", "commit0-") - // if err != nil { - // t.Fatal(err) - // } - - // projectName := "test-project" - - // templates := packr.New("templates", "../templates") - // templator := templator.NewTemplator(templates) - - // root := cmd.Create(projectName, tmpdir, templator) - // defer os.RemoveAll(tmpdir) - - // st, err := os.Stat(path.Join(root, util.CommitYml)) - // if err != nil { - // t.Fatal(err) - // } - - // if st.Size() == 0 { - // t.Fatalf("zero.yml is empty") - // } -} diff --git a/go.mod b/go.mod index 1de9fc874..1beeb1a62 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/commitdev/zero -go 1.12 +go 1.13 require ( github.com/aws/aws-sdk-go v1.25.33 @@ -24,6 +24,7 @@ require ( github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/spf13/cobra v0.0.6 github.com/stretchr/testify v1.4.0 + github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect diff --git a/go.sum b/go.sum index d3dcfc63e..6106a2703 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -142,6 +143,7 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/moby v1.13.1 h1:mC5WwQwCXt/dYxZ1cIrRsnJAWw7VdtcTZUIGr4tXzOM= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -179,6 +181,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae h1:vgGSvdW5Lqg+I1aZOlG32uyE6xHpLdKhZzcTEktz5wM= +github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae/go.mod h1:quDq6Se6jlGwiIKia/itDZxqC5rj6/8OdFyMMAwTxCs= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= diff --git a/internal/apply/apply.go b/internal/apply/apply.go index 92e82ce0c..3313d3854 100644 --- a/internal/apply/apply.go +++ b/internal/apply/apply.go @@ -2,25 +2,38 @@ package apply import ( "fmt" + "path/filepath" "log" "os/exec" "path" "strings" + "github.com/commitdev/zero/internal/module" "github.com/commitdev/zero/internal/util" - "github.com/commitdev/zero/pkg/util/flog" + "github.com/commitdev/zero/internal/config/globalconfig" "github.com/commitdev/zero/internal/config/projectconfig" "github.com/commitdev/zero/pkg/util/exit" + "github.com/commitdev/zero/pkg/util/flog" "github.com/manifoldco/promptui" ) -// Apply will bootstrap the runtime environment for the project -func Apply(dir string, applyConfigPath string, applyEnvironments []string) []string { - context := loadContext(dir, applyConfigPath, applyEnvironments) +func Apply(rootDir string, configPath string, environments []string) { + if strings.Trim(configPath, " ") == "" { + exit.Fatal("config path cannot be empty!") + } + configFilePath := path.Join(rootDir, configPath) + projectConfig := projectconfig.LoadConfig(configFilePath) + + if len(environments) == 0 { + fmt.Println(`Choose the environments to apply. This will create infrastructure, CI pipelines, etc. +At this point, real things will be generated that may cost money! +Only a single environment may be suitable for an initial test, but for a real system we suggest setting up both staging and production environments.`) + environments = promptEnvironments() + } - flog.Infof(":tada: Bootstrapping project %s. Please use the zero.[hcl, yaml] file to modify the project as needed. %s.", context.Name) + flog.Infof(":tada: Bootstrapping project %s. Please use the zero-project.yml file to modify the project as needed.", projectConfig.Name) flog.Infof("Cloud provider: %s", "AWS") // will this come from the config? @@ -28,28 +41,38 @@ func Apply(dir string, applyConfigPath string, applyEnvironments []string) []str flog.Infof("Infrastructure executor: %s", "Terraform") - // other details... + applyAll(rootDir, *projectConfig, environments) - return makeAll(dir, context, applyEnvironments) + // TODO Summary + flog.Infof(":check_mark_button: Done - Summary goes here.") } -// loadContext will load the context/configuration to be used by the apply command -func loadContext(dir string, applyConfigPath string, applyEnvironments []string) *projectconfig.ZeroProjectConfig { - if len(applyEnvironments) == 0 { - fmt.Println(`Choose the environments to apply. This will create infrastructure, CI pipelines, etc. -At this point, real things will be generated that may cost money! -Only a single environment may be suitable for an initial test, but for a real system we suggest setting up both staging and production environments.`) - applyEnvironments = promptEnvironments() - } +func applyAll(dir string, projectConfig projectconfig.ZeroProjectConfig, applyEnvironments []string) { + environmentArg := fmt.Sprintf("ENVIRONMENT=%s", strings.Join(applyEnvironments, ",")) - validateEnvironments(applyEnvironments) + // Go through each of the modules and run `make` + for _, mod := range projectConfig.Modules { + // Add env vars for the makefile + envList := []string{ + environmentArg, + fmt.Sprintf("PROJECT_DIR=%s", path.Join(dir, mod.Files.Directory)), + fmt.Sprintf("REPOSITORY=%s", mod.Files.Repository), + } - if applyConfigPath == "" { - exit.Fatal("config path cannot be empty!") + modulePath := module.GetSourceDir(mod.Files.Source) + // Passed in `dir` will only be used to find the project path, not the module path, + // unless the module path is relative + if module.IsLocal(mod.Files.Source) && !filepath.IsAbs(modulePath) { + modulePath = filepath.Join(dir, modulePath) + } + + // Get project credentials for the makefile + credentials := globalconfig.GetProjectCredentials(projectConfig.Name) + + envList = util.AppendProjectEnvToCmdEnv(mod.Parameters, envList) + envList = util.AppendProjectEnvToCmdEnv(credentials.AsEnvVars(), envList) + util.ExecuteCommand(exec.Command("make"), modulePath, envList) } - configPath := path.Join(dir, applyConfigPath) - projectConfig := projectconfig.LoadConfig(configPath) - return projectConfig } // promptEnvironments Prompts the user for the environments to apply against and returns a slice of strings representing the environments @@ -85,18 +108,3 @@ func validateEnvironments(applyEnvironments []string) { } } } - -func makeAll(dir string, projectContext *projectconfig.ZeroProjectConfig, applyEnvironments []string) []string { - environmentArg := fmt.Sprintf("ENVIRONMENT=%s", strings.Join(applyEnvironments, ",")) - envList := []string{environmentArg} - outputs := []string{} - - for _, mod := range projectContext.Modules { - modulePath := path.Join(dir, mod.Files.Directory) - envList = util.AppendProjectEnvToCmdEnv(mod.Parameters, envList) - - output := util.ExecuteCommandOutput(exec.Command("make"), modulePath, envList) - outputs = append(outputs, output) - } - return outputs -} diff --git a/internal/apply/apply_test.go b/internal/apply/apply_test.go index 4a81c628f..a88daee54 100644 --- a/internal/apply/apply_test.go +++ b/internal/apply/apply_test.go @@ -1,27 +1,41 @@ package apply_test import ( + "io/ioutil" + "os" + "path/filepath" "testing" "github.com/commitdev/zero/internal/apply" "github.com/commitdev/zero/internal/constants" "github.com/stretchr/testify/assert" + "github.com/termie/go-shutil" ) func TestApply(t *testing.T) { - // @TODO is there a way to do this without relative paths? - dir := "../../tests/test_data/sample_project/" + dir := "../../tests/test_data/apply/" applyConfigPath := constants.ZeroProjectYml applyEnvironments := []string{"staging", "production"} - want := []string{ - "make module zero-aws-eks-stack\n", - "make module zero-deployable-backend\n", - "make module zero-deployable-react-frontend\n", - } + tmpDir := filepath.Join(os.TempDir(), "apply") + + err := os.RemoveAll(tmpDir) + assert.NoError(t, err) + + err = shutil.CopyTree(dir, tmpDir, nil) + assert.NoError(t, err) t.Run("Should run apply and execute make on each folder module", func(t *testing.T) { - got := apply.Apply(dir, applyConfigPath, applyEnvironments) - assert.ElementsMatch(t, want, got) + apply.Apply(tmpDir, applyConfigPath, applyEnvironments) + assert.FileExists(t, filepath.Join(tmpDir, "project1/project.out")) + assert.FileExists(t, filepath.Join(tmpDir, "project2/project.out")) + + content, err := ioutil.ReadFile(filepath.Join(tmpDir, "project1/project.out")) + assert.NoError(t, err) + assert.Equal(t, "foo: bar\nrepo: github.com/commitdev/project1\n", string(content)) + + content, err = ioutil.ReadFile(filepath.Join(tmpDir, "project2/project.out")) + assert.NoError(t, err) + assert.Equal(t, "baz: qux\n", string(content)) }) } diff --git a/internal/config/globalconfig/global_config.go b/internal/config/globalconfig/global_config.go index 0f1cf91ff..9feb5ddf5 100644 --- a/internal/config/globalconfig/global_config.go +++ b/internal/config/globalconfig/global_config.go @@ -7,6 +7,7 @@ import ( "os" "os/user" "path" + "reflect" "github.com/commitdev/zero/internal/constants" "github.com/commitdev/zero/pkg/util/exit" @@ -25,14 +26,14 @@ type ProjectCredential struct { } type AWSResourceConfig struct { - AccessKeyId string `yaml:"accessKeyId,omitempty"` - SecretAccessKey string `yaml:"secretAccessKey,omitempty"` + AccessKeyID string `yaml:"accessKeyId,omitempty" env:"AWS_ACCESS_KEY_ID"` + SecretAccessKey string `yaml:"secretAccessKey,omitempty" env:"AWS_SECRET_ACCESS_KEY"` } type GithubResourceConfig struct { - AccessToken string `yaml:"accessToken,omitempty"` + AccessToken string `yaml:"accessToken,omitempty" env:"GITHUB_ACCESS_TOKEN"` } type CircleCiResourceConfig struct { - ApiKey string `yaml:"apiKey,omitempty"` + ApiKey string `yaml:"apiKey,omitempty" env:"CIRCLECI_API_KEY"` } func (p ProjectCredentials) Unmarshal(data []byte) error { @@ -50,6 +51,35 @@ func (p ProjectCredentials) Unmarshal(data []byte) error { return nil } +// AsEnvVars marshals ProjectCredential as a map of key/value strings suitable for environment variables +func (p ProjectCredential) AsEnvVars() map[string]string { + t := reflect.ValueOf(p) + + list := make(map[string]string) + list = gatherFieldTags(t, list) + + return list +} + +func gatherFieldTags(t reflect.Value, list map[string]string) map[string]string { + reflectType := t.Type() + + for i := 0; i < t.NumField(); i++ { + fieldValue := t.Field(i) + fieldType := reflectType.Field(i) + + if fieldType.Type.Kind() == reflect.Struct { + list = gatherFieldTags(fieldValue, list) + continue + } + + if env := fieldType.Tag.Get("env"); env != "" { + list[env] = fieldValue.String() + } + } + return list +} + func LoadUserCredentials() ProjectCredentials { data := readOrCreateUserCredentialsFile() diff --git a/internal/config/globalconfig/global_config_test.go b/internal/config/globalconfig/global_config_test.go index 12c47eb4a..9f9278220 100644 --- a/internal/config/globalconfig/global_config_test.go +++ b/internal/config/globalconfig/global_config_test.go @@ -66,7 +66,7 @@ func TestGetUserCredentials(t *testing.T) { project := globalconfig.GetProjectCredentials(projectName) // Reading from fixtures: tests/test_data/configs/credentials.yml - assert.Equal(t, "AKIAABCD", project.AWSResourceConfig.AccessKeyId) + assert.Equal(t, "AKIAABCD", project.AWSResourceConfig.AccessKeyID) assert.Equal(t, "ZXCV", project.AWSResourceConfig.SecretAccessKey) assert.Equal(t, "0987", project.GithubResourceConfig.AccessToken) assert.Equal(t, "SOME_API_KEY", project.CircleCiResourceConfig.ApiKey) @@ -87,17 +87,36 @@ func TestEditUserCredentials(t *testing.T) { t.Run("Should create new project if not exist", func(t *testing.T) { projectName := "test-project3" project := globalconfig.GetProjectCredentials(projectName) - project.AWSResourceConfig.AccessKeyId = "TEST_KEY_ID_1" + project.AWSResourceConfig.AccessKeyID = "TEST_KEY_ID_1" globalconfig.Save(project) - newKeyID := globalconfig.GetProjectCredentials(projectName).AWSResourceConfig.AccessKeyId + newKeyID := globalconfig.GetProjectCredentials(projectName).AWSResourceConfig.AccessKeyID assert.Equal(t, "TEST_KEY_ID_1", newKeyID) }) t.Run("Should edit old project if already exist", func(t *testing.T) { projectName := "my-project" project := globalconfig.GetProjectCredentials(projectName) - project.AWSResourceConfig.AccessKeyId = "EDITED_ACCESS_KEY_ID" + project.AWSResourceConfig.AccessKeyID = "EDITED_ACCESS_KEY_ID" globalconfig.Save(project) - newKeyID := globalconfig.GetProjectCredentials(projectName).AWSResourceConfig.AccessKeyId + newKeyID := globalconfig.GetProjectCredentials(projectName).AWSResourceConfig.AccessKeyID assert.Equal(t, "EDITED_ACCESS_KEY_ID", newKeyID) }) } + +func TestMarshalProjectCredentialAsEnvVars(t *testing.T) { + t.Run("Should be able to marshal a ProjectCredential into env vars", func(t *testing.T) { + pc := globalconfig.ProjectCredential{ + AWSResourceConfig: globalconfig.AWSResourceConfig{ + AccessKeyID: "AKID", + SecretAccessKey: "SAK", + }, + CircleCiResourceConfig: globalconfig.CircleCiResourceConfig{ + ApiKey: "APIKEY", + }, + } + + envVars := pc.AsEnvVars() + assert.Equal(t, "AKID", envVars["AWS_ACCESS_KEY_ID"]) + assert.Equal(t, "SAK", envVars["AWS_SECRET_ACCESS_KEY"]) + assert.Equal(t, "APIKEY", envVars["CIRCLECI_API_KEY"]) + }) +} diff --git a/internal/init/init.go b/internal/init/init.go index 9e72b62b8..8663d0ab0 100644 --- a/internal/init/init.go +++ b/internal/init/init.go @@ -6,11 +6,6 @@ import ( "path" "sync" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/sts" "github.com/commitdev/zero/internal/config/globalconfig" "github.com/commitdev/zero/internal/config/moduleconfig" "github.com/commitdev/zero/internal/config/projectconfig" @@ -239,10 +234,10 @@ func mapVendorToPrompts(projectCred globalconfig.ProjectCredential, vendor strin moduleconfig.Parameter{ Field: "accessKeyId", Label: "AWS Access Key ID", - Default: projectCred.AWSResourceConfig.AccessKeyId, + Default: projectCred.AWSResourceConfig.AccessKeyID, }, CustomCondition(customAwsMustInputCondition), - project.ValidateAKID, + ValidateAKID, }, { moduleconfig.Parameter{ @@ -251,7 +246,7 @@ func mapVendorToPrompts(projectCred globalconfig.ProjectCredential, vendor strin Default: projectCred.AWSResourceConfig.SecretAccessKey, }, CustomCondition(customAwsMustInputCondition), - project.ValidateSAK, + ValidateSAK, }, } prompts = append(prompts, awsPrompts...) @@ -311,34 +306,6 @@ func chooseStack(reg registry.Registry) []string { return registry.GetModulesByName(reg, providerResult) } -func fillProviderDetails(projectConfig *projectconfig.ZeroProjectConfig, s project.Secrets) { - if projectConfig.Infrastructure.AWS != nil { - sess, err := session.NewSession(&aws.Config{ - Region: aws.String(projectConfig.Infrastructure.AWS.Region), - Credentials: credentials.NewStaticCredentials(s.AWS.AccessKeyID, s.AWS.SecretAccessKey, ""), - }) - - svc := sts.New(sess) - input := &sts.GetCallerIdentityInput{} - - awsCaller, err := svc.GetCallerIdentity(input) - if err != nil { - if aerr, ok := err.(awserr.Error); ok { - switch aerr.Code() { - default: - exit.Error(aerr.Error()) - } - } else { - exit.Error(err.Error()) - } - } - - if awsCaller != nil && awsCaller.Account != nil { - projectConfig.Infrastructure.AWS.AccountID = *awsCaller.Account - } - } -} - func defaultProjConfig() projectconfig.ZeroProjectConfig { return projectconfig.ZeroProjectConfig{ Name: "", diff --git a/internal/init/prompts.go b/internal/init/prompts.go index 1a46b7cbe..d976a4c4c 100644 --- a/internal/init/prompts.go +++ b/internal/init/prompts.go @@ -1,6 +1,7 @@ package init import ( + "errors" "fmt" "log" "os" @@ -10,6 +11,7 @@ import ( "github.com/commitdev/zero/internal/config/globalconfig" "github.com/commitdev/zero/internal/config/moduleconfig" + "github.com/commitdev/zero/internal/util" "github.com/commitdev/zero/pkg/credentials" "github.com/commitdev/zero/pkg/util/exit" "github.com/manifoldco/promptui" @@ -67,6 +69,24 @@ func SpecificValueValidation(values ...string) func(string) error { } } +func ValidateAKID(input string) error { + // 20 uppercase alphanumeric characters + var awsAccessKeyIDPat = regexp.MustCompile(`^[A-Z0-9]{20}$`) + if !awsAccessKeyIDPat.MatchString(input) { + return errors.New("Invalid aws_access_key_id") + } + return nil +} + +func ValidateSAK(input string) error { + // 40 base64 characters + var awsSecretAccessKeyPat = regexp.MustCompile(`^[A-Za-z0-9/+=]{40}$`) + if !awsSecretAccessKeyPat.MatchString(input) { + return errors.New("Invalid aws_secret_access_key") + } + return nil +} + // TODO: validation / allow prompt retry ...etc func (p PromptHandler) GetParam(projectParams map[string]string) string { var err error @@ -127,7 +147,7 @@ func promptParameter(prompt PromptHandler) (error, string) { func executeCmd(command string, envVars map[string]string) string { cmd := exec.Command("bash", "-c", command) - cmd.Env = appendProjectEnvToCmdEnv(envVars, os.Environ()) + cmd.Env = util.AppendProjectEnvToCmdEnv(envVars, os.Environ()) out, err := cmd.Output() if err != nil { @@ -142,15 +162,6 @@ func sanitizeParameterValue(str string) string { return re.ReplaceAllString(str, "") } -func appendProjectEnvToCmdEnv(envMap map[string]string, envList []string) []string { - for key, val := range envMap { - if val != "" { - envList = append(envList, fmt.Sprintf("%s=%s", key, val)) - } - } - return envList -} - // PromptParams renders series of prompt UI based on the config func PromptModuleParams(moduleConfig moduleconfig.ModuleConfig, parameters map[string]string) (map[string]string, error) { diff --git a/internal/module/module.go b/internal/module/module.go index d8a17e225..8e8fd5322 100644 --- a/internal/module/module.go +++ b/internal/module/module.go @@ -21,7 +21,7 @@ func FetchModule(source string, wg *sync.WaitGroup) { defer wg.Done() localPath := GetSourceDir(source) - if !isLocal(source) { + if !IsLocal(source) { err := getter.Get(localPath, source) if err != nil { exit.Fatal("Failed to fetch remote module from %s: %v\n", source, err) @@ -41,7 +41,7 @@ func ParseModuleConfig(source string) (moduleconfig.ModuleConfig, error) { // GetSourcePath gets a unique local source directory name. For local modules, it use the local directory func GetSourceDir(source string) string { - if !isLocal(source) { + if !IsLocal(source) { h := md5.New() io.WriteString(h, source) source = base64.StdEncoding.EncodeToString(h.Sum(nil)) @@ -52,7 +52,7 @@ func GetSourceDir(source string) string { } // IsLocal uses the go-getter FileDetector to check if source is a file -func isLocal(source string) bool { +func IsLocal(source string) bool { pwd := util.GetCwd() // ref: https://github.com/hashicorp/go-getter/blob/master/detect_test.go diff --git a/internal/module/module_internal_test.go b/internal/module/module_internal_test.go index 8ec9e0826..e191da406 100644 --- a/internal/module/module_internal_test.go +++ b/internal/module/module_internal_test.go @@ -6,13 +6,13 @@ import ( func TestIsLocal(t *testing.T) { source := "./tests/test_data/modules" - res := isLocal(source) + res := IsLocal(source) if !res { t.Errorf("Error, source %s SHOULD BE determined as local", source) } source = "https://github.com/commitdev/my-repo" - res = isLocal(source) + res = IsLocal(source) if res { t.Errorf("Error, source %s SHOULD NOT BE determined as local", source) } diff --git a/internal/util/util.go b/internal/util/util.go index bc24f5edc..7e0116c28 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "strconv" "strings" "text/template" @@ -47,17 +48,21 @@ func GetCwd() string { } func ExecuteCommand(cmd *exec.Cmd, pathPrefix string, envars []string) { - dir := GetCwd() - cmd.Dir = path.Join(dir, pathPrefix) + cmd.Dir = pathPrefix + if !filepath.IsAbs(pathPrefix) { + dir := GetCwd() + cmd.Dir = path.Join(dir, pathPrefix) + } stdoutPipe, _ := cmd.StdoutPipe() stderrPipe, _ := cmd.StderrPipe() var errStdout, errStderr error + cmd.Env = os.Environ() if envars != nil { - cmd.Env = envars + cmd.Env = append(os.Environ(), envars...) } err := cmd.Start() @@ -89,17 +94,21 @@ func ExecuteCommand(cmd *exec.Cmd, pathPrefix string, envars []string) { // ExecuteCommandOutput runs the command and returns its // combined standard output and standard error. func ExecuteCommandOutput(cmd *exec.Cmd, pathPrefix string, envars []string) string { - dir := GetCwd() - cmd.Dir = path.Join(dir, pathPrefix) + cmd.Dir = pathPrefix + if !filepath.IsAbs(pathPrefix) { + dir := GetCwd() + cmd.Dir = path.Join(dir, pathPrefix) + } + cmd.Env = os.Environ() if envars != nil { - cmd.Env = envars + cmd.Env = append(os.Environ(), envars...) } out, err := cmd.CombinedOutput() if err != nil { - log.Fatalf("Executing command failed: (%v) %s\n", err, out) + log.Fatalf("Executing command with output failed: (%v) %s\n", err, out) } return string(out) } diff --git a/pkg/credentials/credentials.go b/pkg/credentials/credentials.go index 9389865f0..2abe8b751 100644 --- a/pkg/credentials/credentials.go +++ b/pkg/credentials/credentials.go @@ -1,42 +1,16 @@ package credentials import ( - "errors" - "fmt" "io/ioutil" "log" - "os" "os/user" "path/filepath" "regexp" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/commitdev/zero/internal/config/globalconfig" - "github.com/commitdev/zero/internal/config/projectconfig" - "github.com/manifoldco/promptui" ) -// Secrets - AWS prompted credentials -type Secrets struct { - AWS AWS - CircleCIKey string - GithubToken string -} - -type AWS struct { - AccessKeyID string - SecretAccessKey string -} - -func MakeAwsEnvars(cfg *projectconfig.ZeroProjectConfig, awsSecrets Secrets) []string { - env := os.Environ() - env = append(env, fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", awsSecrets.AWS.AccessKeyID)) - env = append(env, fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", awsSecrets.AWS.SecretAccessKey)) - env = append(env, fmt.Sprintf("AWS_DEFAULT_REGION=%s", cfg.Infrastructure.AWS.Region)) - - return env -} - func AwsCredsPath() string { usr, err := user.Current() if err != nil { @@ -55,7 +29,7 @@ func GetAWSProfileCredentials(credsPath string, profileName string, creds global if err != nil { log.Fatal(err) } - creds.AWSResourceConfig.AccessKeyId = awsCreds.AccessKeyID + creds.AWSResourceConfig.AccessKeyID = awsCreds.AccessKeyID creds.AWSResourceConfig.SecretAccessKey = awsCreds.SecretAccessKey return creds } @@ -82,86 +56,3 @@ func GetAWSProfiles() ([]string, error) { } return profiles, nil } - -func ValidateAKID(input string) error { - // 20 uppercase alphanumeric characters - var awsAccessKeyIDPat = regexp.MustCompile(`^[A-Z0-9]{20}$`) - if !awsAccessKeyIDPat.MatchString(input) { - return errors.New("Invalid aws_access_key_id") - } - return nil -} - -func ValidateSAK(input string) error { - // 40 base64 characters - var awsSecretAccessKeyPat = regexp.MustCompile(`^[A-Za-z0-9/+=]{40}$`) - if !awsSecretAccessKeyPat.MatchString(input) { - return errors.New("Invalid aws_secret_access_key") - } - return nil -} - -func promptAWSCredentials(secrets *Secrets) { - accessKeyIDPrompt := promptui.Prompt{ - Label: "Aws Access Key ID ", - Validate: ValidateAKID, - } - - accessKeyIDResult, err := accessKeyIDPrompt.Run() - - if err != nil { - log.Fatalf("Prompt failed %v\n", err) - panic(err) - } - - secretAccessKeyPrompt := promptui.Prompt{ - Label: "Aws Secret Access Key ", - Validate: ValidateSAK, - Mask: '*', - } - - secretAccessKeyResult, err := secretAccessKeyPrompt.Run() - - if err != nil { - log.Fatalf("Prompt failed %v\n", err) - panic(err) - } - - secrets.AWS.AccessKeyID = accessKeyIDResult - secrets.AWS.SecretAccessKey = secretAccessKeyResult -} - -func promptGitHubCredentials(secrets *Secrets) { -} - -func promptCircleCICredentials(secrets *Secrets) { - validateKey := func(input string) error { - // 40 base64 characters - var awsSecretAccessKeyPat = regexp.MustCompile(`^[A-Za-z0-9]{40}$`) - if !awsSecretAccessKeyPat.MatchString(input) { - return errors.New("Invalid CircleCI API Key") - } - return nil - } - - prompt := promptui.Prompt{ - Label: "Please enter your CircleCI API key (you can create one at https://circleci.com/account/api) ", - Validate: validateKey, - } - - key, err := prompt.Run() - - if err != nil { - log.Fatalf("Prompt failed %v\n", err) - panic(err) - } - secrets.CircleCIKey = key -} - -func fileExists(filename string) bool { - info, err := os.Stat(filename) - if os.IsNotExist(err) { - return false - } - return !info.IsDir() -} diff --git a/pkg/credentials/credentials_test.go b/pkg/credentials/credentials_test.go index 9386c993a..9c9e3c62c 100644 --- a/pkg/credentials/credentials_test.go +++ b/pkg/credentials/credentials_test.go @@ -13,14 +13,14 @@ func TestFillAWSProfileCredentials(t *testing.T) { t.Run("fills project credentials", func(t *testing.T) { projectCreds := globalconfig.ProjectCredential{} projectCreds = credentials.GetAWSProfileCredentials(mockAwsCredentialFilePath, "default", projectCreds) - assert.Equal(t, "MOCK1_ACCESS_KEY", projectCreds.AWSResourceConfig.AccessKeyId) + assert.Equal(t, "MOCK1_ACCESS_KEY", projectCreds.AWSResourceConfig.AccessKeyID) assert.Equal(t, "MOCK1_SECRET_ACCESS_KEY", projectCreds.AWSResourceConfig.SecretAccessKey) }) t.Run("supports non-default profiles", func(t *testing.T) { projectCreds := globalconfig.ProjectCredential{} projectCreds = credentials.GetAWSProfileCredentials(mockAwsCredentialFilePath, "foobar", projectCreds) - assert.Equal(t, "MOCK2_ACCESS_KEY", projectCreds.AWSResourceConfig.AccessKeyId) + assert.Equal(t, "MOCK2_ACCESS_KEY", projectCreds.AWSResourceConfig.AccessKeyID) assert.Equal(t, "MOCK2_SECRET_ACCESS_KEY", projectCreds.AWSResourceConfig.SecretAccessKey) }) } diff --git a/tests/test_data/apply/project1/Makefile b/tests/test_data/apply/project1/Makefile new file mode 100644 index 000000000..a60a27603 --- /dev/null +++ b/tests/test_data/apply/project1/Makefile @@ -0,0 +1,3 @@ +current_dir: + @echo "foo: ${foo}" > project.out + @echo "repo: ${REPOSITORY}" >> project.out diff --git a/tests/test_data/apply/project2/Makefile b/tests/test_data/apply/project2/Makefile new file mode 100644 index 000000000..a17340f10 --- /dev/null +++ b/tests/test_data/apply/project2/Makefile @@ -0,0 +1,2 @@ +current_dir: + @echo "baz: ${baz}" > project.out diff --git a/tests/test_data/apply/zero-project.yml b/tests/test_data/apply/zero-project.yml new file mode 100644 index 000000000..09c239cfc --- /dev/null +++ b/tests/test_data/apply/zero-project.yml @@ -0,0 +1,17 @@ +name: sample_project + +modules: + project1: + parameters: + foo: bar + files: + dir: project1 + repo: github.com/commitdev/project1 + source: project1 + project2: + parameters: + baz: qux + files: + dir: project2 + repo: github.com/commitdev/project2 + source: project2 diff --git a/tests/test_data/sample_project/zero-aws-eks-stack/Makefile b/tests/test_data/sample_project/zero-aws-eks-stack/Makefile deleted file mode 100644 index 105cbca1b..000000000 --- a/tests/test_data/sample_project/zero-aws-eks-stack/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -current_dir: - @echo "make module" `pwd | xargs basename` \ No newline at end of file diff --git a/tests/test_data/sample_project/zero-deployable-backend/Makefile b/tests/test_data/sample_project/zero-deployable-backend/Makefile deleted file mode 100644 index 105cbca1b..000000000 --- a/tests/test_data/sample_project/zero-deployable-backend/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -current_dir: - @echo "make module" `pwd | xargs basename` \ No newline at end of file diff --git a/tests/test_data/sample_project/zero-deployable-react-frontend/Makefile b/tests/test_data/sample_project/zero-deployable-react-frontend/Makefile deleted file mode 100644 index 105cbca1b..000000000 --- a/tests/test_data/sample_project/zero-deployable-react-frontend/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -current_dir: - @echo "make module" `pwd | xargs basename` \ No newline at end of file diff --git a/tests/test_data/sample_project/zero-project.yml b/tests/test_data/sample_project/zero-project.yml deleted file mode 100644 index e8731b2fe..000000000 --- a/tests/test_data/sample_project/zero-project.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: sample_project - -context: - -modules: - aws-eks-stack: - files: - dir: zero-aws-eks-stack - repo: github.com/commitdev/zero-aws-eks-stack - deployable-backend: - files: - dir: zero-deployable-backend - repo: github.com/commitdev/zero-deployable-backend - deployable-react-frontend: - files: - dir: zero-deployable-react-frontend - repo: github.com/commitdev/zero-deployable-react-frontend \ No newline at end of file