Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions internal/apply/apply.go
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind adding this to Apply as well?
Right now it's just loading all creds but I guess it would be nice to be more precise and only expose the necessary ones.
You'll need to load the module config to be able to get the required creds first though.

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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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