Skip to content

Commit a09ffcf

Browse files
authored
Merge pull request #154 from commitdev/prompt-default
support module-config default / value
2 parents 9d6372d + 544ab60 commit a09ffcf

File tree

9 files changed

+282
-164
lines changed

9 files changed

+282
-164
lines changed

internal/config/projectconfig/project_config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
type ZeroProjectConfig struct {
1212
Name string
1313
Infrastructure Infrastructure // TODO simplify and flatten / rename?
14-
Context map[string]string
14+
Parameters map[string]string
1515
Modules []string
1616
}
1717

internal/context/init.go

Lines changed: 53 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ type Registry map[string][]string
2424
// Create cloud provider context
2525
func Init(outDir string) *projectconfig.ZeroProjectConfig {
2626
projectConfig := defaultProjConfig()
27-
projectConfig.Name = promptProjectName()
27+
28+
projectConfig.Name = getProjectNamePrompt().GetParam(projectConfig.Parameters)
2829

2930
rootDir := path.Join(outDir, projectConfig.Name)
3031
flog.Infof(":tada: Creating project")
@@ -36,15 +37,17 @@ func Init(outDir string) *projectconfig.ZeroProjectConfig {
3637
exit.Fatal("Error creating root: %v ", err)
3738
}
3839

39-
projectConfig.Context["ShouldPushRepoUpstream"] = promptPushRepoUpstream()
40-
projectConfig.Context["GithubRootOrg"] = promptGithubRootOrg()
41-
projectConfig.Context["githubPersonalToken"] = promptGithubPersonalToken(projectConfig.Name)
42-
43-
// chooseCloudProvider(&projectConfig)
44-
// fmt.Println(&projectConfig)
45-
// s := project.GetSecrets(rootDir)
46-
// fillProviderDetails(&projectConfig, s)
47-
// fmt.Println(&projectConfig)
40+
prompts := getProjectPrompts(projectConfig.Name)
41+
projectConfig.Parameters["ShouldPushRepoUpstream"] = prompts["ShouldPushRepoUpstream"].GetParam(projectConfig.Parameters)
42+
// Prompting for push-up stream, then conditionally prompting for github
43+
projectConfig.Parameters["GithubRootOrg"] = prompts["GithubRootOrg"].GetParam(projectConfig.Parameters)
44+
personalToken := prompts["githubPersonalToken"].GetParam(projectConfig.Parameters)
45+
if personalToken != "" && personalToken != globalconfig.GetUserCredentials(projectConfig.Name).AccessToken {
46+
projectConfig.Parameters["githubPersonalToken"] = personalToken
47+
projectCredential := globalconfig.GetUserCredentials(projectConfig.Name)
48+
projectCredential.GithubResourceConfig.AccessToken = personalToken
49+
globalconfig.Save(projectCredential)
50+
}
4851
moduleSources := chooseStack(getRegistry())
4952
moduleConfigs := loadAllModules(moduleSources)
5053
for _ = range moduleConfigs {
@@ -53,7 +56,7 @@ func Init(outDir string) *projectconfig.ZeroProjectConfig {
5356

5457
projectParameters := promptAllModules(moduleConfigs)
5558
for k, v := range projectParameters {
56-
projectConfig.Context[k] = v
59+
projectConfig.Parameters[k] = v
5760
// TODO: Add parameters to module structs inside project
5861
}
5962

@@ -82,77 +85,54 @@ func promptAllModules(modules map[string]moduleconfig.ModuleConfig) map[string]s
8285
parameterValues := make(map[string]string)
8386
for _, config := range modules {
8487
var err error
85-
parameterValues, err = module.PromptParams(config, parameterValues)
88+
parameterValues, err = PromptModuleParams(config, parameterValues)
8689
if err != nil {
8790
exit.Fatal("Exiting prompt: %v\n", err)
8891
}
8992
}
9093
return parameterValues
9194
}
9295

93-
// global configs
94-
func promptPushRepoUpstream() string {
95-
providerPrompt := promptui.Prompt{
96-
Label: "Should the created projects be checked into github automatically? (y/n)",
97-
Default: "y",
98-
AllowEdit: false,
99-
}
100-
providerResult, err := providerPrompt.Run()
101-
if err != nil {
102-
exit.Fatal("Exiting prompt: %v\n", err)
103-
}
104-
return providerResult
105-
}
106-
107-
func promptGithubRootOrg() string {
108-
providerPrompt := promptui.Prompt{
109-
Label: "What's the root of the github org to create repositories in?",
110-
Default: "github.com/",
111-
AllowEdit: true,
112-
}
113-
result, err := providerPrompt.Run()
114-
if err != nil {
115-
exit.Fatal("Exiting prompt: %v\n", err)
116-
}
117-
return result
118-
}
119-
120-
func promptGithubPersonalToken(projectName string) string {
121-
defaultToken := ""
122-
123-
project := globalconfig.GetUserCredentials(projectName)
124-
if project.GithubResourceConfig.AccessToken != "" {
125-
defaultToken = project.GithubResourceConfig.AccessToken
126-
}
127-
128-
providerPrompt := promptui.Prompt{
129-
Label: "Github Personal Access Token with access to the above organization",
130-
Default: defaultToken,
131-
}
132-
result, err := providerPrompt.Run()
133-
if err != nil {
134-
exit.Fatal("Prompt failed %v\n", err)
135-
}
136-
137-
// If its different from saved token, update it
138-
if project.GithubResourceConfig.AccessToken != result {
139-
project.GithubResourceConfig.AccessToken = result
140-
globalconfig.Save(project)
96+
// Project name is prompt individually because the rest of the prompts
97+
// requires the projectName to populate defaults
98+
func getProjectNamePrompt() PromptHandler {
99+
return PromptHandler{
100+
moduleconfig.Parameter{
101+
Field: "projectName",
102+
Label: "Project Name",
103+
Default: "",
104+
},
105+
NoCondition,
141106
}
142-
return result
143107
}
144108

145-
func promptProjectName() string {
146-
providerPrompt := promptui.Prompt{
147-
Label: "Project Name",
148-
Default: "",
149-
AllowEdit: false,
150-
}
151-
result, err := providerPrompt.Run()
152-
if err != nil {
153-
exit.Fatal("Prompt failed %v\n", err)
109+
func getProjectPrompts(projectName string) map[string]PromptHandler {
110+
return map[string]PromptHandler{
111+
"ShouldPushRepoUpstream": {
112+
moduleconfig.Parameter{
113+
Field: "ShouldPushRepoUpstream",
114+
Label: "Should the created projects be checked into github automatically? (y/n)",
115+
Default: "y",
116+
},
117+
NoCondition,
118+
},
119+
"GithubRootOrg": {
120+
moduleconfig.Parameter{
121+
Field: "GithubRootOrg",
122+
Label: "What's the root of the github org to create repositories in?",
123+
Default: "github.com/",
124+
},
125+
KeyMatchCondition("ShouldPushRepoUpstream", "y"),
126+
},
127+
"githubPersonalToken": {
128+
moduleconfig.Parameter{
129+
Field: "githubPersonalToken",
130+
Label: "Github Personal Access Token with access to the above organization",
131+
Default: globalconfig.GetUserCredentials(projectName).AccessToken,
132+
},
133+
KeyMatchCondition("ShouldPushRepoUpstream", "y"),
134+
},
154135
}
155-
return result
156136
}
157137

158138
func chooseCloudProvider(projectConfig *projectconfig.ZeroProjectConfig) {
@@ -241,7 +221,7 @@ func defaultProjConfig() projectconfig.ZeroProjectConfig {
241221
Infrastructure: projectconfig.Infrastructure{
242222
AWS: nil,
243223
},
244-
Context: map[string]string{},
245-
Modules: []string{},
224+
Parameters: map[string]string{},
225+
Modules: []string{},
246226
}
247227
}

internal/context/prompts.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package context
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
"os/exec"
8+
"regexp"
9+
10+
"github.com/commitdev/zero/internal/config/moduleconfig"
11+
"github.com/commitdev/zero/pkg/util/exit"
12+
"github.com/manifoldco/promptui"
13+
)
14+
15+
type PromptHandler struct {
16+
moduleconfig.Parameter
17+
Condition func(map[string]string) bool
18+
}
19+
20+
func NoCondition(map[string]string) bool {
21+
return true
22+
}
23+
func KeyMatchCondition(key string, value string) func(map[string]string) bool {
24+
return func(param map[string]string) bool {
25+
return param[key] == value
26+
}
27+
}
28+
29+
// TODO: validation / allow prompt retry ...etc
30+
func (p PromptHandler) GetParam(projectParams map[string]string) string {
31+
var err error
32+
var result string
33+
if p.Condition(projectParams) {
34+
// TODO: figure out scope of projectParams per project
35+
// potentially dangerous to have cross module env leaking
36+
// so if community module has an `execute: twitter tweet $ENV`
37+
// it wouldnt leak things the module shouldnt have access to
38+
if p.Parameter.Execute != "" {
39+
result = executeCmd(p.Parameter.Execute, projectParams)
40+
} else if p.Parameter.Value != "" {
41+
result = p.Parameter.Value
42+
} else {
43+
err, result = promptParameter(p.Parameter)
44+
}
45+
if err != nil {
46+
exit.Fatal("Exiting prompt: %v\n", err)
47+
}
48+
49+
return sanitizeParameterValue(result)
50+
}
51+
return ""
52+
}
53+
54+
func promptParameter(param moduleconfig.Parameter) (error, string) {
55+
label := param.Label
56+
if param.Label == "" {
57+
label = param.Field
58+
}
59+
defaultValue := param.Default
60+
61+
var err error
62+
var result string
63+
if len(param.Options) > 0 {
64+
prompt := promptui.Select{
65+
Label: label,
66+
Items: param.Options,
67+
}
68+
_, result, err = prompt.Run()
69+
70+
} else {
71+
prompt := promptui.Prompt{
72+
Label: label,
73+
Default: defaultValue,
74+
AllowEdit: true,
75+
}
76+
result, err = prompt.Run()
77+
}
78+
if err != nil {
79+
exit.Fatal("Exiting prompt: %v\n", err)
80+
}
81+
82+
return nil, result
83+
}
84+
85+
func executeCmd(command string, envVars map[string]string) string {
86+
cmd := exec.Command("bash", "-c", command)
87+
cmd.Env = appendProjectEnvToCmdEnv(envVars, os.Environ())
88+
out, err := cmd.Output()
89+
90+
if err != nil {
91+
log.Fatalf("Failed to execute %v\n", err)
92+
}
93+
return string(out)
94+
}
95+
96+
// aws cli prints output with linebreak in them
97+
func sanitizeParameterValue(str string) string {
98+
re := regexp.MustCompile("\\n")
99+
return re.ReplaceAllString(str, "")
100+
}
101+
102+
func appendProjectEnvToCmdEnv(envMap map[string]string, envList []string) []string {
103+
for key, val := range envMap {
104+
if val != "" {
105+
envList = append(envList, fmt.Sprintf("%s=%s", key, val))
106+
}
107+
}
108+
return envList
109+
}
110+
111+
// PromptParams renders series of prompt UI based on the config
112+
func PromptModuleParams(moduleConfig moduleconfig.ModuleConfig, parameters map[string]string) (map[string]string, error) {
113+
114+
for _, promptConfig := range moduleConfig.Parameters {
115+
// deduplicate fields already prompted and received
116+
if _, isAlreadySet := parameters[promptConfig.Field]; isAlreadySet {
117+
continue
118+
}
119+
promptHandler := PromptHandler{
120+
promptConfig,
121+
NoCondition,
122+
}
123+
result := promptHandler.GetParam(parameters)
124+
125+
parameters[promptConfig.Field] = result
126+
}
127+
return parameters, nil
128+
}

internal/context/prompts_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package context_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/commitdev/zero/internal/config/moduleconfig"
7+
"github.com/commitdev/zero/internal/context"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestGetParam(t *testing.T) {
13+
projectParams := map[string]string{}
14+
t.Run("Should execute params without prompt", func(t *testing.T) {
15+
param := moduleconfig.Parameter{
16+
Field: "account-id",
17+
Execute: "echo \"my-acconut-id\"",
18+
}
19+
20+
prompt := context.PromptHandler{
21+
param,
22+
context.NoCondition,
23+
}
24+
25+
result := prompt.GetParam(projectParams)
26+
assert.Equal(t, "my-acconut-id", result)
27+
})
28+
29+
t.Run("executes with project context", func(t *testing.T) {
30+
param := moduleconfig.Parameter{
31+
Field: "myEnv",
32+
Execute: "echo $INJECTEDENV",
33+
}
34+
35+
prompt := context.PromptHandler{
36+
param,
37+
context.NoCondition,
38+
}
39+
40+
result := prompt.GetParam(map[string]string{
41+
"INJECTEDENV": "SOME_ENV_VAR_VALUE",
42+
})
43+
assert.Equal(t, "SOME_ENV_VAR_VALUE", result)
44+
})
45+
46+
t.Run("Should return static value", func(t *testing.T) {
47+
param := moduleconfig.Parameter{
48+
Field: "placeholder",
49+
Value: "lorem-ipsum",
50+
}
51+
52+
prompt := context.PromptHandler{
53+
param,
54+
context.NoCondition,
55+
}
56+
57+
result := prompt.GetParam(projectParams)
58+
assert.Equal(t, "lorem-ipsum", result)
59+
})
60+
61+
}

internal/generate/generate_infrastructure.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func Execute(cfg *projectconfig.ZeroProjectConfig, pathPrefix string) {
7474
"cognito_client_id",
7575
}
7676
outputValues := GetOutputs(cfg, pathPrefix, outputs)
77-
cfg.Context["cognito_pool_id"] = outputValues["cognito_pool_id"]
78-
cfg.Context["cognito_client_id"] = outputValues["cognito_client_id"]
77+
cfg.Parameters["cognito_pool_id"] = outputValues["cognito_pool_id"]
78+
cfg.Parameters["cognito_client_id"] = outputValues["cognito_client_id"]
7979
}
8080
}

internal/generate/generate_modules.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,9 @@ func GenerateModules(cfg *config.GeneratorConfig) {
3737

3838
// Prompt for module params and execute each of the generator modules
3939
for _, mod := range templateModules {
40-
// FIXME:(david) generate flow probably wont need prompts anymore
41-
// added an empty map to fix test temporarily
42-
err := mod.PromptParams(map[string]string{})
43-
if err != nil {
44-
flog.Warnf("module %s: params prompt failed", mod.Source)
45-
}
40+
// TODO: read zero-project.yml instead
4641

47-
err = Generate(mod, cfg)
42+
err := Generate(mod, cfg)
4843
if err != nil {
4944
exit.Error("module %s: %s", mod.Source, err)
5045
}

0 commit comments

Comments
 (0)