Skip to content

Commit

Permalink
Merge pull request #181 from commitdev/credentials-in-execute
Browse files Browse the repository at this point in the history
vendor credentials is env-vars for execute
  • Loading branch information
davidcheung committed Jun 25, 2020
2 parents 57334dc + 767af71 commit 72db194
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 26 deletions.
11 changes: 9 additions & 2 deletions internal/apply/apply.go
Expand Up @@ -77,11 +77,18 @@ func applyAll(dir string, projectConfig projectconfig.ZeroProjectConfig, applyEn
modulePath = filepath.Join(dir, modulePath)
}

// TODO: in the case user lost the `/tmp` (module source dir), this will fail
// and we should redownload the module for the user
modConfig, err := module.ParseModuleConfig(modulePath)
if err != nil {
exit.Fatal("Failed to load module config, credentials cannot be injected properly")
}

// Get project credentials for the makefile
credentials := globalconfig.GetProjectCredentials(projectConfig.Name)

credentialEnvs := credentials.SelectedVendorsCredentialsAsEnv(modConfig.RequiredCredentials)
envList = util.AppendProjectEnvToCmdEnv(mod.Parameters, envList)
envList = util.AppendProjectEnvToCmdEnv(credentials.AsEnvVars(), envList)
envList = util.AppendProjectEnvToCmdEnv(credentialEnvs, envList)
util.ExecuteCommand(exec.Command("make"), modulePath, envList)
return nil
})
Expand Down
42 changes: 34 additions & 8 deletions internal/config/globalconfig/global_config.go
Expand Up @@ -8,8 +8,10 @@ import (
"os/user"
"path"
"reflect"
"strings"

"github.com/commitdev/zero/internal/constants"
"github.com/commitdev/zero/internal/util"
"github.com/commitdev/zero/pkg/util/exit"
yaml "gopkg.in/yaml.v2"
)
Expand All @@ -20,20 +22,20 @@ type ProjectCredentials map[string]ProjectCredential

type ProjectCredential struct {
ProjectName string `yaml:"-"`
AWSResourceConfig `yaml:"aws,omitempty"`
GithubResourceConfig `yaml:"github,omitempty"`
CircleCiResourceConfig `yaml:"circleci,omitempty"`
AWSResourceConfig `yaml:"aws,omitempty" vendor:"aws"`
GithubResourceConfig `yaml:"github,omitempty" vendor:"github"`
CircleCiResourceConfig `yaml:"circleci,omitempty" vendor:"circleci"`
}

type AWSResourceConfig struct {
AccessKeyID string `yaml:"accessKeyId,omitempty" env:"AWS_ACCESS_KEY_ID"`
SecretAccessKey string `yaml:"secretAccessKey,omitempty" env:"AWS_SECRET_ACCESS_KEY"`
AccessKeyID string `yaml:"accessKeyId,omitempty" env:"AWS_ACCESS_KEY_ID,omitempty"`
SecretAccessKey string `yaml:"secretAccessKey,omitempty" env:"AWS_SECRET_ACCESS_KEY,omitempty"`
}
type GithubResourceConfig struct {
AccessToken string `yaml:"accessToken,omitempty" env:"GITHUB_ACCESS_TOKEN"`
AccessToken string `yaml:"accessToken,omitempty" env:"GITHUB_ACCESS_TOKEN,omitempty"`
}
type CircleCiResourceConfig struct {
ApiKey string `yaml:"apiKey,omitempty" env:"CIRCLECI_API_KEY"`
ApiKey string `yaml:"apiKey,omitempty" env:"CIRCLECI_API_KEY,omitempty"`
}

func (p ProjectCredentials) Unmarshal(data []byte) error {
Expand Down Expand Up @@ -74,12 +76,36 @@ func gatherFieldTags(t reflect.Value, list map[string]string) map[string]string
}

if env := fieldType.Tag.Get("env"); env != "" {
list[env] = fieldValue.String()
name, opts := parseTag(env)
if idx := strings.Index(opts, "omitempty"); idx != -1 && fieldValue.String() == "" {
continue
}
list[name] = fieldValue.String()
}
}
return list
}

