Skip to content

Commit

Permalink
zero init features: dedupe/new prompts/config file
Browse files Browse the repository at this point in the history
  • Loading branch information
davidcheung committed Jun 1, 2020
1 parent 532413e commit fa2ad3e
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 12 deletions.
10 changes: 6 additions & 4 deletions configs/configs.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package configs

const (
TemplatesDir = "tmp/templates"
ZeroProjectYml = "zero-project.yml"
IgnoredPaths = "(?i)zero.module.yml|.git/"
TemplateExtn = ".tmpl"
TemplatesDir = "tmp/templates"
ZeroProjectYml = "zero-project.yml"
ZeroHomeDirectory = ".zero"
UserCredentials = "credentials.yml"
IgnoredPaths = "(?i)zero.module.yml|.git/"
TemplateExtn = ".tmpl"
)
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ require (
github.com/matryer/is v1.3.0 // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/spf13/cobra v0.0.6
github.com/stretchr/testify v1.5.1 // indirect
github.com/stretchr/testify v1.4.0
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
Expand Down
117 changes: 117 additions & 0 deletions internal/config/global_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package config

import (
"bytes"
"io/ioutil"
"log"
"os"
"os/user"
"path"

"github.com/commitdev/zero/configs"
yaml "gopkg.in/yaml.v2"
)

var GetCredentialsPath = getCredentialsPath

type Projects map[string]*Project

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

type AWSResourceConfig struct {
AccessKeyId string `yaml:"accessKeyId,omitempty"`
SecretAccessKey string `yaml:"secretAccessKey,omitempty"`
}
type GithubResourceConfig struct {
AccessToken string `yaml:"accessToken,omitempty"`
}
type CircleCiResourceConfig struct {
ApiKey string `yaml:"apiKey,omitempty"`
}

func (p *Projects) Unmarshal(data []byte) error {
err := yaml.NewDecoder(bytes.NewReader(data)).Decode(p)
if err != nil {
return err
}
for k, v := range *p {
v.ProjectName = k
}
return nil
}

func LoadUserCredentials() Projects {
data := ReadOrCreateUserCredentialsFile()

projects := Projects{}
err := projects.Unmarshal(data)

if err != nil {
log.Fatalf("Failed to parse configuration: %v", err)
}
return projects
}

func getCredentialsPath() string {
usr, err := user.Current()
if err != nil {
log.Fatalf("Failed to get user directory path: %v", err)
}

rootDir := path.Join(usr.HomeDir, configs.ZeroHomeDirectory)
os.MkdirAll(rootDir, os.ModePerm)
filePath := path.Join(rootDir, configs.UserCredentials)
return filePath
}

func ReadOrCreateUserCredentialsFile() []byte {
credPath := GetCredentialsPath()

_, fileStateErr := os.Stat(credPath)
if os.IsNotExist(fileStateErr) {
var file, fileStateErr = os.Create(credPath)
if fileStateErr != nil {
log.Fatalf("Failed to create config file: %v", fileStateErr)
}
defer file.Close()
}
data, err := ioutil.ReadFile(credPath)
if err != nil {
log.Fatalf("Failed to read credentials file: %v", err)
}
return data
}

func GetUserCredentials(targetProjectName string) *Project {
projects := LoadUserCredentials()

if val, ok := projects[targetProjectName]; ok {
return val
} else {
p := Project{
ProjectName: targetProjectName,
}
projects[targetProjectName] = &p
return &p
}
}

func (project *Project) Save() {
projects := LoadUserCredentials()
projects[project.ProjectName] = project
projects.Save()
}

func (projects *Projects) Save() {
credsPath := GetCredentialsPath()
content, _ := yaml.Marshal(&projects)
err := ioutil.WriteFile(credsPath, content, 0644)
if err != nil {
log.Panicf("failed to parse config: %v", err)
}
}
103 changes: 103 additions & 0 deletions internal/config/global_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package config_test

import (
"fmt"
"io/ioutil"
"log"
"os"
"path"
"testing"

"github.com/commitdev/zero/internal/config"
"github.com/stretchr/testify/assert"
)

const baseTestFixturesDir = "../../tests/test_data/configs/"

var testCredentialFile = func() (func() string, func()) {
tmpConfigPath := getTmpConfig()
mockFunc := func() string { return tmpConfigPath }
teardownFunc := func() { os.RemoveAll(tmpConfigPath) }
return mockFunc, teardownFunc
}

func getTmpConfig() string {
pathFrom := path.Join(baseTestFixturesDir, fmt.Sprintf("credentials%s.yml", ""))
pathTo := path.Join(baseTestFixturesDir, fmt.Sprintf("credentials%s.yml", "-tmp"))
copyFile(pathFrom, pathTo)
return pathTo
}

func copyFile(from string, to string) {
bytesRead, err := ioutil.ReadFile(from)
if err != nil {
log.Fatal(err)
}

err = ioutil.WriteFile(to, bytesRead, 0644)
if err != nil {
log.Fatal(err)
}
}
func TestReadOrCreateUserCredentialsFile(t *testing.T) {
config.GetCredentialsPath = func() string {
return path.Join("../../tests/test_data/", "configs/does-not-exist.yml")
}
credPath := config.GetCredentialsPath()
defer os.RemoveAll(credPath)

_, fileStateErr := os.Stat(credPath)
assert.True(t, os.IsNotExist(fileStateErr), "File should not exist")

config.ReadOrCreateUserCredentialsFile()
stats, err := os.Stat(credPath)
assert.False(t, os.IsNotExist(err), "File should be created")
assert.Equal(t, "does-not-exist.yml", stats.Name(), "Should create yml automatically")
}

func TestGetUserCredentials(t *testing.T) {
var teardownFn func()
config.GetCredentialsPath, teardownFn = testCredentialFile()
defer teardownFn()

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

// Reading from fixtures: tests/test_data/configs/credentials.yml
assert.Equal(t, "AKIAABCD", project.AWSResourceConfig.AccessKeyId)
assert.Equal(t, "ZXCV", project.AWSResourceConfig.SecretAccessKey)
assert.Equal(t, "0987", project.GithubResourceConfig.AccessToken)
assert.Equal(t, "SOME_API_KEY", project.CircleCiResourceConfig.ApiKey)
})

