diff --git a/pkg/api/models.go b/pkg/api/models.go index ec09885f6..ea3a6f95a 100644 --- a/pkg/api/models.go +++ b/pkg/api/models.go @@ -251,6 +251,7 @@ type ConfigurationItem struct { Optional bool Condition *Condition Validation *Validation + Values []string } type Artifact struct { diff --git a/pkg/bundle/configuration.go b/pkg/bundle/configuration.go index 1e4825cff..3dda77dbb 100644 --- a/pkg/bundle/configuration.go +++ b/pkg/bundle/configuration.go @@ -199,6 +199,23 @@ func Configure(ctx map[string]interface{}, item *api.ConfigurationItem, context return err } ctx[item.Name] = contents + + case Enum: + var res string + if len(item.Values) == 0 { + return fmt.Errorf("no values defined for %s", item.Name) + } + if value := getEnvVar(repo, item.Name); value != "" { + res = value + } else { + prompt, opts := enumSurvey(def, item) + err = survey.AskOne(prompt, &res, opts...) + if err != nil { + return err + } + } + + ctx[item.Name] = res } return diff --git a/pkg/bundle/configuration_test.go b/pkg/bundle/configuration_test.go index 44ec1ea5a..7709a61db 100644 --- a/pkg/bundle/configuration_test.go +++ b/pkg/bundle/configuration_test.go @@ -26,6 +26,20 @@ func TestConfigureEnvVariables(t *testing.T) { expectedValue string envVars map[string]string }{ + { + name: "test enum item", + item: &api.ConfigurationItem{ + Name: "test_item", + Default: "", + Type: bundle.Enum, + Values: []string{"a", "b", "c"}, + }, + context: &manifest.Context{}, + ctx: map[string]interface{}{}, + repo: "test", + envVars: map[string]string{"PLURAL_TEST_TEST_ITEM": "a"}, + expectedValue: "a", + }, { name: "test integer item", item: &api.ConfigurationItem{ diff --git a/pkg/bundle/surveys.go b/pkg/bundle/surveys.go index 5c63604a5..9fed7b3e5 100644 --- a/pkg/bundle/surveys.go +++ b/pkg/bundle/surveys.go @@ -14,6 +14,8 @@ import ( "github.com/mitchellh/go-homedir" ) +const enterTheValue = "Enter the value" + func stringValidator(item *api.ConfigurationItem) survey.AskOpt { return survey.WithValidator(func(val interface{}) error { res, _ := val.(string) @@ -33,7 +35,7 @@ func stringSurvey(def string, item *api.ConfigurationItem) (survey.Prompt, []sur } return &survey.Input{ - Message: "Enter the value", + Message: enterTheValue, Default: def, }, opts } @@ -45,7 +47,7 @@ func passwordSurvey(item *api.ConfigurationItem) (survey.Prompt, []survey.AskOpt opts = append(opts, survey.WithValidator(survey.Required)) } - return &survey.Password{Message: "Enter the value"}, opts + return &survey.Password{Message: enterTheValue}, opts } func boolSurvey() (survey.Prompt, []survey.AskOpt) { @@ -54,7 +56,7 @@ func boolSurvey() (survey.Prompt, []survey.AskOpt) { func intSurvey(def string) (survey.Prompt, []survey.AskOpt) { return &survey.Input{ - Message: "Enter the value", + Message: enterTheValue, Default: def, }, []survey.AskOpt{survey.WithValidator(survey.Required)} } @@ -108,6 +110,19 @@ func fileSurvey(def string, item *api.ConfigurationItem) (prompt survey.Prompt, return } +func enumSurvey(def string, item *api.ConfigurationItem) (input survey.Prompt, opts []survey.AskOpt) { + opts = []survey.AskOpt{survey.WithValidator(survey.Required)} + if def == "" && len(item.Values) > 0 { + def = item.Values[0] + } + input = &survey.Select{ + Message: enterTheValue, + Default: def, + Options: item.Values, + } + return +} + func CleanPath(path string) string { if strings.HasSuffix(path, "/") { return path diff --git a/pkg/bundle/types.go b/pkg/bundle/types.go index 6f838efc5..40ae016d0 100644 --- a/pkg/bundle/types.go +++ b/pkg/bundle/types.go @@ -9,4 +9,5 @@ const ( File = "FILE" Function = "FUNCTION" Password = "PASSWORD" + Enum = "ENUM" ) diff --git a/pkg/pr/crd.go b/pkg/pr/crd.go index 0917cde17..f3e651860 100644 --- a/pkg/pr/crd.go +++ b/pkg/pr/crd.go @@ -117,6 +117,11 @@ func configuration(pr *v1alpha1.PrAutomation, contextFile string) (map[string]in } ci.Validation = validation } + if len(t.Values) > 0 { + ci.Values = algorithms.Map(t.Values, func(t *string) string { + return *t + }) + } return ci }) utils.Highlight("Lets' fill out the configuration for this PR automation:\n") diff --git a/pkg/pr/crd_test.go b/pkg/pr/crd_test.go new file mode 100644 index 000000000..e0bb29fb4 --- /dev/null +++ b/pkg/pr/crd_test.go @@ -0,0 +1,39 @@ +package pr_test + +import ( + "os" + "testing" + + "github.com/pluralsh/plural-cli/pkg/pr" + "github.com/stretchr/testify/assert" +) + +func TestBuildCRD(t *testing.T) { + tests := []struct { + name string + path string + envs map[string]string + expectedCtx map[string]interface{} + }{ + { + name: "test PR automation", + path: "../../test/prautomation/prautomations.yaml", + envs: map[string]string{"PLURAL__NAME": "test", "PLURAL__REGION": "eu-central-1", "PLURAL__TYPE": "s3"}, + expectedCtx: map[string]interface{}{ + "name": "test", "region": "eu-central-1", "type": "s3", + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + for k, v := range test.envs { + os.Setenv(k, v) + } + + prTemplate, err := pr.BuildCRD(test.path, "") + assert.NoError(t, err) + assert.Equal(t, test.expectedCtx, prTemplate.Context) + + }) + } +} diff --git a/test/prautomation/prautomations.yaml b/test/prautomation/prautomations.yaml new file mode 100644 index 000000000..02e73623c --- /dev/null +++ b/test/prautomation/prautomations.yaml @@ -0,0 +1,35 @@ +apiVersion: deployments.plural.sh/v1alpha1 +kind: PrAutomation +metadata: + name: blob-creator +spec: + name: blob-creator + documentation: | + Sets up a PR to provision a blobstore with a given type (eg s3) and region + creates: + templates: + - source: templates/blob/stack.yaml + destination: "services/blobstores/{{ context.type }}/{{ context.name }}.yaml" + external: false + - source: templates/blob/service.yaml + destination: "bootstrap/blobstores.yaml" + external: false + scmConnectionRef: + name: github + title: "Adding a {{ context.type }} bucket {{ context.name }}" + message: "Setup a stack to manage the {{ context.name }} {{ context.type }} bucket" + identifier: your-org/your-plural-up-repo + configuration: + - name: name + type: STRING + documentation: the name of this blob store (if using s3, this would become an s3 bucket name) + validation: + regex: "[a-z][a-z-0-9]+" + - name: type + type: ENUM + documentation: the type of blob storage to provision + values: + - s3 + - name: region + type: STRING + documentation: the region your blobstore will live in