diff --git a/README.md b/README.md index a20c7fb..6678ae4 100644 --- a/README.md +++ b/README.md @@ -100,4 +100,18 @@ Once this is done, we copy all the files from the scaffold directory into the ta If there is a `.lagoon/post-message.txt` file, this is shown to the user. -Finally, the temporary directory with the scaffolding is removed. \ No newline at end of file +Finally, the temporary directory with the scaffolding is removed. + +## Values from exiting files + +When doing something like importing from an existing project, we might sometimes need to read values from existing files. + +This is achieved using the `valueFiles` section in the flow file. +We support `.env`, `.json`, and `.yml` files for the moment. + +If you list a particular file name in the `valueFiles` section, we will attempt to read the file and parse it as the appropriate type. +These values will then be available to the template files when they're hydrated. + +### example + +``` \ No newline at end of file diff --git a/go.mod b/go.mod index 20a6e1c..2b479bd 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/joho/godotenv v1.5.1 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index 3c9361d..265a806 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= diff --git a/internal/assets/comprehension_tests/.env b/internal/assets/comprehension_tests/.env new file mode 100644 index 0000000..bcc0623 --- /dev/null +++ b/internal/assets/comprehension_tests/.env @@ -0,0 +1,5 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost \ No newline at end of file diff --git a/internal/assets/comprehension_tests/comprehension_test.json b/internal/assets/comprehension_tests/comprehension_test.json new file mode 100644 index 0000000..97021db --- /dev/null +++ b/internal/assets/comprehension_tests/comprehension_test.json @@ -0,0 +1,10 @@ +{ + "name": "laravel/laravel", + "type": "project", + "keywords": ["framework", "testing"], + "require": { + "php": "^8.1" + }, + "minimum-stability": "stable", + "prefer-stable": true +} \ No newline at end of file diff --git a/internal/assets/comprehension_tests/comprehension_test.yml b/internal/assets/comprehension_tests/comprehension_test.yml new file mode 100644 index 0000000..c247dd9 --- /dev/null +++ b/internal/assets/comprehension_tests/comprehension_test.yml @@ -0,0 +1,10 @@ +docker-compose-yaml: docker-compose.yml +project: "lagoon-sync" +lagoon-sync: + ssh: + host: "example.ssh" + port: "22" + verbose: true + mariadb: + config: + hostname: "${MARIADB_HOST:-mariadb}" \ No newline at end of file diff --git a/internal/valgen.go b/internal/valgen.go index 7c0b1f9..099aee9 100644 --- a/internal/valgen.go +++ b/internal/valgen.go @@ -1,10 +1,15 @@ package internal import ( + "encoding/json" "errors" "fmt" "github.com/AlecAivazis/survey/v2" + "github.com/joho/godotenv" "gopkg.in/yaml.v2" + "log" + "os" + "path/filepath" ) type surveyQuestion struct { @@ -18,8 +23,20 @@ type surveyQuestion struct { Questions []surveyQuestion `yaml:"questions,omitempty"` } +type valueFileValue struct { + Name string `yaml:"name"` + Path string `yaml:"path"` //this is going to depend on the type - but let's assume it's grandparent.parent.child + Default string `yaml:"default"` // The default if the value can't be found +} + +type valueFile struct { + Name string `yaml:"name"` + Values []valueFileValue `yaml:"values"` +} + type surveyQuestionsFile struct { - Questions []surveyQuestion `yaml:"questions"` + Questions []surveyQuestion `yaml:"questions"` + ValueFiles []valueFile `yaml:"valueFiles"` // This is a list of files we can potentially source values from } func UnmarshallSurveyQuestions(incoming []byte) ([]surveyQuestion, error) { @@ -32,6 +49,58 @@ func UnmarshallSurveyQuestions(incoming []byte) ([]surveyQuestion, error) { return incomingMap.Questions, nil } +func loadValuesFromValuesFiles(files []string) (map[string]interface{}, error) { + vals := make(map[string]interface{}) + for _, file := range files { + f, err := os.Stat(file) + if err != nil { + //this might be okay, just log it + log.Default().Printf("Unable to find file `%v`", file) + } + extension := filepath.Ext(f.Name()) + switch extension { + case ".env": + myEnv, err := godotenv.Read(file) + if err != nil { + log.Default().Printf("Unable to read env file `%v`", file) + continue + } + vals[f.Name()] = myEnv + case ".json": + fileData, err := os.ReadFile(file) + if err != nil { + log.Default().Printf("Unable to read json file `%v`", file) + continue + } + var v interface{} + err = json.Unmarshal(fileData, &v) + if err != nil { + log.Default().Printf("Unable to unmarshal json file `%v`", file) + continue + } + vals[f.Name()] = v + case ".yml": + fallthrough + case ".yaml": + fileData, err := os.ReadFile(file) + if err != nil { + log.Default().Printf("Unable to read yaml file `%v`", file) + continue + } + var v interface{} + err = yaml.Unmarshal(fileData, &v) + if err != nil { + log.Default().Printf("Unable to unmarshal json file `%v`", file) + continue + } + vals[f.Name()] = v + default: + return nil, errors.New(fmt.Sprintf("Unsupported file comprehension for `%v`", file)) + } + } + return vals, nil +} + func RunFromSurveyQuestions(questions []surveyQuestion, interactive bool) (interface{}, error) { vals := make(map[string]interface{}) for _, question := range questions { diff --git a/internal/valgen_test.go b/internal/valgen_test.go index 206c7aa..d5224bb 100644 --- a/internal/valgen_test.go +++ b/internal/valgen_test.go @@ -205,3 +205,88 @@ func Test_runFromSurveyQuestions(t *testing.T) { }) } } + +func Test_loadValuesFromValuesFiles(t *testing.T) { + type args struct { + files []string + } + tests := []struct { + name string + args args + want map[string]interface{} + wantErr bool + }{ + { + name: "Test env file", + args: args{ + files: []string{"assets/comprehension_tests/.env"}, + }, + want: map[string]interface{}{ + ".env": map[string]string{ + "APP_NAME": "Laravel", + "APP_ENV": "local", + "APP_KEY": "", + "APP_DEBUG": "true", + "APP_URL": "http://localhost", + }, + }, + wantErr: false, + }, + { + name: "Test json file", + args: args{ + files: []string{"assets/comprehension_tests/comprehension_test.json"}, + }, + want: map[string]interface{}{ + "comprehension_test.json": map[string]interface{}{ + "name": "laravel/laravel", + "type": "project", + "keywords": []interface{}{"framework", "testing"}, + "require": map[string]interface{}{ + "php": "^8.1", + }, + "minimum-stability": "stable", + "prefer-stable": true, + }, + }, + wantErr: false, + }, + { + name: "Test yaml file", + args: args{ + files: []string{"assets/comprehension_tests/comprehension_test.yml"}, + }, + want: map[string]interface{}{ + "comprehension_test.yml": map[interface{}]interface{}{ + "docker-compose-yaml": "docker-compose.yml", + "project": "lagoon-sync", + "lagoon-sync": map[interface{}]interface{}{ + "ssh": map[interface{}]interface{}{ + "host": "example.ssh", + "port": "22", + "verbose": true, + }, + "mariadb": map[interface{}]interface{}{ + "config": map[interface{}]interface{}{ + "hostname": "${MARIADB_HOST:-mariadb}", + }, + }, + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := loadValuesFromValuesFiles(tt.args.files) + if (err != nil) != tt.wantErr { + t.Errorf("loadValuesFromValuesFiles() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("loadValuesFromValuesFiles() got = %v, want %v", got, tt.want) + } + }) + } +}