t.Run("Fixture file should support multiple projects", func(t *testing.T) {
projectName := "another-project"
project := config.GetUserCredentials(projectName)
assert.Equal(t, "654", project.GithubResourceConfig.AccessToken)
})

}

func TestEditUserCredentials(t *testing.T) {
var teardownFn func()
config.GetCredentialsPath, teardownFn = testCredentialFile()
defer teardownFn()

t.Run("Should create new project if not exist", func(t *testing.T) {
projectName := "test-project3"
project := config.GetUserCredentials(projectName)
project.AWSResourceConfig.AccessKeyId = "TEST_KEY_ID_1"
project.Save()
newKeyID := config.GetUserCredentials(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 := config.GetUserCredentials(projectName)
project.AWSResourceConfig.AccessKeyId = "EDITED_ACCESS_KEY_ID"
project.Save()
newKeyID := config.GetUserCredentials(projectName).AWSResourceConfig.AccessKeyId
assert.Equal(t, "EDITED_ACCESS_KEY_ID", newKeyID)
})
}
3 changes: 2 additions & 1 deletion internal/config/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ func TestInit(t *testing.T) {
t.Fatal(err)
}

config.Init(config.RootDir, projectName, nil)
projectConfig := config.ZeroProjectConfig{}
config.Init(config.RootDir, projectName, &projectConfig)

if _, err := os.Stat(path.Join(testDirPath, configs.ZeroProjectYml)); err != nil {
t.Fatal(err)
Expand Down
71 changes: 66 additions & 5 deletions internal/context/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,20 @@ func Init(projectName string, outDir string) *config.ZeroProjectConfig {

projectConfig := defaultProjConfig(projectName)
promptProjectName(projectName, &projectConfig)
promptPushRepoUpstream(&projectConfig)
promptGithubRootOrg(&projectConfig)
promptGithubPersonalToken(&projectConfig)
chooseStack(&projectConfig)

// TODO: load ~/.zero/config.yml (or credentials)
// TODO: prompt global credentials

// chooseCloudProvider(&projectConfig)
// fmt.Println(&projectConfig)
// s := project.GetSecrets(rootDir)
// fillProviderDetails(&projectConfig, s)
// fmt.Println(&projectConfig)

promptAllModules(&projectConfig)
// TODO: load ~/.zero/config.yml (or credentials)
// TODO: prompt global credentials

return &projectConfig
}
Expand All @@ -56,18 +58,77 @@ func promptAllModules(projectConfig *config.ZeroProjectConfig) {
err := mod.PromptParams(projectConfig.Context)
if err != nil {
log.Fatalf("Exiting prompt: %v\n", err)
panic(err)
}
}
}

// global configs
func promptPushRepoUpstream(projectConfig *config.ZeroProjectConfig) {
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 {
log.Fatalf("Exiting prompt: %v\n", err)
}
projectConfig.Context["ShouldPushRepoUpstream"] = providerResult
}

func promptGithubRootOrg(projectConfig *config.ZeroProjectConfig) {
providerPrompt := promptui.Prompt{
Label: "What's the root of the github org to create repositories in?",
Default: "github.com/",
AllowEdit: true,
}
providerResult, err := providerPrompt.Run()
if err != nil {
panic(err)
}
result := providerResult

projectConfig.Context["GithubRootOrg"] = result
}

func promptGithubPersonalToken(projectConfig *config.ZeroProjectConfig) {
defaultToken := ""

project := config.GetUserCredentials(projectConfig.Name)
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 {
panic(err)
}

projectConfig.Context["githubPersonalToken"] = result

// If its different from saved token, update it
if project.GithubResourceConfig.AccessToken != result {
project.GithubResourceConfig.AccessToken = result
project.Save()
}
}

func promptProjectName(projectName string, projectConfig *config.ZeroProjectConfig) {
providerPrompt := promptui.Prompt{
Label: "Project Name",
Default: projectName,
AllowEdit: false,
}
providerPrompt.Run()
providerResult, err := providerPrompt.Run()
if err != nil {
log.Fatalf("Prompt failed %v\n", err)
panic(err)
}
projectConfig.Name = providerResult
}

func chooseCloudProvider(projectConfig *config.ZeroProjectConfig) {
Expand Down
7 changes: 6 additions & 1 deletion internal/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,14 @@ func (m *TemplateModule) PromptParams(projectContext map[string]string) error {
if promptConfig.Label == "" {
label = promptConfig.Field
}

// deduplicate fields already prompted and received
if _, isAlreadySet := projectContext[promptConfig.Field]; isAlreadySet {
continue
}

var err error
var result string

if len(promptConfig.Options) > 0 {
prompt := promptui.Select{
Label: label,
Expand Down
11 changes: 11 additions & 0 deletions tests/test_data/configs/credentials.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
another-project:
github:
accessToken: "654"
my-project:
aws:
accessKeyId: AKIAABCD
secretAccessKey: ZXCV
github:
accessToken: "0987"
circleci:
apiKey: SOME_API_KEY

0 comments on commit fa2ad3e

Please sign in to comment.