From c89c0d58a3607b1324670a428ecb9d8d33c958f7 Mon Sep 17 00:00:00 2001 From: Mathieu Frenette Date: Sun, 7 Mar 2021 14:28:01 -0500 Subject: [PATCH] Add `confirm` step Signed-off-by: Mathieu Frenette --- README.md | 1 - examples/templates/hello-world/spec.yaml | 10 ++++-- src/internal/evaluation/evaluation_test.go | 2 +- src/internal/spec/spec.go | 25 ++++++++++++- src/internal/spec/spec_test.go | 20 +++++++++++ src/internal/steps/confirm.go | 41 ++++++++++++++++++++++ src/internal/steps/if.go | 2 +- src/internal/steps/input/input.go | 4 +++ 8 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 src/internal/steps/confirm.go diff --git a/README.md b/README.md index 34553df..60f60b3 100644 --- a/README.md +++ b/README.md @@ -474,7 +474,6 @@ To associate a template with an existing project that was not initially generate # Wishlist -- Add `confirm` step (similar to `if`, but `confirm` property contains message to display and `then` the steps to execute). - Allow `do` step to define multiple actions to call. - Add reusable modules (including both templates and scripts). - Add support for injecting snippets in specific sections of files in a second time (ie: adding multiple endpoints to an existing service). diff --git a/examples/templates/hello-world/spec.yaml b/examples/templates/hello-world/spec.yaml index df63edf..aecd315 100644 --- a/examples/templates/hello-world/spec.yaml +++ b/examples/templates/hello-world/spec.yaml @@ -78,6 +78,10 @@ actions: # By convention, the "uninstall" action is in charge of removing the project from infra uninstall: - # Here the "exec" step is invoked multiple times, each executing a single command - - exec: remove-docker-repo - - exec: remove-cicd-triggers \ No newline at end of file + # The "confirm" step is similar to "if", however it prompts user with given message and + # only upon confirmation executes steps in the "then" clause. + - confirm: Are you sure you want to completely uninstall project {{.PROJECT}} from infrastructure? + then: + # Here the "exec" step is invoked multiple times, each executing a single command + - exec: remove-docker-repo + - exec: remove-cicd-triggers diff --git a/src/internal/evaluation/evaluation_test.go b/src/internal/evaluation/evaluation_test.go index 2f1d3e8..60a887a 100644 --- a/src/internal/evaluation/evaluation_test.go +++ b/src/internal/evaluation/evaluation_test.go @@ -23,7 +23,7 @@ func (c context) GetPlaceholders() map[string]string { return c.placeholders } -func (c context) GetShellVars() []string { +func (c context) GetShellVars(includeProcessVars bool) []string { vars := make([]string, len(c.vars)) for key, value := range c.vars { entry := fmt.Sprintf("%s=%v", key, value) diff --git a/src/internal/spec/spec.go b/src/internal/spec/spec.go index a5187c2..809dc7b 100644 --- a/src/internal/spec/spec.go +++ b/src/internal/spec/spec.go @@ -106,13 +106,17 @@ func loadExecutables(list yaml.List, templateDir string) (exec.Executables, erro } func loadExecutable(node yaml.Node, templateDir string) (exec.Executable, error) { - // Special case for if step + // Special case for if and confirm steps _map, ok := node.(yaml.Map) if ok { _, ok = _map["if"] if ok { return loadIfStep(_map, templateDir) } + _, ok = _map["confirm"] + if ok { + return loadConfirmStep(_map, templateDir) + } } // Other steps @@ -186,6 +190,25 @@ func loadIfStep(_map yaml.Map, templateDir string) (exec.Executable, error) { }, nil } +func loadConfirmStep(_map yaml.Map, templateDir string) (exec.Executable, error) { + message, err := getRequiredStringFromMap(_map, "confirm") + if err != nil { + return nil, err + } + list, err := getRequiredList(_map, "then") + if err != nil { + return nil, err + } + executables, err := loadExecutables(list, templateDir) + if err != nil { + return nil, err + } + return steps.Confirm{ + Message: message, + Then: executables, + }, nil +} + func loadInputStep(_map yaml.Map, templateDir string) (exec.Executable, error) { question, err := getRequiredStringFromMap(_map, "question") if err != nil { diff --git a/src/internal/spec/spec_test.go b/src/internal/spec/spec_test.go index 8278799..f63f98e 100644 --- a/src/internal/spec/spec_test.go +++ b/src/internal/spec/spec_test.go @@ -73,6 +73,26 @@ then: }, }, }, + { + Name: "confirm", + Buffer: ` +confirm: Message +then: + - input: + question: Message + var: Variable + default: Default`, + Expected: steps.Confirm{ + Message: "Message", + Then: exec.Executables{ + input.Prompt{ + Message: "Message", + Var: "Variable", + Default: "Default", + }, + }, + }, + }, { Name: "input prompt", Buffer: ` diff --git a/src/internal/steps/confirm.go b/src/internal/steps/confirm.go new file mode 100644 index 0000000..47291bb --- /dev/null +++ b/src/internal/steps/confirm.go @@ -0,0 +1,41 @@ +package steps + +import ( + "github.com/AlecAivazis/survey/v2" + "github.com/Samasource/jen/src/internal/evaluation" + "github.com/Samasource/jen/src/internal/exec" + logging "github.com/Samasource/jen/src/internal/logging" +) + +// Confirm represents a conditional step that executes its child executable only if +// user answers Yes when prompted for given message +type Confirm struct { + Message string + Then exec.Executables +} + +func (c Confirm) String() string { + return "confirm" +} + +// Execute executes a child action only if user answers Yes when prompted for given message +func (c Confirm) Execute(context exec.Context) error { + message, err := evaluation.EvalTemplate(context, c.Message) + if err != nil { + return err + } + prompt := &survey.Confirm{ + Message: message, + Default: false, + } + value := false + if err := survey.AskOne(prompt, &value); err != nil { + return err + } + if !value { + logging.Log("Skipping sub-steps because user cancelled") + return nil + } + logging.Log("Executing sub-steps because user confirmed") + return c.Then.Execute(context) +} diff --git a/src/internal/steps/if.go b/src/internal/steps/if.go index e80117a..0de6791 100644 --- a/src/internal/steps/if.go +++ b/src/internal/steps/if.go @@ -16,7 +16,7 @@ type If struct { } func (i If) String() string { - return "do" + return "if" } // Execute executes a child action only when a given condition evaluates to true diff --git a/src/internal/steps/input/input.go b/src/internal/steps/input/input.go index 4319ada..02f7cb4 100644 --- a/src/internal/steps/input/input.go +++ b/src/internal/steps/input/input.go @@ -14,6 +14,10 @@ type Prompt struct { Default string } +func (p Prompt) String() string { + return "input" +} + // Execute prompts user for input value func (p Prompt) Execute(context exec.Context) error { if context.IsVarOverriden(p.Var) {