-
Notifications
You must be signed in to change notification settings - Fork 53
support module-config default / value #154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,8 @@ type Registry map[string][]string | |
| // Create cloud provider context | ||
| func Init(outDir string) *projectconfig.ZeroProjectConfig { | ||
| projectConfig := defaultProjConfig() | ||
| projectConfig.Name = promptProjectName() | ||
|
|
||
| projectConfig.Name = getProjectNamePrompt().GetParam(projectConfig.Parameters) | ||
|
|
||
| rootDir := path.Join(outDir, projectConfig.Name) | ||
| flog.Infof(":tada: Creating project") | ||
|
|
@@ -36,15 +37,17 @@ func Init(outDir string) *projectconfig.ZeroProjectConfig { | |
| exit.Fatal("Error creating root: %v ", err) | ||
| } | ||
|
|
||
| projectConfig.Context["ShouldPushRepoUpstream"] = promptPushRepoUpstream() | ||
| projectConfig.Context["GithubRootOrg"] = promptGithubRootOrg() | ||
| projectConfig.Context["githubPersonalToken"] = promptGithubPersonalToken(projectConfig.Name) | ||
|
|
||
| // chooseCloudProvider(&projectConfig) | ||
| // fmt.Println(&projectConfig) | ||
| // s := project.GetSecrets(rootDir) | ||
| // fillProviderDetails(&projectConfig, s) | ||
| // fmt.Println(&projectConfig) | ||
| 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 { | ||
|
|
@@ -53,7 +56,7 @@ func Init(outDir string) *projectconfig.ZeroProjectConfig { | |
|
|
||
| projectParameters := promptAllModules(moduleConfigs) | ||
| for k, v := range projectParameters { | ||
| projectConfig.Context[k] = v | ||
| projectConfig.Parameters[k] = v | ||
| // TODO: Add parameters to module structs inside project | ||
| } | ||
|
|
||
|
|
@@ -82,77 +85,54 @@ func promptAllModules(modules map[string]moduleconfig.ModuleConfig) map[string]s | |
| parameterValues := make(map[string]string) | ||
| for _, config := range modules { | ||
| var err error | ||
| parameterValues, err = module.PromptParams(config, parameterValues) | ||
| parameterValues, err = PromptModuleParams(config, parameterValues) | ||
| if err != nil { | ||
| exit.Fatal("Exiting prompt: %v\n", err) | ||
| } | ||
| } | ||
| return parameterValues | ||
| } | ||
|
|
||
| // global configs | ||
| func promptPushRepoUpstream() string { | ||
| providerPrompt := promptui.Prompt{ | ||
| Label: "Should the created projects be checked into github automatically? (y/n)", | ||
| Default: "y", | ||
| AllowEdit: false, | ||
| } | ||
| providerResult, err := providerPrompt.Run() | ||
| if err != nil { | ||
| exit.Fatal("Exiting prompt: %v\n", err) | ||
| } | ||
| return providerResult | ||
| } | ||
|
|
||
| func promptGithubRootOrg() string { | ||
| providerPrompt := promptui.Prompt{ | ||
| Label: "What's the root of the github org to create repositories in?", | ||
| Default: "github.com/", | ||
| AllowEdit: true, | ||
| } | ||
| result, err := providerPrompt.Run() | ||
| if err != nil { | ||
| exit.Fatal("Exiting prompt: %v\n", err) | ||
| } | ||
| return result | ||
| } | ||
|
|
||
| func promptGithubPersonalToken(projectName string) string { | ||
| defaultToken := "" | ||
|
|
||
| project := globalconfig.GetUserCredentials(projectName) | ||
| if project.GithubResourceConfig.AccessToken != "" { | ||
| defaultToken = project.GithubResourceConfig.AccessToken | ||
| } | ||
|
|
||
| providerPrompt := promptui.Prompt{ | ||
| Label: "Github Personal Access Token with access to the above organization", | ||
| Default: defaultToken, | ||
| } | ||
| result, err := providerPrompt.Run() | ||
| if err != nil { | ||
| exit.Fatal("Prompt failed %v\n", err) | ||
| } | ||
|
|
||
| // If its different from saved token, update it | ||
| if project.GithubResourceConfig.AccessToken != result { | ||
| project.GithubResourceConfig.AccessToken = result | ||
| globalconfig.Save(project) | ||
| // Project name is prompt individually because the rest of the prompts | ||
| // requires the projectName to populate defaults | ||
| func getProjectNamePrompt() PromptHandler { | ||
| return PromptHandler{ | ||
| moduleconfig.Parameter{ | ||
| Field: "projectName", | ||
| Label: "Project Name", | ||
| Default: "", | ||
| }, | ||
| NoCondition, | ||
| } | ||
| return result | ||
| } | ||
|
|
||
| func promptProjectName() string { | ||
| providerPrompt := promptui.Prompt{ | ||
| Label: "Project Name", | ||
| Default: "", | ||
| AllowEdit: false, | ||
| } | ||
| result, err := providerPrompt.Run() | ||
| if err != nil { | ||
| exit.Fatal("Prompt failed %v\n", err) | ||
| func getProjectPrompts(projectName string) map[string]PromptHandler { | ||
| return map[string]PromptHandler{ | ||
| "ShouldPushRepoUpstream": { | ||
| moduleconfig.Parameter{ | ||
| Field: "ShouldPushRepoUpstream", | ||
| Label: "Should the created projects be checked into github automatically? (y/n)", | ||
| Default: "y", | ||
| }, | ||
| NoCondition, | ||
| }, | ||
| "GithubRootOrg": { | ||
| moduleconfig.Parameter{ | ||
| Field: "GithubRootOrg", | ||
| Label: "What's the root of the github org to create repositories in?", | ||
| Default: "github.com/", | ||
| }, | ||
| KeyMatchCondition("ShouldPushRepoUpstream", "y"), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice implementation |
||
| }, | ||
| "githubPersonalToken": { | ||
| moduleconfig.Parameter{ | ||
| Field: "githubPersonalToken", | ||
| Label: "Github Personal Access Token with access to the above organization", | ||
| Default: globalconfig.GetUserCredentials(projectName).AccessToken, | ||
| }, | ||
| KeyMatchCondition("ShouldPushRepoUpstream", "y"), | ||
| }, | ||
| } | ||
| return result | ||
| } | ||
|
|
||
| func chooseCloudProvider(projectConfig *projectconfig.ZeroProjectConfig) { | ||
|
|
@@ -241,7 +221,7 @@ func defaultProjConfig() projectconfig.ZeroProjectConfig { | |
| Infrastructure: projectconfig.Infrastructure{ | ||
| AWS: nil, | ||
| }, | ||
| Context: map[string]string{}, | ||
| Modules: []string{}, | ||
| Parameters: map[string]string{}, | ||
| Modules: []string{}, | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| package context | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "log" | ||
| "os" | ||
| "os/exec" | ||
| "regexp" | ||
|
|
||
| "github.com/commitdev/zero/internal/config/moduleconfig" | ||
| "github.com/commitdev/zero/pkg/util/exit" | ||
| "github.com/manifoldco/promptui" | ||
| ) | ||
|
|
||
| type PromptHandler struct { | ||
| moduleconfig.Parameter | ||
| Condition func(map[string]string) bool | ||
| } | ||
|
|
||
| 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 | ||
| } | ||
| } | ||
|
|
||
| // TODO: validation / allow prompt retry ...etc | ||
| func (p PromptHandler) GetParam(projectParams map[string]string) string { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like this would be a good one to have a test for, exercising the different types.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i did, i aliased my |
||
| var err error | ||
| var result string | ||
| if p.Condition(projectParams) { | ||
| // TODO: figure out scope of projectParams per project | ||
| // potentially dangerous to have cross module env leaking | ||
| // so if community module has an `execute: twitter tweet $ENV` | ||
| // it wouldnt leak things the module shouldnt have access to | ||
| if p.Parameter.Execute != "" { | ||
| result = executeCmd(p.Parameter.Execute, projectParams) | ||
| } else if p.Parameter.Value != "" { | ||
| result = p.Parameter.Value | ||
| } else { | ||
| err, result = promptParameter(p.Parameter) | ||
| } | ||
| if err != nil { | ||
| exit.Fatal("Exiting prompt: %v\n", err) | ||
| } | ||
|
|
||
| return sanitizeParameterValue(result) | ||
| } | ||
| return "" | ||
| } | ||
|
|
||
| func promptParameter(param moduleconfig.Parameter) (error, string) { | ||
| label := param.Label | ||
| if param.Label == "" { | ||
| label = param.Field | ||
| } | ||
| defaultValue := param.Default | ||
|
|
||
| var err error | ||
| var result string | ||
| if len(param.Options) > 0 { | ||
| prompt := promptui.Select{ | ||
| Label: label, | ||
| Items: param.Options, | ||
| } | ||
| _, result, err = prompt.Run() | ||
|
|
||
| } else { | ||
| prompt := promptui.Prompt{ | ||
| Label: label, | ||
| Default: defaultValue, | ||
| AllowEdit: true, | ||
| } | ||
| result, err = prompt.Run() | ||
| } | ||
| if err != nil { | ||
| exit.Fatal("Exiting prompt: %v\n", err) | ||
| } | ||
|
|
||
| return nil, result | ||
| } | ||
|
|
||
| func executeCmd(command string, envVars map[string]string) string { | ||
| cmd := exec.Command("bash", "-c", command) | ||
| cmd.Env = appendProjectEnvToCmdEnv(envVars, os.Environ()) | ||
| out, err := cmd.Output() | ||
|
|
||
| if err != nil { | ||
| log.Fatalf("Failed to execute %v\n", err) | ||
| } | ||
| return string(out) | ||
| } | ||
|
|
||
| // aws cli prints output with linebreak in them | ||
| func sanitizeParameterValue(str string) string { | ||
| re := regexp.MustCompile("\\n") | ||
| 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) { | ||
|
|
||
| for _, promptConfig := range moduleConfig.Parameters { | ||
| // deduplicate fields already prompted and received | ||
| if _, isAlreadySet := parameters[promptConfig.Field]; isAlreadySet { | ||
| continue | ||
| } | ||
| promptHandler := PromptHandler{ | ||
| promptConfig, | ||
| NoCondition, | ||
| } | ||
| result := promptHandler.GetParam(parameters) | ||
|
|
||
| parameters[promptConfig.Field] = result | ||
| } | ||
| return parameters, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| package context_test | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/commitdev/zero/internal/config/moduleconfig" | ||
| "github.com/commitdev/zero/internal/context" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestGetParam(t *testing.T) { | ||
| projectParams := map[string]string{} | ||
| t.Run("Should execute params without prompt", func(t *testing.T) { | ||
| param := moduleconfig.Parameter{ | ||
| Field: "account-id", | ||
| Execute: "echo \"my-acconut-id\"", | ||
| } | ||
|
|
||
| prompt := context.PromptHandler{ | ||
| param, | ||
| context.NoCondition, | ||
| } | ||
|
|
||
| result := prompt.GetParam(projectParams) | ||
| assert.Equal(t, "my-acconut-id", result) | ||
| }) | ||
|
|
||
| t.Run("executes with project context", func(t *testing.T) { | ||
| param := moduleconfig.Parameter{ | ||
| Field: "myEnv", | ||
| Execute: "echo $INJECTEDENV", | ||
| } | ||
|
|
||
| prompt := context.PromptHandler{ | ||
| param, | ||
| context.NoCondition, | ||
| } | ||
|
|
||
| result := prompt.GetParam(map[string]string{ | ||
| "INJECTEDENV": "SOME_ENV_VAR_VALUE", | ||
| }) | ||
| assert.Equal(t, "SOME_ENV_VAR_VALUE", result) | ||
| }) | ||
|
|
||
| t.Run("Should return static value", func(t *testing.T) { | ||
| param := moduleconfig.Parameter{ | ||
| Field: "placeholder", | ||
| Value: "lorem-ipsum", | ||
| } | ||
|
|
||
| prompt := context.PromptHandler{ | ||
| param, | ||
| context.NoCondition, | ||
| } | ||
|
|
||
| result := prompt.GetParam(projectParams) | ||
| assert.Equal(t, "lorem-ipsum", result) | ||
| }) | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need to do this as part of this PR but shouldn't these be part of the projectConfig struct rather than parameters? Parameters are passed into project templates, but these values we will be using for zero itself during the create phase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll do this as part of my ticket.