func (p ProjectCredential) SelectedVendorsCredentialsAsEnv(vendors []string) map[string]string {
t := reflect.ValueOf(p)
envs := map[string]string{}
for i := 0; i < t.NumField(); i++ {
childStruct := t.Type().Field(i)
childValue := t.Field(i)
if tag := childStruct.Tag.Get("vendor"); tag != "" && util.ItemInSlice(vendors, tag) {
envs = gatherFieldTags(childValue, envs)
}
}
return envs
}

func parseTag(tag string) (string, string) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tag[idx+1:]
}
return tag, ""
}

func LoadUserCredentials() ProjectCredentials {
data := readOrCreateUserCredentialsFile()

Expand Down
47 changes: 47 additions & 0 deletions internal/config/globalconfig/global_config_test.go
Expand Up @@ -119,4 +119,51 @@ func TestMarshalProjectCredentialAsEnvVars(t *testing.T) {
assert.Equal(t, "SAK", envVars["AWS_SECRET_ACCESS_KEY"])
assert.Equal(t, "APIKEY", envVars["CIRCLECI_API_KEY"])
})

t.Run("should honor omitempty and left out empty values", func(t *testing.T) {
pc := globalconfig.ProjectCredential{}

envVars := pc.AsEnvVars()
assert.Equal(t, 0, len(envVars))
})
}

