From 9fd73bc5c511a839b01a6e6a2fbec02dae1938b8 Mon Sep 17 00:00:00 2001 From: Jason Young Date: Wed, 29 May 2024 10:54:22 -0700 Subject: [PATCH] Add `--disable-dependency-prompt` flag (#181) Co-authored-by: Denis O --- README.md | 41 ++++++++++++++++------------ cli/boilerplate_cli.go | 4 +++ options/options.go | 35 +++++++++++++----------- templates/template_processor.go | 38 ++++++++++++++------------ templates/template_processor_test.go | 9 ++++-- 5 files changed, 73 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index ebd71dae..313e2492 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,8 @@ The `boilerplate` binary supports the following options: folder without any variables). Default: `exit`. * `--disable-hooks`: If this flag is set, no hooks will execute. * `--disable-shell`: If this flag is set, no `shell` helpers will execute. They will instead return the text "replace-me". +* `--disable-dependency-prompt` (optional): Do not prompt for confirmation to include dependencies. Has the same effect as + --non-interactive, without disabling variable prompts. Default: `false`. * `--help`: Show the help text and exit. * `--version`: Show the version and exit. @@ -367,11 +369,11 @@ executing the current one. Each dependency may contain the following keys: - Defaults set on root variables. - Defaults set within the dependency boilerplate config. * `for_each`: If you set this to a list of values, `boilerplate` will loop over each value, and render this dependency - once for each value. This allows you to dynamically render a dependency multiple times based on user input. The + once for each value. This allows you to dynamically render a dependency multiple times based on user input. The current value in the loop will be available as the variable `__each__`, available to both your Go templating and - in other `dependencies` params: e.g., you could reference `{{ .__each__ }}` in `output-folder` to render to each + in other `dependencies` params: e.g., you could reference `{{ .__each__ }}` in `output-folder` to render to each iteration to a different folder. -* `for_each_reference`: The name of another variable whose value should be used as the `for_each` value. +* `for_each_reference`: The name of another variable whose value should be used as the `for_each` value. See the [Dependencies](#dependencies) section for more info. @@ -506,7 +508,7 @@ Note the following: # Skip this dependency if both .Foo and .Bar are set to true skip: "{{ and .Foo .Bar }}" ``` -* Looping over dependencies: You can render a dependency multiple times, dynamically, based on user input, via the +* Looping over dependencies: You can render a dependency multiple times, dynamically, based on user input, via the `for_each` or `for_each_reference` parameter. Example: ```yaml @@ -518,7 +520,7 @@ Note the following: - dev - stage - prod - + dependencies: - name: loop-dependency-example template-url: ../terraform @@ -533,6 +535,9 @@ Note the following: # Use the environment name in the server name default: "example-{{ .__each__ }}" ``` +* Confirming dependency includes: By default in interactive mode, the user must confirm to include each dependency. + If `--non-interactive` is set, dependencies will be included automatically, so long as `skip` is false. If you'd + like to keep interactive mode enabled, but disable dependency confirmation, set `--disable-dependency-prompt`. #### Hooks @@ -572,7 +577,7 @@ Note the following: ```yaml after: - command: some-command - dir: "{{ outputFolder }}/foo/bar" + dir: "{{ outputFolder }}/foo/bar" ``` * `skip` (Optional): Skip this hook if this condition, which can use Go templating syntax and boilerplate variables, evaluates to the string `true`. This is useful to conditionally enable or disable @@ -658,21 +663,21 @@ you were using `boilerplate` to generate a Java project, your template folder co #### Validations -Boilerplate allows you to specify a set of validations when defining a variable. When a user is prompted for a variable that has -validations defined, their input must pass all defined validations. If the user's input does not pass all validations, they'll be +Boilerplate allows you to specify a set of validations when defining a variable. When a user is prompted for a variable that has +validations defined, their input must pass all defined validations. If the user's input does not pass all validations, they'll be presented with real-time feedback on exactly which rules their submission is failing. Once a user's submission passes all defined validations, Boilerplate will accept their submitted value. -Here's an example prompt for a variable with validations that shows how invalid submissions are handled: +Here's an example prompt for a variable with validations that shows how invalid submissions are handled: ![Example Boilerplate real-time validation](./docs/bp-validation.png) -Here's an example demonstating how to specify validations when defining your variables: +Here's an example demonstating how to specify validations when defining your variables: ```yaml variables: - name: CompanyName - description: Enter the name of your organization. + description: Enter the name of your organization. default: "" type: string validations: @@ -680,14 +685,14 @@ variables: - length-5-22 - alphanumeric ``` -This `boilerplate.yml` snippet defines a variable, `CompanyName` which: +This `boilerplate.yml` snippet defines a variable, `CompanyName` which: * Must be supplied by the user. No empty or nil values will be accepted. * Must have a length between 5 and 22 characters * Must contain only alphanumeric characters (no special characters) **Currently supported validations** -Boilerplate uses the [`go-ozzo/ozzo-validation` library](https://github.com/go-ozzo/ozzo-validation). The following validations are currently supported: +Boilerplate uses the [`go-ozzo/ozzo-validation` library](https://github.com/go-ozzo/ozzo-validation). The following validations are currently supported: - "required" - field cannot be empty - "length-{min-int}-{max-int}" - field must be between ${min-int} and ${max-int} characters in length @@ -701,18 +706,18 @@ Boilerplate uses the [`go-ozzo/ozzo-validation` library](https://github.com/go-o #### Variable Ordering Boilerplate allows you to define the relative order in which a set of variables should be presented to the user when prompting -for human input. +for human input. -Here's an example demonstrating how to define the relative order of a set of variables: +Here's an example demonstrating how to define the relative order of a set of variables: ```yaml -variables: +variables: - name: WebsiteURL order: 0 description: Enter the URL to your homepage - - name: ImagePath: + - name: ImagePath: order: 1 - description: Enter the full filepath to your logo image + description: Enter the full filepath to your logo image - name: ProfileName order: 2 description: Enter the display name for your user diff --git a/cli/boilerplate_cli.go b/cli/boilerplate_cli.go index 837c4b40..5d520da7 100644 --- a/cli/boilerplate_cli.go +++ b/cli/boilerplate_cli.go @@ -92,6 +92,10 @@ func CreateBoilerplateCli() *cli.App { Name: options.OptDisableShell, Usage: "If this flag is set, no shell helpers will execute. They will instead return the text 'replace-me'.", }, + &cli.BoolFlag{ + Name: options.OptDisableDependencyPrompt, + Usage: fmt.Sprintf("Do not prompt for confirmation to include dependencies. Has the same effect as --%s, without disabling variable prompts.", options.OptNonInteractive), + }, } // We pass JSON/YAML content to various CLI flags, such as --var, and this JSON/YAML content may contain commas or diff --git a/options/options.go b/options/options.go index 1fc3ddd9..12564d66 100644 --- a/options/options.go +++ b/options/options.go @@ -19,6 +19,7 @@ const OptMissingKeyAction = "missing-key-action" const OptMissingConfigAction = "missing-config-action" const OptDisableHooks = "disable-hooks" const OptDisableShell = "disable-shell" +const OptDisableDependencyPrompt = "disable-dependency-prompt" // The command-line options for the boilerplate app type BoilerplateOptions struct { @@ -27,13 +28,14 @@ type BoilerplateOptions struct { // Working directory where the go-getter defined template is downloaded. TemplateFolder string - OutputFolder string - NonInteractive bool - Vars map[string]interface{} - OnMissingKey MissingKeyAction - OnMissingConfig MissingConfigAction - DisableHooks bool - DisableShell bool + OutputFolder string + NonInteractive bool + Vars map[string]interface{} + OnMissingKey MissingKeyAction + OnMissingConfig MissingConfigAction + DisableHooks bool + DisableShell bool + DisableDependencyPrompt bool } // Validate that the options have reasonable values and return an error if they don't @@ -84,15 +86,16 @@ func ParseOptions(cliContext *cli.Context) (*BoilerplateOptions, error) { } options := &BoilerplateOptions{ - TemplateUrl: templateUrl, - TemplateFolder: templateFolder, - OutputFolder: cliContext.String(OptOutputFolder), - NonInteractive: cliContext.Bool(OptNonInteractive), - OnMissingKey: missingKeyAction, - OnMissingConfig: missingConfigAction, - Vars: vars, - DisableHooks: cliContext.Bool(OptDisableHooks), - DisableShell: cliContext.Bool(OptDisableShell), + TemplateUrl: templateUrl, + TemplateFolder: templateFolder, + OutputFolder: cliContext.String(OptOutputFolder), + NonInteractive: cliContext.Bool(OptNonInteractive), + OnMissingKey: missingKeyAction, + OnMissingConfig: missingConfigAction, + Vars: vars, + DisableHooks: cliContext.Bool(OptDisableHooks), + DisableShell: cliContext.Bool(OptDisableShell), + DisableDependencyPrompt: cliContext.Bool(OptDisableDependencyPrompt), } if err := options.Validate(); err != nil { diff --git a/templates/template_processor.go b/templates/template_processor.go index 6dfc21c1..e45dccb3 100644 --- a/templates/template_processor.go +++ b/templates/template_processor.go @@ -305,15 +305,16 @@ func cloneOptionsForDependency( } return &options.BoilerplateOptions{ - TemplateUrl: templateUrl, - TemplateFolder: templateFolder, - OutputFolder: outputFolder, - NonInteractive: originalOpts.NonInteractive, - Vars: vars, - OnMissingKey: originalOpts.OnMissingKey, - OnMissingConfig: originalOpts.OnMissingConfig, - DisableHooks: originalOpts.DisableHooks, - DisableShell: originalOpts.DisableShell, + TemplateUrl: templateUrl, + TemplateFolder: templateFolder, + OutputFolder: outputFolder, + NonInteractive: originalOpts.NonInteractive, + Vars: vars, + OnMissingKey: originalOpts.OnMissingKey, + OnMissingConfig: originalOpts.OnMissingConfig, + DisableHooks: originalOpts.DisableHooks, + DisableShell: originalOpts.DisableShell, + DisableDependencyPrompt: originalOpts.DisableDependencyPrompt, }, nil } @@ -338,13 +339,14 @@ func cloneVariablesForDependency( NonInteractive: true, OnMissingKey: options.ExitWithError, - TemplateUrl: opts.TemplateUrl, - TemplateFolder: opts.TemplateFolder, - OutputFolder: opts.OutputFolder, - Vars: opts.Vars, - OnMissingConfig: opts.OnMissingConfig, - DisableHooks: opts.DisableHooks, - DisableShell: opts.DisableShell, + TemplateUrl: opts.TemplateUrl, + TemplateFolder: opts.TemplateFolder, + OutputFolder: opts.OutputFolder, + Vars: opts.Vars, + OnMissingConfig: opts.OnMissingConfig, + DisableHooks: opts.DisableHooks, + DisableShell: opts.DisableShell, + DisableDependencyPrompt: opts.DisableDependencyPrompt, } // Start with the original variables. Note that it doesn't matter that originalVariables contains both CLI and @@ -414,7 +416,7 @@ func cloneVariablesForDependency( } // Prompt the user to verify if the given dependency should be executed and return true if they confirm. If -// options.NonInteractive is set to true, this function always returns true. +// options.NonInteractive or options.DisableDependencyPrompt are set to true, this function always returns true. func shouldProcessDependency(dependency variables.Dependency, opts *options.BoilerplateOptions, variables map[string]interface{}) (bool, error) { shouldSkip, err := shouldSkipDependency(dependency, opts, variables) if err != nil { @@ -424,7 +426,7 @@ func shouldProcessDependency(dependency variables.Dependency, opts *options.Boil return false, nil } - if opts.NonInteractive { + if opts.NonInteractive || opts.DisableDependencyPrompt { return true, nil } diff --git a/templates/template_processor_test.go b/templates/template_processor_test.go index bdb35189..1534afba 100644 --- a/templates/template_processor_test.go +++ b/templates/template_processor_test.go @@ -5,11 +5,10 @@ import ( "path/filepath" "testing" - "github.com/stretchr/testify/require" - "github.com/gruntwork-io/boilerplate/options" "github.com/gruntwork-io/boilerplate/variables" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestOutPath(t *testing.T) { @@ -62,6 +61,12 @@ func TestCloneOptionsForDependency(t *testing.T) { map[string]interface{}{}, options.BoilerplateOptions{TemplateUrl: "../dep1", TemplateFolder: filepath.FromSlash("/template/dep1"), OutputFolder: filepath.FromSlash("/output/out1"), NonInteractive: true, Vars: map[string]interface{}{}, OnMissingKey: options.ExitWithError}, }, + { + variables.Dependency{Name: "dep1", TemplateUrl: "../dep1", OutputFolder: "../out1"}, + options.BoilerplateOptions{TemplateFolder: "/template/path/", OutputFolder: "/output/path/", DisableDependencyPrompt: true, Vars: map[string]interface{}{}, OnMissingKey: options.ExitWithError}, + map[string]interface{}{}, + options.BoilerplateOptions{TemplateUrl: "../dep1", TemplateFolder: filepath.FromSlash("/template/dep1"), OutputFolder: filepath.FromSlash("/output/out1"), DisableDependencyPrompt: true, Vars: map[string]interface{}{}, OnMissingKey: options.ExitWithError}, + }, { variables.Dependency{Name: "dep1", TemplateUrl: "../dep1", OutputFolder: "../out1"}, options.BoilerplateOptions{TemplateFolder: "/template/path/", OutputFolder: "/output/path/", NonInteractive: false, Vars: map[string]interface{}{"foo": "bar"}, OnMissingKey: options.Invalid},