From 4e9dc3e0b5b40dce8660d0161e1b0766242f077f Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Thu, 7 Nov 2024 20:16:24 +0000 Subject: [PATCH 1/7] add feature for failed commands workflow --- internal/exec/help.go | 13 ++++++++ internal/exec/workflow_utils.go | 27 ++++++++++++---- .../core-concepts/workflows/workflows.mdx | 32 +++++++++++++++++++ 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/internal/exec/help.go b/internal/exec/help.go index e28c8a98c..126f8bc7c 100644 --- a/internal/exec/help.go +++ b/internal/exec/help.go @@ -103,6 +103,19 @@ func processHelp( "If the workspace does not exist, the command creates it by executing the 'terraform workspace new' command.\n\n" + "Usage: atmos terraform workspace -s \n\n" + "For more details refer to https://atmos.tools/cli/commands/terraform/workspace\n") + } else if componentType == "workflow" && command == "" { + u.PrintMessage("\nAtmos workflow commands support failure handling and resume functionality:\n\n" + + "When a workflow step fails:\n" + + " - The failed step name and command will be displayed\n" + + " - A resume command will be provided to restart from the failed step\n\n" + + "Example:\n" + + "Step 'deploy-vpc' failed!\n" + + "Error: Error applying plan:\n" + + "1 error occurred: AWS API call failed\n\n" + + "Failed command: terraform apply vpc -auto-approve\n\n" + + "To resume the workflow from this step, run:\n" + + "atmos workflow deploy-infra -f workflow1 --from-step deploy-vpc\n\n" + + "For more details refer to https://atmos.tools/cli/commands/workflow/resume\n") } else { u.PrintMessage(fmt.Sprintf("\nAtmos supports native '%s' commands with all the options, arguments and flags.\n", componentType)) u.PrintMessage("In addition, 'component' and 'stack' are required in order to generate variables for the component in the stack.\n") diff --git a/internal/exec/workflow_utils.go b/internal/exec/workflow_utils.go index 08fed48cd..fddacdf6b 100644 --- a/internal/exec/workflow_utils.go +++ b/internal/exec/workflow_utils.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path" + "path/filepath" "sort" "strings" @@ -70,11 +71,10 @@ func ExecuteWorkflow( commandType = "atmos" } + var err error if commandType == "shell" { commandName := fmt.Sprintf("%s-step-%d", workflow, stepIdx) - if err := ExecuteShell(cliConfig, command, commandName, ".", []string{}, dryRun); err != nil { - return err - } + err = ExecuteShell(cliConfig, command, commandName, ".", []string{}, dryRun) } else if commandType == "atmos" { args := strings.Fields(command) @@ -101,12 +101,27 @@ func ExecuteWorkflow( logFunc(cliConfig, fmt.Sprintf("Stack: %s", finalStack)) } - if err := ExecuteShellCommand(cliConfig, "atmos", args, ".", []string{}, dryRun, ""); err != nil { - return err - } + err = ExecuteShellCommand(cliConfig, "atmos", args, ".", []string{}, dryRun, "") } else { return fmt.Errorf("invalid workflow step type '%s'. Supported types are 'atmos' and 'shell'", commandType) } + + if err != nil { + workflowFileName := filepath.Base(workflowPath) + workflowFileName = strings.TrimSuffix(workflowFileName, filepath.Ext(workflowFileName)) + + failedMsg := color.New(color.FgRed).Sprintf("\nStep '%s' failed!", step.Name) + cmdMsg := color.New(color.FgYellow).Sprintf("Failed command: %s", command) + resumeMsg := color.New(color.FgGreen).Sprintf( + "\nTo resume the workflow from this step, run:\ncd %s && atmos workflow %s -f %s --from-step %s", + filepath.Dir(workflowPath), + workflow, + workflowFileName, + step.Name, + ) + + return fmt.Errorf("%s\n%s\n%s\nError: %v", failedMsg, cmdMsg, resumeMsg, err) + } } return nil diff --git a/website/docs/core-concepts/workflows/workflows.mdx b/website/docs/core-concepts/workflows/workflows.mdx index c419c6555..90b1391a5 100644 --- a/website/docs/core-concepts/workflows/workflows.mdx +++ b/website/docs/core-concepts/workflows/workflows.mdx @@ -371,6 +371,38 @@ The Atmos stack used by the workflow commands of type `atmos` can be specified i atmos workflow my-workflow -f workflow1 -s tenant1-ue2-dev ``` +## Workflow Failure Handling and Resume + +When a workflow step fails, Atmos will: +1. Display which step failed +2. Show the exact command that failed +3. Provide a ready-to-use command to resume the workflow from the failed step + +Given this workflow: + +```yaml +workflows: + test-1: + description: "Deploy test components" + steps: + - command: terraform plan test/test-component-override-1 + name: step-1 + - command: terraform plan test/test-component-override-2 + name: step-2 + - command: terraform plan test/test-component-override-3 + name: step-3 +``` + +If step-2 fails, you'll see: + +```console +Step 'step-2' failed! +Failed command: terraform plan test/test-component-override-2 + +To resume the workflow from this step, run: +atmos workflow test-1 -f workflow1 --from-step step-2 +``` + ### Stack Precedence The stack defined inline in the command itself has the lowest priority, it can and will be overridden by any other stack definition. From 0885bf3a0816eadcf7d9e0d54123a9b028f02b14 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 8 Nov 2024 19:41:56 +0000 Subject: [PATCH 2/7] chore: clean code cmd msg --- internal/exec/workflow_utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/exec/workflow_utils.go b/internal/exec/workflow_utils.go index fddacdf6b..1d3817e9b 100644 --- a/internal/exec/workflow_utils.go +++ b/internal/exec/workflow_utils.go @@ -111,7 +111,7 @@ func ExecuteWorkflow( workflowFileName = strings.TrimSuffix(workflowFileName, filepath.Ext(workflowFileName)) failedMsg := color.New(color.FgRed).Sprintf("\nStep '%s' failed!", step.Name) - cmdMsg := color.New(color.FgYellow).Sprintf("Failed command: %s", command) + cmdMsg := color.New(color.FgYellow).Sprintf("\nCommand failed:\n%s", command) resumeMsg := color.New(color.FgGreen).Sprintf( "\nTo resume the workflow from this step, run:\ncd %s && atmos workflow %s -f %s --from-step %s", filepath.Dir(workflowPath), From 65f6c95a808e22dda281e71159a08caf3913ae8d Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 8 Nov 2024 20:03:42 +0000 Subject: [PATCH 3/7] fix: general fixes for workflow cmd output --- internal/exec/help.go | 4 ++-- internal/exec/workflow_utils.go | 14 +++++++++++--- .../docs/core-concepts/workflows/workflows.mdx | 18 ++++++++++-------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/internal/exec/help.go b/internal/exec/help.go index 126f8bc7c..40095aad2 100644 --- a/internal/exec/help.go +++ b/internal/exec/help.go @@ -112,10 +112,10 @@ func processHelp( "Step 'deploy-vpc' failed!\n" + "Error: Error applying plan:\n" + "1 error occurred: AWS API call failed\n\n" + - "Failed command: terraform apply vpc -auto-approve\n\n" + + "Command failed: terraform apply vpc -auto-approve\n\n" + "To resume the workflow from this step, run:\n" + "atmos workflow deploy-infra -f workflow1 --from-step deploy-vpc\n\n" + - "For more details refer to https://atmos.tools/cli/commands/workflow/resume\n") + "For more details refer to https://atmos.tools/cli/commands/workflow/\n") } else { u.PrintMessage(fmt.Sprintf("\nAtmos supports native '%s' commands with all the options, arguments and flags.\n", componentType)) u.PrintMessage("In addition, 'component' and 'stack' are required in order to generate variables for the component in the stack.\n") diff --git a/internal/exec/workflow_utils.go b/internal/exec/workflow_utils.go index 1d3817e9b..c79dcf298 100644 --- a/internal/exec/workflow_utils.go +++ b/internal/exec/workflow_utils.go @@ -111,14 +111,22 @@ func ExecuteWorkflow( workflowFileName = strings.TrimSuffix(workflowFileName, filepath.Ext(workflowFileName)) failedMsg := color.New(color.FgRed).Sprintf("\nStep '%s' failed!", step.Name) - cmdMsg := color.New(color.FgYellow).Sprintf("\nCommand failed:\n%s", command) - resumeMsg := color.New(color.FgGreen).Sprintf( - "\nTo resume the workflow from this step, run:\ncd %s && atmos workflow %s -f %s --from-step %s", + cmdMsg := color.New(color.FgYellow).Sprintf("\nFailed command: %s", command) + + resumeWithCd := fmt.Sprintf("cd %s && atmos workflow %s -f %s --from-step %s", filepath.Dir(workflowPath), workflow, workflowFileName, step.Name, ) + u.LogDebug(cliConfig, fmt.Sprintf("\nFull resume command: %s", resumeWithCd)) + + resumeMsg := color.New(color.FgGreen).Sprintf( + "\nTo resume the workflow from this step, run:\natmos workflow %s -f %s --from-step %s", + workflow, + workflowFileName, + step.Name, + ) return fmt.Errorf("%s\n%s\n%s\nError: %v", failedMsg, cmdMsg, resumeMsg, err) } diff --git a/website/docs/core-concepts/workflows/workflows.mdx b/website/docs/core-concepts/workflows/workflows.mdx index 90b1391a5..f13a6b2e7 100644 --- a/website/docs/core-concepts/workflows/workflows.mdx +++ b/website/docs/core-concepts/workflows/workflows.mdx @@ -380,16 +380,16 @@ When a workflow step fails, Atmos will: Given this workflow: -```yaml +```yaml title="stacks/workflows/networking.yaml" workflows: - test-1: - description: "Deploy test components" + provision-vpcs: + description: "Deploy vpc components" steps: - - command: terraform plan test/test-component-override-1 + - command: terraform plan vpc -s plat-ue2-dev name: step-1 - - command: terraform plan test/test-component-override-2 + - command: terraform plan vpc -s plat-ue2-staging name: step-2 - - command: terraform plan test/test-component-override-3 + - command: terraform plan vpc -s plat-ue2-prod name: step-3 ``` @@ -397,10 +397,12 @@ If step-2 fails, you'll see: ```console Step 'step-2' failed! -Failed command: terraform plan test/test-component-override-2 + +Command failed: +terraform plan vpc -s plat-ue2-staging To resume the workflow from this step, run: -atmos workflow test-1 -f workflow1 --from-step step-2 +atmos workflow provision-vpcs -f networking --from-step step-2 ``` ### Stack Precedence From c2e327b15dfb5c24cae846530a8a5250f302047c Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Sun, 10 Nov 2024 00:14:56 +0000 Subject: [PATCH 4/7] clean up error message --- internal/exec/workflow_utils.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/exec/workflow_utils.go b/internal/exec/workflow_utils.go index c79dcf298..e3fbb3641 100644 --- a/internal/exec/workflow_utils.go +++ b/internal/exec/workflow_utils.go @@ -111,7 +111,9 @@ func ExecuteWorkflow( workflowFileName = strings.TrimSuffix(workflowFileName, filepath.Ext(workflowFileName)) failedMsg := color.New(color.FgRed).Sprintf("\nStep '%s' failed!", step.Name) - cmdMsg := color.New(color.FgYellow).Sprintf("\nFailed command: %s", command) + + u.LogDebug(cliConfig, fmt.Sprintf("\nCommand failed: %s", command)) + u.LogDebug(cliConfig, fmt.Sprintf("Error: %v", err)) resumeWithCd := fmt.Sprintf("cd %s && atmos workflow %s -f %s --from-step %s", filepath.Dir(workflowPath), @@ -128,7 +130,7 @@ func ExecuteWorkflow( step.Name, ) - return fmt.Errorf("%s\n%s\n%s\nError: %v", failedMsg, cmdMsg, resumeMsg, err) + return fmt.Errorf("%s\n%s", failedMsg, resumeMsg) } } From 4990c98c499a3c045f1bea84e294f75cfbef375c Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Sun, 10 Nov 2024 16:30:00 +0000 Subject: [PATCH 5/7] move workflow error message --- cmd/workflow.go | 20 +++++++++++++++++++- internal/exec/help.go | 13 ------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/cmd/workflow.go b/cmd/workflow.go index c19241dc1..c641fb21c 100644 --- a/cmd/workflow.go +++ b/cmd/workflow.go @@ -12,7 +12,25 @@ import ( var workflowCmd = &cobra.Command{ Use: "workflow", Short: "Execute a workflow", - Long: `This command executes a workflow: atmos workflow -f `, + Long: `This command executes a workflow: atmos workflow -f + +Atmos workflow commands support failure handling and resume functionality: + +When a workflow step fails: + - The failed step name and command will be displayed + - A resume command will be provided to restart from the failed step + +Example: +Step 'deploy-vpc' failed! +Error: Error applying plan: +1 error occurred: AWS API call failed + +Command failed: terraform apply vpc -auto-approve + +To resume the workflow from this step, run: +atmos workflow deploy-infra -f workflow1 --from-step deploy-vpc + +For more details refer to https://atmos.tools/cli/commands/workflow/`, Example: "atmos workflow\n" + "atmos workflow -f \n" + "atmos workflow -f -s \n" + diff --git a/internal/exec/help.go b/internal/exec/help.go index 40095aad2..e28c8a98c 100644 --- a/internal/exec/help.go +++ b/internal/exec/help.go @@ -103,19 +103,6 @@ func processHelp( "If the workspace does not exist, the command creates it by executing the 'terraform workspace new' command.\n\n" + "Usage: atmos terraform workspace -s \n\n" + "For more details refer to https://atmos.tools/cli/commands/terraform/workspace\n") - } else if componentType == "workflow" && command == "" { - u.PrintMessage("\nAtmos workflow commands support failure handling and resume functionality:\n\n" + - "When a workflow step fails:\n" + - " - The failed step name and command will be displayed\n" + - " - A resume command will be provided to restart from the failed step\n\n" + - "Example:\n" + - "Step 'deploy-vpc' failed!\n" + - "Error: Error applying plan:\n" + - "1 error occurred: AWS API call failed\n\n" + - "Command failed: terraform apply vpc -auto-approve\n\n" + - "To resume the workflow from this step, run:\n" + - "atmos workflow deploy-infra -f workflow1 --from-step deploy-vpc\n\n" + - "For more details refer to https://atmos.tools/cli/commands/workflow/\n") } else { u.PrintMessage(fmt.Sprintf("\nAtmos supports native '%s' commands with all the options, arguments and flags.\n", componentType)) u.PrintMessage("In addition, 'component' and 'stack' are required in order to generate variables for the component in the stack.\n") From 0373a040a9f029d291959cb6ffa22d68f1b609b3 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Sun, 10 Nov 2024 17:25:34 +0000 Subject: [PATCH 6/7] move workflow msg --- internal/exec/workflow_utils.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/internal/exec/workflow_utils.go b/internal/exec/workflow_utils.go index e3fbb3641..b788db98d 100644 --- a/internal/exec/workflow_utils.go +++ b/internal/exec/workflow_utils.go @@ -115,14 +115,6 @@ func ExecuteWorkflow( u.LogDebug(cliConfig, fmt.Sprintf("\nCommand failed: %s", command)) u.LogDebug(cliConfig, fmt.Sprintf("Error: %v", err)) - resumeWithCd := fmt.Sprintf("cd %s && atmos workflow %s -f %s --from-step %s", - filepath.Dir(workflowPath), - workflow, - workflowFileName, - step.Name, - ) - u.LogDebug(cliConfig, fmt.Sprintf("\nFull resume command: %s", resumeWithCd)) - resumeMsg := color.New(color.FgGreen).Sprintf( "\nTo resume the workflow from this step, run:\natmos workflow %s -f %s --from-step %s", workflow, From e5843c01665abae7cf42e1cd76d32ad950d52cd7 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Sun, 10 Nov 2024 17:25:45 +0000 Subject: [PATCH 7/7] remove cd command --- cmd/workflow.go | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/cmd/workflow.go b/cmd/workflow.go index c641fb21c..206382eed 100644 --- a/cmd/workflow.go +++ b/cmd/workflow.go @@ -12,29 +12,14 @@ import ( var workflowCmd = &cobra.Command{ Use: "workflow", Short: "Execute a workflow", - Long: `This command executes a workflow: atmos workflow -f - -Atmos workflow commands support failure handling and resume functionality: - -When a workflow step fails: - - The failed step name and command will be displayed - - A resume command will be provided to restart from the failed step - -Example: -Step 'deploy-vpc' failed! -Error: Error applying plan: -1 error occurred: AWS API call failed - -Command failed: terraform apply vpc -auto-approve - -To resume the workflow from this step, run: -atmos workflow deploy-infra -f workflow1 --from-step deploy-vpc - -For more details refer to https://atmos.tools/cli/commands/workflow/`, + Long: `This command executes a workflow: atmos workflow -f `, Example: "atmos workflow\n" + "atmos workflow -f \n" + "atmos workflow -f -s \n" + - "atmos workflow -f --from-step ", + "atmos workflow -f --from-step \n\n" + + "To resume the workflow from this step, run:\n" + + "atmos workflow deploy-infra -f workflow1 --from-step deploy-vpc\n\n" + + "For more details refer to https://atmos.tools/cli/commands/workflow/", FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false}, Run: func(cmd *cobra.Command, args []string) { err := e.ExecuteWorkflowCmd(cmd, args)