func TestMarshalSelectedVendorsCredentialsAsEnv(t *testing.T) {
pc := globalconfig.ProjectCredential{
AWSResourceConfig: globalconfig.AWSResourceConfig{
AccessKeyID: "AKID",
SecretAccessKey: "SAK",
},
GithubResourceConfig: globalconfig.GithubResourceConfig{
AccessToken: "FOOBAR",
},
CircleCiResourceConfig: globalconfig.CircleCiResourceConfig{
ApiKey: "APIKEY",
},
}

t.Run("cherry pick credentials by vendor", func(t *testing.T) {
envs := pc.SelectedVendorsCredentialsAsEnv([]string{"aws", "github"})
assert.Equal(t, "AKID", envs["AWS_ACCESS_KEY_ID"])
assert.Equal(t, "SAK", envs["AWS_SECRET_ACCESS_KEY"])
assert.Equal(t, "FOOBAR", envs["GITHUB_ACCESS_TOKEN"])
})

t.Run("omits vendors not selected", func(t *testing.T) {
envs := pc.SelectedVendorsCredentialsAsEnv([]string{"github"})
assert.Equal(t, "FOOBAR", envs["GITHUB_ACCESS_TOKEN"])

_, hasAWSKeyID := envs["AWS_ACCESS_KEY_ID"]
assert.Equal(t, false, hasAWSKeyID)
_, hasAWSSecretAccessKey := envs["AWS_SECRET_ACCESS_KEY"]
assert.Equal(t, false, hasAWSSecretAccessKey)
_, hasCircleCIKey := envs["CIRCLECI_API_KEY"]
assert.Equal(t, false, hasCircleCIKey)
})

t.Run("omits vendors not selected", func(t *testing.T) {
envs := pc.SelectedVendorsCredentialsAsEnv([]string{})
assert.Equal(t, 0, len(envs))
})

}
10 changes: 6 additions & 4 deletions internal/init/init.go
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/commitdev/zero/internal/config/projectconfig"
"github.com/commitdev/zero/internal/module"
"github.com/commitdev/zero/internal/registry"
"github.com/commitdev/zero/internal/util"
project "github.com/commitdev/zero/pkg/credentials"
"github.com/commitdev/zero/pkg/util/exit"
"github.com/commitdev/zero/pkg/util/flog"
Expand Down Expand Up @@ -51,7 +52,7 @@ func Init(outDir string) *projectconfig.ZeroProjectConfig {
credentialPrompts := getCredentialPrompts(projectCredentials, moduleConfigs)
projectCredentials = promptCredentialsAndFillProjectCreds(credentialPrompts, projectCredentials)
globalconfig.Save(projectCredentials)
projectParameters := promptAllModules(moduleConfigs)
projectParameters := promptAllModules(moduleConfigs, projectCredentials)

// Map parameter values back to specific modules
for moduleName, module := range moduleConfigs {
Expand Down Expand Up @@ -101,11 +102,12 @@ func loadAllModules(moduleSources []string) (map[string]moduleconfig.ModuleConfi
}

// promptAllModules takes a map of all the modules and prompts the user for values for all the parameters
func promptAllModules(modules map[string]moduleconfig.ModuleConfig) map[string]string {
func promptAllModules(modules map[string]moduleconfig.ModuleConfig, projectCredentials globalconfig.ProjectCredential) map[string]string {
parameterValues := make(map[string]string)
for _, config := range modules {
var err error
parameterValues, err = PromptModuleParams(config, parameterValues)

parameterValues, err = PromptModuleParams(config, parameterValues, projectCredentials)
if err != nil {
exit.Fatal("Exiting prompt: %v\n", err)
}
Expand Down Expand Up @@ -175,7 +177,7 @@ func getCredentialPrompts(projectCredentials globalconfig.ProjectCredential, mod
// map is to keep track of which vendor they belong to, to fill them back into the projectConfig
prompts := []CredentialPrompts{}
for _, vendor := range AvailableVendorOrders {
if itemInSlice(uniqueVendors, vendor) {
if util.ItemInSlice(uniqueVendors, vendor) {
vendorPrompts := CredentialPrompts{vendor, mapVendorToPrompts(projectCredentials, vendor)}
prompts = append(prompts, vendorPrompts)
}
Expand Down
23 changes: 11 additions & 12 deletions internal/init/prompts.go
Expand Up @@ -163,19 +163,27 @@ func sanitizeParameterValue(str string) string {
}

// PromptParams renders series of prompt UI based on the config
func PromptModuleParams(moduleConfig moduleconfig.ModuleConfig, parameters map[string]string) (map[string]string, error) {
func PromptModuleParams(moduleConfig moduleconfig.ModuleConfig, parameters map[string]string, projectCredentials globalconfig.ProjectCredential) (map[string]string, error) {

credentialEnvs := projectCredentials.SelectedVendorsCredentialsAsEnv(moduleConfig.RequiredCredentials)
for _, promptConfig := range moduleConfig.Parameters {
// deduplicate fields already prompted and received
if _, isAlreadySet := parameters[promptConfig.Field]; isAlreadySet {
continue
}

promptHandler := PromptHandler{
promptConfig,
NoCondition,
NoValidation,
}
result := promptHandler.GetParam(parameters)
// merging the context of param and credentals
// this treats credentialEnvs as throwaway, parameters is shared between modules
// so credentials should not be in parameters as it gets returned to parent
for k, v := range parameters {
credentialEnvs[k] = v
}
result := promptHandler.GetParam(credentialEnvs)

parameters[promptConfig.Field] = result
}
Expand Down Expand Up @@ -212,18 +220,9 @@ func promptCredentialsAndFillProjectCreds(credentialPrompts []CredentialPrompts,

func appendToSet(set []string, toAppend []string) []string {
for _, appendee := range toAppend {
if !itemInSlice(set, appendee) {
if !util.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
}
9 changes: 9 additions & 0 deletions internal/util/util.go
Expand Up @@ -133,3 +133,12 @@ func IndentString(content string, spaces int) string {
}
return result
}

func ItemInSlice(slice []string, target string) bool {
for _, item := range slice {
if item == target {
return true
}
}
return false
}
19 changes: 19 additions & 0 deletions tests/test_data/apply/project1/zero-module.yml
@@ -0,0 +1,19 @@
name: project1
description: 'project1'
author: 'Commit'

template:
strictMode: true
delimiters:
- "<%"
- "%>"
inputDir: '.'
outputDir: 'test'

requiredCredentials:
- aws
- github

parameters:
- field: foo
label: foo
19 changes: 19 additions & 0 deletions tests/test_data/apply/project2/zero-module.yml
@@ -0,0 +1,19 @@
name: project2
description: 'project2'
author: 'Commit'

template:
strictMode: true
delimiters:
- "<%"
- "%>"
inputDir: '.'
outputDir: 'test'

requiredCredentials:
- aws
- github

parameters:
- field: baz
label: baz

0 comments on commit 72db194

Please sign in to comment.