Skip to content

Commit

Permalink
prompt global credentials according to modules
Browse files Browse the repository at this point in the history
  • Loading branch information
davidcheung committed Jun 17, 2020
1 parent bbaf538 commit dea7ac1
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 25 deletions.
2 changes: 1 addition & 1 deletion internal/config/globalconfig/global_config.go
Expand Up @@ -93,7 +93,7 @@ func readOrCreateUserCredentialsFile() []byte {
return data
}

func GetUserCredentials(targetProjectName string) ProjectCredential {
func GetProjectCredentials(targetProjectName string) ProjectCredential {
projects := LoadUserCredentials()

if val, ok := projects[targetProjectName]; ok {
Expand Down
14 changes: 7 additions & 7 deletions internal/config/globalconfig/global_config_test.go
Expand Up @@ -49,7 +49,7 @@ func TestReadOrCreateUserCredentialsFile(t *testing.T) {
_, fileStateErr := os.Stat(credPath)
assert.True(t, os.IsNotExist(fileStateErr), "File should not exist")
// attempting to read the file should create the file
globalconfig.GetUserCredentials("any-project")
globalconfig.GetProjectCredentials("any-project")

stats, err := os.Stat(credPath)
assert.False(t, os.IsNotExist(err), "File should be created")
Expand All @@ -63,7 +63,7 @@ func TestGetUserCredentials(t *testing.T) {

t.Run("Fixture file should have existing project with creds", func(t *testing.T) {
projectName := "my-project"
project := globalconfig.GetUserCredentials(projectName)
project := globalconfig.GetProjectCredentials(projectName)

// Reading from fixtures: tests/test_data/configs/credentials.yml
assert.Equal(t, "AKIAABCD", project.AWSResourceConfig.AccessKeyId)
Expand All @@ -74,7 +74,7 @@ func TestGetUserCredentials(t *testing.T) {

t.Run("Fixture file should support multiple projects", func(t *testing.T) {
projectName := "another-project"
project := globalconfig.GetUserCredentials(projectName)
project := globalconfig.GetProjectCredentials(projectName)
assert.Equal(t, "654", project.GithubResourceConfig.AccessToken)
})
}
Expand All @@ -86,18 +86,18 @@ func TestEditUserCredentials(t *testing.T) {

t.Run("Should create new project if not exist", func(t *testing.T) {
projectName := "test-project3"
project := globalconfig.GetUserCredentials(projectName)
project := globalconfig.GetProjectCredentials(projectName)
project.AWSResourceConfig.AccessKeyId = "TEST_KEY_ID_1"
globalconfig.Save(project)
newKeyID := globalconfig.GetUserCredentials(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.GetUserCredentials(projectName)
project := globalconfig.GetProjectCredentials(projectName)
project.AWSResourceConfig.AccessKeyId = "EDITED_ACCESS_KEY_ID"
globalconfig.Save(project)
newKeyID := globalconfig.GetUserCredentials(projectName).AWSResourceConfig.AccessKeyId
newKeyID := globalconfig.GetProjectCredentials(projectName).AWSResourceConfig.AccessKeyId
assert.Equal(t, "EDITED_ACCESS_KEY_ID", newKeyID)
})
}
2 changes: 1 addition & 1 deletion internal/config/moduleconfig/module_config.go
Expand Up @@ -12,7 +12,7 @@ type ModuleConfig struct {
Description string
Author string
TemplateConfig `yaml:"template"`
RequiredCredentials []string
RequiredCredentials []string `yaml:"requiredCredentials"`
Parameters []Parameter
}

Expand Down
86 changes: 70 additions & 16 deletions internal/context/init.go
Expand Up @@ -54,13 +54,10 @@ func Init(outDir string) *projectconfig.ZeroProjectConfig {

// 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)
}

projectCredentials := globalconfig.GetProjectCredentials(projectConfig.Name)
credentialPrompts := getCredentialPrompts(projectCredentials, moduleConfigs)
projectCredentials = promptCredentialsAndFillProjectCreds(credentialPrompts, projectCredentials)
globalconfig.Save(projectCredentials)
projectParameters := promptAllModules(moduleConfigs)

// Map parameter values back to specific modules
Expand All @@ -84,6 +81,7 @@ func Init(outDir string) *projectconfig.ZeroProjectConfig {

// TODO : Write the project config file. For now, print.
pp.Println(projectConfig)
pp.Print(projectCredentials)

// TODO: load ~/.zero/config.yml (or credentials)
// TODO: prompt global credentials
Expand Down Expand Up @@ -159,15 +157,6 @@ func getProjectPrompts(projectName string, modules map[string]moduleconfig.Modul
KeyMatchCondition("ShouldPushRepositories", "y"),
NoValidation,
},
"GithubPersonalToken": {
moduleconfig.Parameter{
Field: "GithubPersonalToken",
Label: "Github Personal Access Token with access to the above organization",
Default: globalconfig.GetUserCredentials(projectName).AccessToken,
},
KeyMatchCondition("ShouldPushRepositories", "y"),
NoValidation,
},
}

for moduleName, module := range modules {
Expand All @@ -187,6 +176,71 @@ func getProjectPrompts(projectName string, modules map[string]moduleconfig.Modul
return handlers
}

func getCredentialPrompts(projectCredentials globalconfig.ProjectCredential, moduleConfigs map[string]moduleconfig.ModuleConfig) map[string][]PromptHandler {
var uniqueVendors []string
for _, module := range moduleConfigs {
uniqueVendors = appendToSet(uniqueVendors, module.RequiredCredentials)
}
// map is to keep track of which vendor they belong to, to fill them back into the projectConfig
prompts := map[string][]PromptHandler{}
for _, vendor := range uniqueVendors {
prompts[vendor] = mapVendorToPrompts(projectCredentials, vendor)
}
return prompts
}

func mapVendorToPrompts(projectCred globalconfig.ProjectCredential, vendor string) []PromptHandler {
var prompts []PromptHandler

switch vendor {
case "aws":
awsPrompts := []PromptHandler{
{
moduleconfig.Parameter{
Field: "accessKeyId",
Label: "AWS Access Key ID",
Default: projectCred.AWSResourceConfig.AccessKeyId,
},
NoCondition,
NoValidation,
},
{
moduleconfig.Parameter{
Field: "secretAccessKey",
Label: "AWS Secret access key",
Default: projectCred.AWSResourceConfig.SecretAccessKey,
},
NoCondition,
NoValidation,
},
}
prompts = append(prompts, awsPrompts...)
case "github":
githubPrompt := PromptHandler{
moduleconfig.Parameter{
Field: "accessToken",
Label: "Github Personal Access Token with access to the above organization",
Default: projectCred.GithubResourceConfig.AccessToken,
},
NoCondition,
NoValidation,
}
prompts = append(prompts, githubPrompt)
case "circleci":
circleCiPrompt := PromptHandler{
moduleconfig.Parameter{
Field: "apiKey",
Label: "Circleci api key for CI/CD",
Default: projectCred.CircleCiResourceConfig.ApiKey,
},
NoCondition,
NoValidation,
}
prompts = append(prompts, circleCiPrompt)
}
return prompts
}

func chooseCloudProvider(projectConfig *projectconfig.ZeroProjectConfig) {
// @TODO move options into configs
providerPrompt := promptui.Select{
Expand Down
40 changes: 40 additions & 0 deletions internal/context/prompts.go
Expand Up @@ -8,9 +8,11 @@ import (
"regexp"
"strings"

"github.com/commitdev/zero/internal/config/globalconfig"
"github.com/commitdev/zero/internal/config/moduleconfig"
"github.com/commitdev/zero/pkg/util/exit"
"github.com/manifoldco/promptui"
"gopkg.in/yaml.v2"
)

type PromptHandler struct {
Expand Down Expand Up @@ -147,3 +149,41 @@ func PromptModuleParams(moduleConfig moduleconfig.ModuleConfig, parameters map[s
}
return parameters, nil
}

func promptCredentialsAndFillProjectCreds(credentialPrompts map[string][]PromptHandler, credentials globalconfig.ProjectCredential) globalconfig.ProjectCredential {
promptsValues := map[string]map[string]string{}

for vendor, prompts := range credentialPrompts {
vendorPromptValues := map[string]string{}

// vendors like AWS have multiple prompts (accessKeyId and secretAccessKey)
for _, prompt := range prompts {
vendorPromptValues[prompt.Field] = prompt.GetParam(map[string]string{})
}
promptsValues[vendor] = vendorPromptValues
}

// FIXME: what is a good way to dynamically modify partial data of a struct
// current just marashing to yaml, then unmarshaling into the base struct
yamlContent, _ := yaml.Marshal(promptsValues)
yaml.Unmarshal(yamlContent, &credentials)
return credentials
}

func appendToSet(set []string, toAppend []string) []string {
for _, appendee := range toAppend {
if !itemInSlice(set, appendee) {
set = append(set, appendee)
}
}
return set
}

func itemInSlice(slice []string, target string) bool {
for _, item := range slice {
if item == target {
return true
}
}
return false
}
4 changes: 4 additions & 0 deletions internal/module/module_test.go
Expand Up @@ -46,6 +46,10 @@ func TestParseModuleConfig(t *testing.T) {
assert.Equal(t, "CI Platform", param.Label)
})

t.Run("requiredCredentials are loaded", func(t *testing.T) {
assert.Equal(t, []string{"aws", "circleci", "github"}, mod.RequiredCredentials)
})

t.Run("TemplateConfig is unmarshaled", func(t *testing.T) {
mod, _ = module.ParseModuleConfig(testModuleSource)
assert.Equal(t, ".circleci", mod.TemplateConfig.OutputDir)
Expand Down
5 changes: 5 additions & 0 deletions tests/test_data/modules/ci/zero-module.yml
Expand Up @@ -4,6 +4,11 @@ author: ""
icon: ""
thumbnail: ""

requiredCredentials:
- aws
- circleci
- github

# Template variables to populate, these could be overwritten by the file spefic frontmatter variables
template:
# strictMode: true # will only parse files that includes the .tmpl.* extension, otherwise it will copy file
Expand Down

0 comments on commit dea7ac1

Please sign in to comment.