From 506aec408ba37977fae98fe49eca514e9dce4492 Mon Sep 17 00:00:00 2001 From: UKEME BASSEY Date: Tue, 6 Jun 2023 10:33:31 -0400 Subject: [PATCH 1/5] incorporate various post plan terminal statuses --- tfe/resource_tfe_workspace_run.go | 69 +++++++++------ tfe/workspace_run_helpers.go | 141 ++++++++++++++++++++++++++---- 2 files changed, 169 insertions(+), 41 deletions(-) diff --git a/tfe/resource_tfe_workspace_run.go b/tfe/resource_tfe_workspace_run.go index e0dfd8219..20d9e0212 100644 --- a/tfe/resource_tfe_workspace_run.go +++ b/tfe/resource_tfe_workspace_run.go @@ -18,31 +18,27 @@ var ( ) var planPendingStatuses = map[tfe.RunStatus]bool{ - tfe.RunPending: true, - tfe.RunPlanQueued: true, - tfe.RunPlanning: true, - tfe.RunCostEstimating: true, - tfe.RunPolicyChecking: true, - tfe.RunQueuing: true, - tfe.RunFetching: true, -} - -var planTerminalStatuses = map[tfe.RunStatus]bool{ - tfe.RunPlanned: true, - tfe.RunPlannedAndFinished: true, - tfe.RunErrored: true, - tfe.RunCostEstimated: true, - tfe.RunPolicyChecked: true, - tfe.RunPolicySoftFailed: true, - tfe.RunPolicyOverride: true, + tfe.RunPending: true, + tfe.RunPlanQueued: true, + tfe.RunPlanning: true, + tfe.RunCostEstimating: true, + tfe.RunPolicyChecking: true, + tfe.RunQueuing: true, + tfe.RunFetching: true, + tfe.RunPostPlanRunning: true, + tfe.RunPostPlanCompleted: true, + tfe.RunPrePlanRunning: true, + tfe.RunPrePlanCompleted: true, } var applyPendingStatuses = map[tfe.RunStatus]bool{ - tfe.RunConfirmed: true, - tfe.RunApplyQueued: true, - tfe.RunApplying: true, - tfe.RunQueuing: true, - tfe.RunFetching: true, + tfe.RunConfirmed: true, + tfe.RunApplyQueued: true, + tfe.RunApplying: true, + tfe.RunQueuing: true, + tfe.RunFetching: true, + tfe.RunQueuingApply: true, + tfe.RunPreApplyRunning: true, } var applyDoneStatuses = map[tfe.RunStatus]bool{ @@ -50,10 +46,33 @@ var applyDoneStatuses = map[tfe.RunStatus]bool{ tfe.RunErrored: true, } +var confirmationPendingStatuses = map[tfe.RunStatus]bool{ + tfe.RunPostPlanCompleted: true, + tfe.RunPlanned: true, + tfe.RunCostEstimated: true, + tfe.RunPolicyChecked: true, +} + var confirmationDoneStatuses = map[tfe.RunStatus]bool{ - tfe.RunConfirmed: true, - tfe.RunApplyQueued: true, - tfe.RunApplying: true, + tfe.RunConfirmed: true, + tfe.RunApplyQueued: true, + tfe.RunApplying: true, + tfe.RunPrePlanCompleted: true, + tfe.RunPrePlanRunning: true, + tfe.RunQueuingApply: true, +} + +var policyOverridenStatuses = map[tfe.RunStatus]bool{ + tfe.RunPolicyChecked: true, + tfe.RunConfirmed: true, + tfe.RunApplyQueued: true, + tfe.RunApplying: true, + tfe.RunPrePlanCompleted: true, + tfe.RunPrePlanRunning: true, +} + +var policyOverridePendingStatuses = map[tfe.RunStatus]bool{ + tfe.RunPolicyOverride: true, } func resourceTFEWorkspaceRun() *schema.Resource { diff --git a/tfe/workspace_run_helpers.go b/tfe/workspace_run_helpers.go index fea3239e7..8d2a6e751 100644 --- a/tfe/workspace_run_helpers.go +++ b/tfe/workspace_run_helpers.go @@ -95,6 +95,10 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b } log.Printf("[DEBUG] Run %s created for workspace %s", run.ID, ws.ID) + hasPostPlanTaskStage, err := readPostPlanTaskStageInRun(meta, run.ID) + if err != nil { + return err + } // in fire-and-forget mode, that's all we need to do if !waitForRun { @@ -103,7 +107,7 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b } isPlanOp := true - run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, planPendingStatuses, planTerminalStatuses) + run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, planPendingStatuses, isPlanComplete) if err != nil { return err } @@ -118,6 +122,20 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b return fmt.Errorf("run errored during plan, use the run ID %s to debug error", run.ID) } + if run.Status == tfe.RunPolicyOverride { + log.Printf("[INFO] Policy check soft-failed, awaiting manual override for run %q", run.ID) + run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, policyOverridePendingStatuses, isManuallyOverriden) + if err != nil { + return err + } + } + + if !run.HasChanges && !run.AllowEmptyApply { + run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, confirmationPendingStatuses, isPlannedAndFinished) + if err != nil { + return err + } + } // A run is complete when it is successfully planned with no changes to apply if run.Status == tfe.RunPlannedAndFinished { log.Printf("[INFO] Plan finished, no changes to apply") @@ -125,12 +143,10 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b return nil } - if run.Status == tfe.RunPolicyOverride { - log.Printf("[INFO] Policy check soft-failed, awaiting manual override for run %q", run.ID) - run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, map[tfe.RunStatus]bool{tfe.RunPolicyOverride: true}, confirmationDoneStatuses) - if err != nil { - return err - } + // wait for run to be comfirmable before attempting to confirm + run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, confirmationPendingStatuses, isConfirmable) + if err != nil { + return err } // if human approval is required, an apply will auto kick off when run is manually approved if manualConfirm { @@ -138,7 +154,7 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b confirmationPendingStatus[run.Status] = true log.Printf("[INFO] Plan complete, waiting for manual confirm before proceeding run %q", run.ID) - run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, confirmationPendingStatus, confirmationDoneStatuses) + run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, confirmationPendingStatus, isConfirmed) if err != nil { return err } @@ -159,7 +175,7 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b } isPlanOp = false - run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, applyPendingStatuses, applyDoneStatuses) + run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, applyPendingStatuses, isCompleted) if err != nil { return err } @@ -182,9 +198,8 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b } } -func awaitRun(meta interface{}, runID string, wsID string, organization string, isPlanOp bool, runPendingStatus map[tfe.RunStatus]bool, - runTerminalStatus map[tfe.RunStatus]bool, -) (*tfe.Run, error) { +func awaitRun(meta interface{}, runID string, wsID string, organization string, isPlanOp bool, + hasPostPlanTaskStage bool, runPendingStatus map[tfe.RunStatus]bool, isDone func(*tfe.Run, bool) bool) (*tfe.Run, error) { config := meta.(ConfiguredClient) for i := 0; ; i++ { @@ -199,11 +214,10 @@ func awaitRun(meta interface{}, runID string, wsID string, organization string, continue } - _, runHasEnded := runTerminalStatus[run.Status] _, runIsInProgress := runPendingStatus[run.Status] switch { - case runHasEnded: + case isDone(run, hasPostPlanTaskStage): log.Printf("[INFO] Run %s has reached a terminal state: %s", runID, run.Status) return run, nil case runIsInProgress: @@ -265,9 +279,12 @@ func awaitRun(meta interface{}, runID string, wsID string, organization string, } log.Printf("[INFO] Waiting for run %s, status is %s", runID, run.Status) + case run.Status == tfe.RunCanceled: + log.Printf("[INFO] Run %s has been canceled, status is %s", runID, run.Status) + return nil, fmt.Errorf("run %s has been canceled, status is %s", runID, run.Status) default: - log.Printf("[INFO] Run %s has entered state: %s", runID, run.Status) - return run, nil + log.Printf("[INFO] Run %s has entered unexpected state: %s", runID, run.Status) + return nil, fmt.Errorf("run %s has entered unexpected state: %s", runID, run.Status) } } } @@ -359,3 +376,95 @@ func backoff(min, max float64, iter int) time.Duration { } return time.Duration(backoff) * time.Millisecond } + +func readPostPlanTaskStageInRun(meta interface{}, runID string) (bool, error) { + config := meta.(ConfiguredClient) + hasPostPlanTaskStage := false + options := tfe.TaskStageListOptions{} + + for { + taskStages, err := config.Client.TaskStages.List(ctx, runID, &options) + if err != nil { + return hasPostPlanTaskStage, fmt.Errorf("[ERROR] Could not read task stages for run %s: %v", runID, err) + } + for _, item := range taskStages.Items { + if item.Stage == tfe.PostPlan { + hasPostPlanTaskStage = true + return hasPostPlanTaskStage, nil + } + } + + // Exit the loop when we've seen all pages. + if taskStages.CurrentPage >= taskStages.TotalPages { + break + } + + options.PageNumber = taskStages.NextPage + } + + return hasPostPlanTaskStage, nil +} + +func isPlanComplete(run *tfe.Run, hasPostPlanTaskStage bool) bool { + hasPolicyCheck := len(run.PolicyChecks) > 0 + hasCostEstimate := run.CostEstimate != nil + + /* + tfe.RunCostEstimated and tfe.RunPolicyChecked are optional terminal statuses that may or may not be present in a plan. + Policy checks are currently the last checks performed as a post plan op if present + These following statuses are added at compute-time. When plan has changes, the following occur in order: + 1. tfe.RunPolicyChecked is the plan terminal status if present, otherwise + 2. tfe.RunCostEstimated is the plan terminal status if present, otherwise + 3. tfe.RunPostPlanCompleted is the plan terminal status if present, otherwise + 4. tfe.RunPlanned is the plan terminal status if all of above is absent + */ + var planTerminalStatuses = map[tfe.RunStatus]bool{ + tfe.RunErrored: true, + tfe.RunPlannedAndFinished: true, + tfe.RunPolicySoftFailed: true, + tfe.RunPolicyOverride: true, + } + + if hasPolicyCheck { + planTerminalStatuses[tfe.RunPolicyChecked] = true + + planPendingStatuses[tfe.RunCostEstimated] = true + planPendingStatuses[tfe.RunPlanned] = true + } else if hasCostEstimate { + planTerminalStatuses[tfe.RunCostEstimated] = true + + planPendingStatuses[tfe.RunPlanned] = true + } else if hasPostPlanTaskStage { + planTerminalStatuses[tfe.RunPostPlanCompleted] = true + + planPendingStatuses[tfe.RunPlanned] = true + } else { + // there are no post plan ops + planTerminalStatuses[tfe.RunPlanned] = true + } + _, found := planTerminalStatuses[run.Status] + return found +} + +func isManuallyOverriden(run *tfe.Run, hasPostPlanTaskStage bool) bool { + _, found := policyOverridenStatuses[run.Status] + return found +} + +func isPlannedAndFinished(run *tfe.Run, hasPostPlanTaskStage bool) bool { + return tfe.RunPlannedAndFinished == run.Status +} + +func isConfirmable(run *tfe.Run, hasPostPlanTaskStage bool) bool { + return run.Actions.IsConfirmable +} + +func isConfirmed(run *tfe.Run, hasPostPlanTaskStage bool) bool { + _, found := confirmationDoneStatuses[run.Status] + return found +} + +func isCompleted(run *tfe.Run, hasPostPlanTaskStage bool) bool { + _, found := applyDoneStatuses[run.Status] + return found +} From 535297ba7c3bbe953fea2d72a1bc59867d35b94c Mon Sep 17 00:00:00 2001 From: UKEME BASSEY Date: Tue, 6 Jun 2023 10:41:15 -0400 Subject: [PATCH 2/5] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70e87068c..e90853f8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ BUG FIXES: ENHANCEMENTS: * `r/tfe_agent_pool`: Add attribute `organization_scoped` to set the scope of an agent pool by @hs26gill [870](https://github.com/hashicorp/terraform-provider-tfe/pull/870) * `d/tfe_agent_pool`: Add attribute `organization_scoped` and `allowed_workspace_ids` to retrieve agent pool scope and associated allowed workspace ids by @hs26gill [870](https://github.com/hashicorp/terraform-provider-tfe/pull/870) +* `r/tfe_workspace_run`: Incorporate missing post plan statuses to ensure that `tfe_workspace_run` resource waits for a plan to process completely before attempting to confirm the associated run, by @uk1288 ([#921](https://github.com/hashicorp/terraform-provider-tfe/pull/921)) ## v0.45.0 (May 25, 2023) From b2804e75b57292895452d5b7c03c4d770de76b00 Mon Sep 17 00:00:00 2001 From: UKEME BASSEY Date: Wed, 14 Jun 2023 10:57:56 -0400 Subject: [PATCH 3/5] simplify methods to represent smaller steps --- tfe/resource_tfe_workspace_run.go | 30 +-- tfe/workspace_run_helpers.go | 302 ++++++++++++++++-------------- tfe/workspace_run_helpers_test.go | 5 +- 3 files changed, 184 insertions(+), 153 deletions(-) diff --git a/tfe/resource_tfe_workspace_run.go b/tfe/resource_tfe_workspace_run.go index 20d9e0212..2c99c0b0c 100644 --- a/tfe/resource_tfe_workspace_run.go +++ b/tfe/resource_tfe_workspace_run.go @@ -32,13 +32,14 @@ var planPendingStatuses = map[tfe.RunStatus]bool{ } var applyPendingStatuses = map[tfe.RunStatus]bool{ - tfe.RunConfirmed: true, - tfe.RunApplyQueued: true, - tfe.RunApplying: true, - tfe.RunQueuing: true, - tfe.RunFetching: true, - tfe.RunQueuingApply: true, - tfe.RunPreApplyRunning: true, + tfe.RunConfirmed: true, + tfe.RunApplyQueued: true, + tfe.RunApplying: true, + tfe.RunQueuing: true, + tfe.RunFetching: true, + tfe.RunQueuingApply: true, + tfe.RunPreApplyRunning: true, + tfe.RunPreApplyCompleted: true, } var applyDoneStatuses = map[tfe.RunStatus]bool{ @@ -54,15 +55,16 @@ var confirmationPendingStatuses = map[tfe.RunStatus]bool{ } var confirmationDoneStatuses = map[tfe.RunStatus]bool{ - tfe.RunConfirmed: true, - tfe.RunApplyQueued: true, - tfe.RunApplying: true, - tfe.RunPrePlanCompleted: true, - tfe.RunPrePlanRunning: true, - tfe.RunQueuingApply: true, + tfe.RunConfirmed: true, + tfe.RunApplyQueued: true, + tfe.RunApplying: true, + tfe.RunPrePlanCompleted: true, + tfe.RunPrePlanRunning: true, + tfe.RunQueuingApply: true, + tfe.RunPreApplyCompleted: true, } -var policyOverridenStatuses = map[tfe.RunStatus]bool{ +var policyOverriddenStatuses = map[tfe.RunStatus]bool{ tfe.RunPolicyChecked: true, tfe.RunConfirmed: true, tfe.RunApplyQueued: true, diff --git a/tfe/workspace_run_helpers.go b/tfe/workspace_run_helpers.go index 8d2a6e751..35870adf4 100644 --- a/tfe/workspace_run_helpers.go +++ b/tfe/workspace_run_helpers.go @@ -15,23 +15,9 @@ import ( ) func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun bool, currentRetryAttempts int) error { - var runArgs map[string]interface{} - - if isDestroyRun { - // destroy block is optional, if it is not set then destroy action is noop for a destroy type run - destroyArgs, ok := d.GetOk("destroy") - if !ok { - return nil - } - runArgs = destroyArgs.([]interface{})[0].(map[string]interface{}) - } else { - createArgs, ok := d.GetOk("apply") - if !ok { - // apply block is optional, if it is not set then set a random ID to allow for consistent result after apply ops - d.SetId(fmt.Sprintf("%d", rand.New(rand.NewSource(time.Now().UnixNano())).Int())) - return nil - } - runArgs = createArgs.([]interface{})[0].(map[string]interface{}) + runArgs := getRunArgs(d, isDestroyRun) + if runArgs == nil { + return nil } retryBOMin := runArgs["retry_backoff_min"].(int) @@ -63,39 +49,7 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b waitForRun := runArgs["wait_for_run"].(bool) manualConfirm := runArgs["manual_confirm"].(bool) - // In fire-and-forget mode (waitForRun=false), autoapply is set to !manualConfirm - // This should be intuitive, as "manual confirm" is the opposite of "auto apply" - // - // In apply-and-wait mode (waitForRun=true), autoapply is set to false to give the tfe_workspace_run resource full control of run confirmation. - autoApply := false - if !waitForRun { - autoApply = !manualConfirm - } - - runConfig := tfe.RunCreateOptions{ - Workspace: ws, - IsDestroy: tfe.Bool(isDestroyRun), - Message: tfe.String(fmt.Sprintf( - "Triggered by tfe_workspace_run resource via terraform-provider-tfe on %s", - time.Now().Format(time.UnixDate), - )), - AutoApply: tfe.Bool(autoApply), - } - log.Printf("[DEBUG] Create run for workspace: %s", ws.ID) - run, err := config.Client.Runs.Create(ctx, runConfig) - if err != nil { - return fmt.Errorf( - "error creating run for workspace %s: %w", ws.ID, err) - } - - if run == nil { - log.Printf("[ERROR] The client returned both a nil run and nil error, this should not happen") - return fmt.Errorf( - "the client returned both a nil run and nil error for workspace %s, this should not happen", ws.ID) - } - - log.Printf("[DEBUG] Run %s created for workspace %s", run.ID, ws.ID) - hasPostPlanTaskStage, err := readPostPlanTaskStageInRun(meta, run.ID) + run, err := createRun(config.Client, waitForRun, manualConfirm, isDestroyRun, ws) if err != nil { return err } @@ -107,12 +61,17 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b } isPlanOp := true - run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, planPendingStatuses, isPlanComplete) + hasPostPlanTaskStage, err := readPostPlanTaskStageInRun(config.Client, run.ID) + if err != nil { + return err + } + + run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, planPendingStatuses, isPlanComplete) if err != nil { return err } - if run.Status == tfe.RunErrored || run.Status == tfe.RunStatus(tfe.PolicySoftFailed) { + if (run.Status == tfe.RunErrored) || (run.Status == tfe.RunStatus(tfe.PolicySoftFailed)) { if retry && currentRetryAttempts < retryMaxAttempts { currentRetryAttempts++ log.Printf("[INFO] Run errored during plan, retrying run, retry count: %d", currentRetryAttempts) @@ -124,14 +83,14 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b if run.Status == tfe.RunPolicyOverride { log.Printf("[INFO] Policy check soft-failed, awaiting manual override for run %q", run.ID) - run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, policyOverridePendingStatuses, isManuallyOverriden) + run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, policyOverridePendingStatuses, isManuallyOverriden) if err != nil { return err } } if !run.HasChanges && !run.AllowEmptyApply { - run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, confirmationPendingStatuses, isPlannedAndFinished) + run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, confirmationPendingStatuses, isPlannedAndFinished) if err != nil { return err } @@ -144,42 +103,114 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b } // wait for run to be comfirmable before attempting to confirm - run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, confirmationPendingStatuses, isConfirmable) + run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, confirmationPendingStatuses, isConfirmable) if err != nil { return err } + + err = confirmRun(config.Client, manualConfirm, isPlanOp, hasPostPlanTaskStage, run, ws) + if err != nil { + return err + } + + isPlanOp = false + run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, applyPendingStatuses, isCompleted) + if err != nil { + return err + } + + return completeOrRetryRun(meta, run, d, retry, currentRetryAttempts, retryMaxAttempts, isDestroyRun) +} + +func getRunArgs(d *schema.ResourceData, isDestroyRun bool) map[string]interface{} { + var runArgs map[string]interface{} + + if isDestroyRun { + // destroy block is optional, if it is not set then destroy action is noop for a destroy type run + destroyArgs, ok := d.GetOk("destroy") + if !ok { + return nil + } + runArgs = destroyArgs.([]interface{})[0].(map[string]interface{}) + } else { + createArgs, ok := d.GetOk("apply") + if !ok { + // apply block is optional, if it is not set then set a random ID to allow for consistent result after apply ops + d.SetId(fmt.Sprintf("%d", rand.New(rand.NewSource(time.Now().UnixNano())).Int())) + return nil + } + runArgs = createArgs.([]interface{})[0].(map[string]interface{}) + } + + return runArgs +} + +func createRun(client *tfe.Client, waitForRun bool, manualConfirm bool, isDestroyRun bool, ws *tfe.Workspace) (*tfe.Run, error) { + // In fire-and-forget mode (waitForRun=false), autoapply is set to !manualConfirm + // This should be intuitive, as "manual confirm" is the opposite of "auto apply" + // + // In apply-and-wait mode (waitForRun=true), autoapply is set to false to give the tfe_workspace_run resource full control of run confirmation. + autoApply := false + if !waitForRun { + autoApply = !manualConfirm + } + + runConfig := tfe.RunCreateOptions{ + Workspace: ws, + IsDestroy: tfe.Bool(isDestroyRun), + Message: tfe.String(fmt.Sprintf( + "Triggered by tfe_workspace_run resource via terraform-provider-tfe on %s", + time.Now().Format(time.UnixDate), + )), + AutoApply: tfe.Bool(autoApply), + } + log.Printf("[DEBUG] Create run for workspace: %s", ws.ID) + run, err := client.Runs.Create(ctx, runConfig) + if err != nil { + return nil, fmt.Errorf( + "error creating run for workspace %s: %w", ws.ID, err) + } + + if run == nil { + log.Printf("[ERROR] The client returned both a nil run and nil error, this should not happen") + return nil, fmt.Errorf( + "the client returned both a nil run and nil error for workspace %s, this should not happen", ws.ID) + } + + log.Printf("[DEBUG] Run %s created for workspace %s", run.ID, ws.ID) + return run, nil +} + +func confirmRun(client *tfe.Client, manualConfirm bool, isPlanOp bool, hasPostPlanTaskStage bool, run *tfe.Run, ws *tfe.Workspace) error { // if human approval is required, an apply will auto kick off when run is manually approved if manualConfirm { confirmationPendingStatus := map[tfe.RunStatus]bool{} confirmationPendingStatus[run.Status] = true log.Printf("[INFO] Plan complete, waiting for manual confirm before proceeding run %q", run.ID) - run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, confirmationPendingStatus, isConfirmed) + _, err := awaitRun(client, run.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, confirmationPendingStatus, isConfirmed) if err != nil { return err } } else { // if human approval is NOT required, go ahead and kick off an apply log.Printf("[INFO] Plan complete, confirming an apply for run %q", run.ID) - err = config.Client.Runs.Apply(ctx, run.ID, tfe.RunApplyOptions{ + err := client.Runs.Apply(ctx, run.ID, tfe.RunApplyOptions{ Comment: tfe.String(fmt.Sprintf("Run confirmed by tfe_workspace_run resource via terraform-provider-tfe on %s", time.Now().Format(time.UnixDate))), }) if err != nil { - refreshed, fetchErr := config.Client.Runs.Read(ctx, run.ID) + refreshed, fetchErr := client.Runs.Read(ctx, run.ID) if fetchErr != nil { err = fmt.Errorf("%w\n additionally, got an error while reading the run: %s", err, fetchErr.Error()) } return fmt.Errorf("run errored while applying run %s (waited til status %s, currently status %s): %w", run.ID, run.Status, refreshed.Status, err) } } + return nil +} - isPlanOp = false - run, err = awaitRun(meta, run.ID, ws.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, applyPendingStatuses, isCompleted) - if err != nil { - return err - } - +func completeOrRetryRun(meta interface{}, run *tfe.Run, d *schema.ResourceData, retry bool, currentRetryAttempts int, retryMaxAttempts int, isDestroyRun bool) error { switch run.Status { case tfe.RunApplied: log.Printf("[INFO] Apply complete for run %q", run.ID) @@ -198,17 +229,15 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b } } -func awaitRun(meta interface{}, runID string, wsID string, organization string, isPlanOp bool, +func awaitRun(client *tfe.Client, runID string, organization string, isPlanOp bool, hasPostPlanTaskStage bool, runPendingStatus map[tfe.RunStatus]bool, isDone func(*tfe.Run, bool) bool) (*tfe.Run, error) { - config := meta.(ConfiguredClient) - for i := 0; ; i++ { select { case <-ctx.Done(): return nil, fmt.Errorf("context canceled: %w", ctx.Err()) case <-time.After(backoff(backoffMin, backoffMax, i)): log.Printf("[DEBUG] Polling run %s", runID) - run, err := config.Client.Runs.Read(ctx, runID) + run, err := client.Runs.Read(ctx, runID) if err != nil { log.Printf("[ERROR] Could not read run %s: %v", runID, err) continue @@ -221,64 +250,7 @@ func awaitRun(meta interface{}, runID string, wsID string, organization string, log.Printf("[INFO] Run %s has reached a terminal state: %s", runID, run.Status) return run, nil case runIsInProgress: - log.Printf("[DEBUG] Reading workspace %s", wsID) - ws, err := config.Client.Workspaces.ReadByID(ctx, wsID) - if err != nil { - log.Printf("[ERROR] Unable to read workspace %s: %v", wsID, err) - continue - } - - // if the workspace is locked and the current run has not started, assume that workspace was locked for other purposes. - // display a message to indicate that the workspace is waiting to be manually unlocked before the run can proceed - if ws.Locked && ws.CurrentRun != nil { - currentRun, err := config.Client.Runs.Read(ctx, ws.CurrentRun.ID) - if err != nil { - log.Printf("[ERROR] Unable to read current run %s: %v", ws.CurrentRun.ID, err) - continue - } - - if currentRun.Status == tfe.RunPending { - log.Printf("[INFO] Waiting for manually locked workspace to be unlocked") - continue - } - } - - // if this run is the current run in it's workspace, display it's position in the organization queue - if ws.CurrentRun != nil && ws.CurrentRun.ID == runID { - runPositionInOrg, err := readRunPositionInOrgQueue(meta, runID, organization) - if err != nil { - log.Printf("[ERROR] Unable to read run position in organization queue %v", err) - continue - } - - orgCapacity, err := config.Client.Organizations.ReadCapacity(ctx, organization) - if err != nil { - log.Printf("[ERROR] Unable to read capacity for organization %s: %v", organization, err) - continue - } - if runPositionInOrg > 0 { - log.Printf("[INFO] Waiting for %d queued run(s) before starting run", runPositionInOrg-orgCapacity.Running) - continue - } - } - - // if this run is not the current run in it's workspace, display it's position in the workspace queue - runPositionInWorkspace, err := readRunPositionInWorkspaceQueue(meta, runID, wsID, isPlanOp, ws.CurrentRun) - if err != nil { - log.Printf("[ERROR] Unable to read run position in workspace queue %v", err) - continue - } - - if runPositionInWorkspace > 0 { - log.Printf( - "[INFO] Waiting for %d run(s) to finish in workspace %s before being queued...", - runPositionInWorkspace, - ws.Name, - ) - continue - } - - log.Printf("[INFO] Waiting for run %s, status is %s", runID, run.Status) + logRunProgress(client, organization, isPlanOp, run) case run.Status == tfe.RunCanceled: log.Printf("[INFO] Run %s has been canceled, status is %s", runID, run.Status) return nil, fmt.Errorf("run %s has been canceled, status is %s", runID, run.Status) @@ -290,13 +262,73 @@ func awaitRun(meta interface{}, runID string, wsID string, organization string, } } -func readRunPositionInOrgQueue(meta interface{}, runID string, organization string) (int, error) { - config := meta.(ConfiguredClient) +func logRunProgress(client *tfe.Client, organization string, isPlanOp bool, run *tfe.Run) { + log.Printf("[DEBUG] Reading workspace %s", run.Workspace.ID) + ws, err := client.Workspaces.ReadByID(ctx, run.Workspace.ID) + if err != nil { + log.Printf("[ERROR] Unable to read workspace %s: %v", run.Workspace.ID, err) + return + } + + // if the workspace is locked and the current run has not started, assume that workspace was locked for other purposes. + // display a message to indicate that the workspace is waiting to be manually unlocked before the run can proceed + if ws.Locked && ws.CurrentRun != nil { + currentRun, err := client.Runs.Read(ctx, ws.CurrentRun.ID) + if err != nil { + log.Printf("[ERROR] Unable to read current run %s: %v", ws.CurrentRun.ID, err) + return + } + + if currentRun.Status == tfe.RunPending { + log.Printf("[INFO] Waiting for manually locked workspace to be unlocked") + return + } + } + + // if this run is the current run in it's workspace, display it's position in the organization queue + if ws.CurrentRun != nil && ws.CurrentRun.ID == run.ID { + runPositionInOrg, err := readRunPositionInOrgQueue(client, run.ID, organization) + if err != nil { + log.Printf("[ERROR] Unable to read run position in organization queue %v", err) + return + } + + orgCapacity, err := client.Organizations.ReadCapacity(ctx, organization) + if err != nil { + log.Printf("[ERROR] Unable to read capacity for organization %s: %v", organization, err) + return + } + if runPositionInOrg > 0 { + log.Printf("[INFO] Waiting for %d queued run(s) before starting run", runPositionInOrg-orgCapacity.Running) + return + } + } + + // if this run is not the current run in it's workspace, display it's position in the workspace queue + runPositionInWorkspace, err := readRunPositionInWorkspaceQueue(client, run.ID, ws.ID, isPlanOp, ws.CurrentRun) + if err != nil { + log.Printf("[ERROR] Unable to read run position in workspace queue %v", err) + return + } + + if runPositionInWorkspace > 0 { + log.Printf( + "[INFO] Waiting for %d run(s) to finish in workspace %s before being queued...", + runPositionInWorkspace, + ws.Name, + ) + return + } + + log.Printf("[INFO] Waiting for run %s, status is %s", run.ID, run.Status) +} + +func readRunPositionInOrgQueue(client *tfe.Client, runID string, organization string) (int, error) { position := 0 options := tfe.ReadRunQueueOptions{} for { - runQueue, err := config.Client.Organizations.ReadRunQueue(ctx, organization, options) + runQueue, err := client.Organizations.ReadRunQueue(ctx, organization, options) if err != nil { return position, fmt.Errorf("unable to read run queue for organization %s: %w", organization, err) } @@ -318,14 +350,13 @@ func readRunPositionInOrgQueue(meta interface{}, runID string, organization stri return position, nil } -func readRunPositionInWorkspaceQueue(meta interface{}, runID string, wsID string, isPlanOp bool, currentRun *tfe.Run) (int, error) { - config := meta.(ConfiguredClient) +func readRunPositionInWorkspaceQueue(client *tfe.Client, runID string, wsID string, isPlanOp bool, currentRun *tfe.Run) (int, error) { position := 0 options := tfe.RunListOptions{} found := false for { - runList, err := config.Client.Runs.List(ctx, wsID, &options) + runList, err := client.Runs.List(ctx, wsID, &options) if err != nil { return position, fmt.Errorf("unable to read run list for workspace %s: %w", wsID, err) } @@ -377,13 +408,12 @@ func backoff(min, max float64, iter int) time.Duration { return time.Duration(backoff) * time.Millisecond } -func readPostPlanTaskStageInRun(meta interface{}, runID string) (bool, error) { - config := meta.(ConfiguredClient) +func readPostPlanTaskStageInRun(client *tfe.Client, runID string) (bool, error) { hasPostPlanTaskStage := false options := tfe.TaskStageListOptions{} for { - taskStages, err := config.Client.TaskStages.List(ctx, runID, &options) + taskStages, err := client.TaskStages.List(ctx, runID, &options) if err != nil { return hasPostPlanTaskStage, fmt.Errorf("[ERROR] Could not read task stages for run %s: %v", runID, err) } @@ -447,7 +477,7 @@ func isPlanComplete(run *tfe.Run, hasPostPlanTaskStage bool) bool { } func isManuallyOverriden(run *tfe.Run, hasPostPlanTaskStage bool) bool { - _, found := policyOverridenStatuses[run.Status] + _, found := policyOverriddenStatuses[run.Status] return found } diff --git a/tfe/workspace_run_helpers_test.go b/tfe/workspace_run_helpers_test.go index 8c24c592e..b1ecf3bb7 100644 --- a/tfe/workspace_run_helpers_test.go +++ b/tfe/workspace_run_helpers_test.go @@ -81,7 +81,6 @@ func MockRunsListForWorkspaceQueue(t *testing.T, client *tfe.Client, workspaceID } func TestReadRunPositionInWorkspaceQueue(t *testing.T) { - defaultOrganization := "my-org" client := testTfeClient(t, testClientOptions{}) MockRunsListForWorkspaceQueue(t, client, "ws-1", "ws-2") @@ -114,7 +113,7 @@ func TestReadRunPositionInWorkspaceQueue(t *testing.T) { for name, testCase := range testCases { t.Run(name, func(t *testing.T) { position, err := readRunPositionInWorkspaceQueue( - ConfiguredClient{Client: client, Organization: defaultOrganization}, + client, testCase.currentRunID, testCase.workspace, false, @@ -224,7 +223,7 @@ func TestReadRunPositionInOrgQueue(t *testing.T) { for name, testCase := range testCases { t.Run(name, func(t *testing.T) { position, err := readRunPositionInOrgQueue( - ConfiguredClient{Client: client, Organization: defaultOrganization}, + client, "run-02", testCase.orgName, ) From e46189826c8f12d0f23abb5217222dd46d8008d4 Mon Sep 17 00:00:00 2001 From: UKEME BASSEY Date: Mon, 26 Jun 2023 14:35:07 -0400 Subject: [PATCH 4/5] some plan pending and terminal cleanup --- tfe/resource_tfe_workspace_run.go | 14 ----- tfe/workspace_run_helpers.go | 96 ++++++++++++++++++++----------- 2 files changed, 64 insertions(+), 46 deletions(-) diff --git a/tfe/resource_tfe_workspace_run.go b/tfe/resource_tfe_workspace_run.go index 2c99c0b0c..6d0bcf131 100644 --- a/tfe/resource_tfe_workspace_run.go +++ b/tfe/resource_tfe_workspace_run.go @@ -17,20 +17,6 @@ var ( backoffMax = 3000.0 ) -var planPendingStatuses = map[tfe.RunStatus]bool{ - tfe.RunPending: true, - tfe.RunPlanQueued: true, - tfe.RunPlanning: true, - tfe.RunCostEstimating: true, - tfe.RunPolicyChecking: true, - tfe.RunQueuing: true, - tfe.RunFetching: true, - tfe.RunPostPlanRunning: true, - tfe.RunPostPlanCompleted: true, - tfe.RunPrePlanRunning: true, - tfe.RunPrePlanCompleted: true, -} - var applyPendingStatuses = map[tfe.RunStatus]bool{ tfe.RunConfirmed: true, tfe.RunApplyQueued: true, diff --git a/tfe/workspace_run_helpers.go b/tfe/workspace_run_helpers.go index 35870adf4..b3d79e1b6 100644 --- a/tfe/workspace_run_helpers.go +++ b/tfe/workspace_run_helpers.go @@ -66,7 +66,8 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b return err } - run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, planPendingStatuses, isPlanComplete) + planPendingStatuses, planTerminalStatuses := planStatuses(run, hasPostPlanTaskStage) + run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, planPendingStatuses, isPlanComplete(planTerminalStatuses)) if err != nil { return err } @@ -83,14 +84,14 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b if run.Status == tfe.RunPolicyOverride { log.Printf("[INFO] Policy check soft-failed, awaiting manual override for run %q", run.ID) - run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, policyOverridePendingStatuses, isManuallyOverriden) + run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, policyOverridePendingStatuses, isManuallyOverriden) if err != nil { return err } } if !run.HasChanges && !run.AllowEmptyApply { - run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, confirmationPendingStatuses, isPlannedAndFinished) + run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, confirmationPendingStatuses, isPlannedAndFinished) if err != nil { return err } @@ -103,18 +104,18 @@ func createWorkspaceRun(d *schema.ResourceData, meta interface{}, isDestroyRun b } // wait for run to be comfirmable before attempting to confirm - run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, confirmationPendingStatuses, isConfirmable) + run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, confirmationPendingStatuses, isConfirmable) if err != nil { return err } - err = confirmRun(config.Client, manualConfirm, isPlanOp, hasPostPlanTaskStage, run, ws) + err = confirmRun(config.Client, manualConfirm, isPlanOp, run, ws) if err != nil { return err } isPlanOp = false - run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, applyPendingStatuses, isCompleted) + run, err = awaitRun(config.Client, run.ID, ws.Organization.Name, isPlanOp, applyPendingStatuses, isCompleted) if err != nil { return err } @@ -181,14 +182,14 @@ func createRun(client *tfe.Client, waitForRun bool, manualConfirm bool, isDestro return run, nil } -func confirmRun(client *tfe.Client, manualConfirm bool, isPlanOp bool, hasPostPlanTaskStage bool, run *tfe.Run, ws *tfe.Workspace) error { +func confirmRun(client *tfe.Client, manualConfirm bool, isPlanOp bool, run *tfe.Run, ws *tfe.Workspace) error { // if human approval is required, an apply will auto kick off when run is manually approved if manualConfirm { confirmationPendingStatus := map[tfe.RunStatus]bool{} confirmationPendingStatus[run.Status] = true log.Printf("[INFO] Plan complete, waiting for manual confirm before proceeding run %q", run.ID) - _, err := awaitRun(client, run.ID, ws.Organization.Name, isPlanOp, hasPostPlanTaskStage, confirmationPendingStatus, isConfirmed) + _, err := awaitRun(client, run.ID, ws.Organization.Name, isPlanOp, confirmationPendingStatus, isConfirmed) if err != nil { return err } @@ -229,8 +230,7 @@ func completeOrRetryRun(meta interface{}, run *tfe.Run, d *schema.ResourceData, } } -func awaitRun(client *tfe.Client, runID string, organization string, isPlanOp bool, - hasPostPlanTaskStage bool, runPendingStatus map[tfe.RunStatus]bool, isDone func(*tfe.Run, bool) bool) (*tfe.Run, error) { +func awaitRun(client *tfe.Client, runID string, organization string, isPlanOp bool, runPendingStatus map[tfe.RunStatus]bool, isDone func(*tfe.Run) bool) (*tfe.Run, error) { for i := 0; ; i++ { select { case <-ctx.Done(): @@ -243,25 +243,36 @@ func awaitRun(client *tfe.Client, runID string, organization string, isPlanOp bo continue } - _, runIsInProgress := runPendingStatus[run.Status] - - switch { - case isDone(run, hasPostPlanTaskStage): - log.Printf("[INFO] Run %s has reached a terminal state: %s", runID, run.Status) - return run, nil - case runIsInProgress: - logRunProgress(client, organization, isPlanOp, run) - case run.Status == tfe.RunCanceled: - log.Printf("[INFO] Run %s has been canceled, status is %s", runID, run.Status) - return nil, fmt.Errorf("run %s has been canceled, status is %s", runID, run.Status) - default: - log.Printf("[INFO] Run %s has entered unexpected state: %s", runID, run.Status) - return nil, fmt.Errorf("run %s has entered unexpected state: %s", runID, run.Status) + run, err = hasFinalStatus(client, run, organization, isPlanOp, runPendingStatus, isDone) + if run == nil && err == nil { + // if both error and run is nil, then run is still in progress + continue } + + return run, err } } } +func hasFinalStatus(client *tfe.Client, run *tfe.Run, organization string, isPlanOp bool, runPendingStatus map[tfe.RunStatus]bool, isDone func(*tfe.Run) bool) (*tfe.Run, error) { + _, runIsInProgress := runPendingStatus[run.Status] + + switch { + case isDone(run): + log.Printf("[INFO] Run %s has reached a terminal state: %s", run.ID, run.Status) + return run, nil + case runIsInProgress: + logRunProgress(client, organization, isPlanOp, run) + return nil, nil + case run.Status == tfe.RunCanceled: + log.Printf("[INFO] Run %s has been canceled, status is %s", run.ID, run.Status) + return nil, fmt.Errorf("run %s has been canceled, status is %s", run.ID, run.Status) + default: + log.Printf("[INFO] Run %s has entered unexpected state: %s", run.ID, run.Status) + return nil, fmt.Errorf("run %s has entered unexpected state: %s", run.ID, run.Status) + } +} + func logRunProgress(client *tfe.Client, organization string, isPlanOp bool, run *tfe.Run) { log.Printf("[DEBUG] Reading workspace %s", run.Workspace.ID) ws, err := client.Workspaces.ReadByID(ctx, run.Workspace.ID) @@ -435,7 +446,7 @@ func readPostPlanTaskStageInRun(client *tfe.Client, runID string) (bool, error) return hasPostPlanTaskStage, nil } -func isPlanComplete(run *tfe.Run, hasPostPlanTaskStage bool) bool { +func planStatuses(run *tfe.Run, hasPostPlanTaskStage bool) (map[tfe.RunStatus]bool, map[tfe.RunStatus]bool) { hasPolicyCheck := len(run.PolicyChecks) > 0 hasCostEstimate := run.CostEstimate != nil @@ -455,6 +466,20 @@ func isPlanComplete(run *tfe.Run, hasPostPlanTaskStage bool) bool { tfe.RunPolicyOverride: true, } + var planPendingStatuses = map[tfe.RunStatus]bool{ + tfe.RunPending: true, + tfe.RunPlanQueued: true, + tfe.RunPlanning: true, + tfe.RunCostEstimating: true, + tfe.RunPolicyChecking: true, + tfe.RunQueuing: true, + tfe.RunFetching: true, + tfe.RunPostPlanRunning: true, + tfe.RunPostPlanCompleted: true, + tfe.RunPrePlanRunning: true, + tfe.RunPrePlanCompleted: true, + } + if hasPolicyCheck { planTerminalStatuses[tfe.RunPolicyChecked] = true @@ -472,29 +497,36 @@ func isPlanComplete(run *tfe.Run, hasPostPlanTaskStage bool) bool { // there are no post plan ops planTerminalStatuses[tfe.RunPlanned] = true } - _, found := planTerminalStatuses[run.Status] - return found + + return planPendingStatuses, planTerminalStatuses +} + +func isPlanComplete(planTerminalStatuses map[tfe.RunStatus]bool) func(run *tfe.Run) bool { + return func(run *tfe.Run) bool { + _, found := planTerminalStatuses[run.Status] + return found + } } -func isManuallyOverriden(run *tfe.Run, hasPostPlanTaskStage bool) bool { +func isManuallyOverriden(run *tfe.Run) bool { _, found := policyOverriddenStatuses[run.Status] return found } -func isPlannedAndFinished(run *tfe.Run, hasPostPlanTaskStage bool) bool { +func isPlannedAndFinished(run *tfe.Run) bool { return tfe.RunPlannedAndFinished == run.Status } -func isConfirmable(run *tfe.Run, hasPostPlanTaskStage bool) bool { +func isConfirmable(run *tfe.Run) bool { return run.Actions.IsConfirmable } -func isConfirmed(run *tfe.Run, hasPostPlanTaskStage bool) bool { +func isConfirmed(run *tfe.Run) bool { _, found := confirmationDoneStatuses[run.Status] return found } -func isCompleted(run *tfe.Run, hasPostPlanTaskStage bool) bool { +func isCompleted(run *tfe.Run) bool { _, found := applyDoneStatuses[run.Status] return found } From 2d12bfdda7103eb6bd4d2cfe0746ec4008819354 Mon Sep 17 00:00:00 2001 From: UKEME BASSEY Date: Thu, 29 Jun 2023 10:00:30 -0400 Subject: [PATCH 5/5] upgrade go-tfe to 1.29.0 --- go.mod | 2 +- go.sum | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index ccfa54b3c..82698f8ab 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.4 // indirect github.com/hashicorp/go-slug v0.11.1 - github.com/hashicorp/go-tfe v1.28.0 + github.com/hashicorp/go-tfe v1.29.0 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hcl v1.0.0 github.com/hashicorp/hcl/v2 v2.17.0 // indirect diff --git a/go.sum b/go.sum index 508da2fe7..f64ea199f 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,6 @@ +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/ProtonMail/go-crypto v0.0.0-20230619160724-3fbb1f12458c h1:figwFwYep1Qnl64Y+Rc8tyQWE0xvYAN+5EX+rD40pTU= github.com/ProtonMail/go-crypto v0.0.0-20230619160724-3fbb1f12458c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= @@ -7,6 +10,8 @@ github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= @@ -18,6 +23,7 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= @@ -58,6 +64,8 @@ github.com/hashicorp/go-slug v0.11.1 h1:c6lLdQnlhUWbS5I7hw8SvfymoFuy6EmiFDedy6ir github.com/hashicorp/go-slug v0.11.1/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4= github.com/hashicorp/go-tfe v1.28.0 h1:YQNfHz5UPMiOD2idad4GCjzG3R2ExPww741PBPqMOIU= github.com/hashicorp/go-tfe v1.28.0/go.mod h1:z0182DGE/63AKUaWblUVBIrt+xdSmsuuXg5AoxGqDF4= +github.com/hashicorp/go-tfe v1.29.0 h1:hVvgoKtLAWTkXl9p/8WnItCaW65VJwqpjLZkXe8R2AM= +github.com/hashicorp/go-tfe v1.29.0/go.mod h1:z0182DGE/63AKUaWblUVBIrt+xdSmsuuXg5AoxGqDF4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -95,8 +103,10 @@ github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= 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/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -114,8 +124,10 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -129,8 +141,12 @@ github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DV github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -140,6 +156,7 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -149,6 +166,7 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0= github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -241,6 +259,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=