diff --git a/internal/config/projectconfig/project_config.go b/internal/config/projectconfig/project_config.go index 4fdd1cc74..c31182589 100644 --- a/internal/config/projectconfig/project_config.go +++ b/internal/config/projectconfig/project_config.go @@ -9,10 +9,11 @@ import ( ) type ZeroProjectConfig struct { - Name string - Infrastructure Infrastructure // TODO simplify and flatten / rename? - Parameters map[string]string - Modules []string + Name string + ShouldPushRepositories bool + Infrastructure Infrastructure // TODO simplify and flatten / rename? + Parameters map[string]string + Modules []string } type Infrastructure struct { diff --git a/internal/context/init.go b/internal/context/init.go index 2454f74d4..eb4f54024 100644 --- a/internal/context/init.go +++ b/internal/context/init.go @@ -1,8 +1,10 @@ package context import ( + "fmt" "os" "path" + "sync" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" @@ -28,7 +30,7 @@ func Init(outDir string) *projectconfig.ZeroProjectConfig { projectConfig.Name = getProjectNamePrompt().GetParam(projectConfig.Parameters) rootDir := path.Join(outDir, projectConfig.Name) - flog.Infof(":tada: Creating project") + flog.Infof(":tada: Initializing project") err := os.MkdirAll(rootDir, os.ModePerm) if os.IsExist(err) { @@ -37,29 +39,45 @@ func Init(outDir string) *projectconfig.ZeroProjectConfig { exit.Fatal("Error creating root: %v ", err) } - prompts := getProjectPrompts(projectConfig.Name) - projectConfig.Parameters["ShouldPushRepoUpstream"] = prompts["ShouldPushRepoUpstream"].GetParam(projectConfig.Parameters) - // Prompting for push-up stream, then conditionally prompting for github - projectConfig.Parameters["GithubRootOrg"] = prompts["GithubRootOrg"].GetParam(projectConfig.Parameters) - personalToken := prompts["githubPersonalToken"].GetParam(projectConfig.Parameters) - if personalToken != "" && personalToken != globalconfig.GetUserCredentials(projectConfig.Name).AccessToken { - projectConfig.Parameters["githubPersonalToken"] = personalToken - projectCredential := globalconfig.GetUserCredentials(projectConfig.Name) - projectCredential.GithubResourceConfig.AccessToken = personalToken - globalconfig.Save(projectCredential) - } moduleSources := chooseStack(getRegistry()) moduleConfigs := loadAllModules(moduleSources) for _ = range moduleConfigs { // TODO: initialize module structs inside project } + prompts := getProjectPrompts(projectConfig.Name, moduleConfigs) + + initParams := make(map[string]string) + projectConfig.ShouldPushRepositories = true + initParams["ShouldPushRepositories"] = prompts["ShouldPushRepositories"].GetParam(initParams) + if initParams["ShouldPushRepositories"] == "n" { + projectConfig.ShouldPushRepositories = false + } + + // Prompting for push-up stream, then conditionally prompting for github + initParams["GithubRootOrg"] = prompts["GithubRootOrg"].GetParam(initParams) + initParams["GithubPersonalToken"] = prompts["GithubPersonalToken"].GetParam(initParams) + if initParams["GithubRootOrg"] != "" && initParams["GithubPersonalToken"] != globalconfig.GetUserCredentials(projectConfig.Name).AccessToken { + projectCredential := globalconfig.GetUserCredentials(projectConfig.Name) + projectCredential.GithubResourceConfig.AccessToken = initParams["GithubPersonalToken"] + globalconfig.Save(projectCredential) + } + projectParameters := promptAllModules(moduleConfigs) for k, v := range projectParameters { projectConfig.Parameters[k] = v // TODO: Add parameters to module structs inside project } + for moduleName, _ := range moduleConfigs { + // @TODO : Uncomment when this struct is implemented + repoName := prompts[moduleName].GetParam(initParams) + repoURL := fmt.Sprintf("%s/%s", initParams["GithubRootOrg"], repoName) + //projectConfig.Modules[moduleName].Files.Directory = prompts[moduleName].GetParam(initParams) + //projectConfig.Modules[moduleName].Files.Repository = repoURL + fmt.Println(repoURL) + } + // TODO: load ~/.zero/config.yml (or credentials) // TODO: prompt global credentials @@ -70,8 +88,15 @@ func Init(outDir string) *projectconfig.ZeroProjectConfig { func loadAllModules(moduleSources []string) map[string]moduleconfig.ModuleConfig { modules := make(map[string]moduleconfig.ModuleConfig) + wg := sync.WaitGroup{} + wg.Add(len(moduleSources)) + for _, moduleSource := range moduleSources { + go module.FetchModule(moduleSource, &wg) + } + wg.Wait() + for _, moduleSource := range moduleSources { - mod, err := module.FetchModule(moduleSource) + mod, err := module.ParseModuleConfig(moduleSource) if err != nil { exit.Fatal("Unable to load module: %v\n", err) } @@ -103,18 +128,20 @@ func getProjectNamePrompt() PromptHandler { Default: "", }, NoCondition, + NoValidation, } } -func getProjectPrompts(projectName string) map[string]PromptHandler { - return map[string]PromptHandler{ - "ShouldPushRepoUpstream": { +func getProjectPrompts(projectName string, modules map[string]moduleconfig.ModuleConfig) map[string]PromptHandler { + handlers := map[string]PromptHandler{ + "ShouldPushRepositories": { moduleconfig.Parameter{ - Field: "ShouldPushRepoUpstream", + Field: "ShouldPushRepositories", Label: "Should the created projects be checked into github automatically? (y/n)", Default: "y", }, NoCondition, + SpecificValueValidation("y", "n"), }, "GithubRootOrg": { moduleconfig.Parameter{ @@ -122,17 +149,35 @@ func getProjectPrompts(projectName string) map[string]PromptHandler { Label: "What's the root of the github org to create repositories in?", Default: "github.com/", }, - KeyMatchCondition("ShouldPushRepoUpstream", "y"), + KeyMatchCondition("ShouldPushRepositories", "y"), + NoValidation, }, - "githubPersonalToken": { + "GithubPersonalToken": { moduleconfig.Parameter{ - Field: "githubPersonalToken", + Field: "GithubPersonalToken", Label: "Github Personal Access Token with access to the above organization", Default: globalconfig.GetUserCredentials(projectName).AccessToken, }, - KeyMatchCondition("ShouldPushRepoUpstream", "y"), + KeyMatchCondition("ShouldPushRepositories", "y"), + NoValidation, }, } + + for moduleName, module := range modules { + label := fmt.Sprintf("What do you want to call the %s project?", moduleName) + + handlers[moduleName] = PromptHandler{ + moduleconfig.Parameter{ + Field: moduleName, + Label: label, + Default: module.OutputDir, + }, + NoCondition, + NoValidation, + } + } + + return handlers } func chooseCloudProvider(projectConfig *projectconfig.ZeroProjectConfig) { diff --git a/internal/context/prompts.go b/internal/context/prompts.go index 7dcc7939f..467f0c309 100644 --- a/internal/context/prompts.go +++ b/internal/context/prompts.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "regexp" + "strings" "github.com/commitdev/zero/internal/config/moduleconfig" "github.com/commitdev/zero/pkg/util/exit" @@ -15,17 +16,34 @@ import ( type PromptHandler struct { moduleconfig.Parameter Condition func(map[string]string) bool + Validate func(string) error } func NoCondition(map[string]string) bool { return true } + func KeyMatchCondition(key string, value string) func(map[string]string) bool { return func(param map[string]string) bool { return param[key] == value } } +func NoValidation(string) error { + return nil +} + +func SpecificValueValidation(values ...string) func(string) error { + return func(checkValue string) error { + for _, allowedValue := range values { + if checkValue == allowedValue { + return nil + } + } + return fmt.Errorf("Please choose one of %s", strings.Join(values, "/")) + } +} + // TODO: validation / allow prompt retry ...etc func (p PromptHandler) GetParam(projectParams map[string]string) string { var err error @@ -40,7 +58,7 @@ func (p PromptHandler) GetParam(projectParams map[string]string) string { } else if p.Parameter.Value != "" { result = p.Parameter.Value } else { - err, result = promptParameter(p.Parameter) + err, result = promptParameter(p) } if err != nil { exit.Fatal("Exiting prompt: %v\n", err) @@ -51,7 +69,8 @@ func (p PromptHandler) GetParam(projectParams map[string]string) string { return "" } -func promptParameter(param moduleconfig.Parameter) (error, string) { +func promptParameter(prompt PromptHandler) (error, string) { + param := prompt.Parameter label := param.Label if param.Label == "" { label = param.Field @@ -72,6 +91,7 @@ func promptParameter(param moduleconfig.Parameter) (error, string) { Label: label, Default: defaultValue, AllowEdit: true, + Validate: prompt.Validate, } result, err = prompt.Run() } @@ -119,6 +139,7 @@ func PromptModuleParams(moduleConfig moduleconfig.ModuleConfig, parameters map[s promptHandler := PromptHandler{ promptConfig, NoCondition, + NoValidation, } result := promptHandler.GetParam(parameters) diff --git a/internal/context/prompts_test.go b/internal/context/prompts_test.go index 0d14a34ba..e002979bc 100644 --- a/internal/context/prompts_test.go +++ b/internal/context/prompts_test.go @@ -20,6 +20,7 @@ func TestGetParam(t *testing.T) { prompt := context.PromptHandler{ param, context.NoCondition, + context.NoValidation, } result := prompt.GetParam(projectParams) @@ -35,6 +36,7 @@ func TestGetParam(t *testing.T) { prompt := context.PromptHandler{ param, context.NoCondition, + context.NoValidation, } result := prompt.GetParam(map[string]string{ @@ -52,6 +54,7 @@ func TestGetParam(t *testing.T) { prompt := context.PromptHandler{ param, context.NoCondition, + context.NoValidation, } result := prompt.GetParam(projectParams) diff --git a/internal/module/module.go b/internal/module/module.go index 3c4a18605..743f599b8 100644 --- a/internal/module/module.go +++ b/internal/module/module.go @@ -7,11 +7,13 @@ import ( "log" "path" "regexp" + "sync" "github.com/commitdev/zero/internal/config" "github.com/commitdev/zero/internal/config/moduleconfig" "github.com/commitdev/zero/internal/constants" "github.com/commitdev/zero/internal/util" + "github.com/commitdev/zero/pkg/util/exit" "github.com/hashicorp/go-getter" ) @@ -21,17 +23,24 @@ type TemplateModule struct { Config moduleconfig.ModuleConfig } -// FetchModule downloads the remote module source (or loads the local files) and parses the module config yaml -func FetchModule(source string) (moduleconfig.ModuleConfig, error) { - config := moduleconfig.ModuleConfig{} +// FetchModule downloads the remote module source if necessary. Meant to be run in a goroutine. +func FetchModule(source string, wg *sync.WaitGroup) { + defer wg.Done() + localPath := GetSourceDir(source) if !isLocal(source) { err := getter.Get(localPath, source) if err != nil { - return config, err + exit.Fatal("Failed to fetch remote module from %s: %v\n", source, err) } } + return +} +// ParseModuleConfig loads the local config file for a module and parses the yaml +func ParseModuleConfig(source string) (moduleconfig.ModuleConfig, error) { + localPath := GetSourceDir(source) + config := moduleconfig.ModuleConfig{} configPath := path.Join(localPath, constants.ZeroModuleYml) config, err := moduleconfig.LoadModuleConfig(configPath) return config, err diff --git a/internal/module/module_test.go b/internal/module/module_test.go index 40ca15c9d..4b0d82a6e 100644 --- a/internal/module/module_test.go +++ b/internal/module/module_test.go @@ -32,7 +32,7 @@ func TestNewTemplateModule(t *testing.T) { var mod moduleconfig.ModuleConfig t.Run("Loading module from source", func(t *testing.T) { - mod, _ = module.FetchModule(testModuleSource) + mod, _ = module.ParseModuleConfig(testModuleSource) assert.Equal(t, "CI templates", mod.Name) })