Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Pre-Plan Run Tasks to Terraform CLI #31617

Merged
merged 2 commits into from Aug 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Expand Up @@ -36,6 +36,7 @@ ENHANCEMENTS:
* The COS backend now supports global acceleration. ([#31425](https://github.com/hashicorp/terraform/issues/31425))
* providercache: include host in provider installation error ([#31524](https://github.com/hashicorp/terraform/issues/31524))
* refactoring: `moved` blocks can now be used to move resources to and from external modules ([#31556](https://github.com/hashicorp/terraform/issues/31556))
* When showing the progress of a remote operation running in Terraform Cloud, Terraform CLI will include information about pre-plan run tasks ([#31617](https://github.com/hashicorp/terraform/issues/31617))

BUG FIXES:

Expand All @@ -54,7 +55,7 @@ EXPERIMENTS:
* The built-in `defaults` function, previously used to meet the use-case of replacing null values with default values, will not graduate to stable and has been removed. Use the second argument of `optional` inline in your type constraint to declare default values instead.

If you have any experimental modules that were participating in this experiment, you will need to remove the experiment opt-in and adopt the new syntax for declaring default values in order to migrate your existing module to the stablized version of this feature. If you are writing a shared module for others to use, we recommend declaring that your module requires Terraform v1.3.0 or later to give specific feedback when using the new feature on older Terraform versions, in place of the previous declaration to use the experimental form of this feature:

```hcl
terraform {
required_version = ">= 1.3.0"
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -40,7 +40,7 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-plugin v1.4.3
github.com/hashicorp/go-retryablehttp v0.7.1
github.com/hashicorp/go-tfe v1.6.0
github.com/hashicorp/go-tfe v1.7.0
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Expand Up @@ -375,8 +375,8 @@ github.com/hashicorp/go-slug v0.9.1/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-tfe v1.6.0 h1:lRfyTVLBP1njo2wShE9FimALzVZBfOqMGNuBdsor38w=
github.com/hashicorp/go-tfe v1.6.0/go.mod h1:E8a90lC4kjU5Lc2c0D+SnWhUuyuoCIVm4Ewzv3jCD3A=
github.com/hashicorp/go-tfe v1.7.0 h1:GELRhS5dizF6giwjZBqUC/xPaSuNYB+hWRtUnf6i8K8=
github.com/hashicorp/go-tfe v1.7.0/go.mod h1:E8a90lC4kjU5Lc2c0D+SnWhUuyuoCIVm4Ewzv3jCD3A=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
Expand Down
11 changes: 11 additions & 0 deletions internal/cloud/backend_common.go
Expand Up @@ -199,6 +199,17 @@ func (b *Cloud) waitForRun(stopCtx, cancelCtx context.Context, op *backend.Opera
}
}

func (b *Cloud) waitTaskStage(stopCtx, cancelCtx context.Context, op *backend.Operation, r *tfe.Run, stageID string, outputTitle string) error {
integration := &IntegrationContext{
B: b,
StopContext: stopCtx,
CancelContext: cancelCtx,
Op: op,
Run: r,
}
return b.runTasks(integration, integration.BeginOutput(outputTitle), stageID)
}

func (b *Cloud) costEstimate(stopCtx, cancelCtx context.Context, op *backend.Operation, r *tfe.Run) error {
if r.CostEstimate == nil {
return nil
Expand Down
51 changes: 26 additions & 25 deletions internal/cloud/backend_plan.go
Expand Up @@ -291,6 +291,28 @@ in order to capture the filesystem context the remote workspace expects:
runHeader, b.hostname, b.organization, op.Workspace, r.ID)) + "\n"))
}

// Retrieve the run to get task stages.
// Task Stages are calculated upfront so we only need to call this once for the run.
taskStages := make([]*tfe.TaskStage, 0)
result, err := b.client.Runs.ReadWithOptions(stopCtx, r.ID, &tfe.RunReadOptions{
Include: []tfe.RunIncludeOpt{tfe.RunTaskStages},
})
if err == nil {
taskStages = result.TaskStages
} else {
// This error would be expected for older versions of TFE that do not allow
// fetching task_stages.
if !strings.HasSuffix(err.Error(), "Invalid include parameter") {
return r, generalError("Failed to retrieve run", err)
}
}

if stageID := getTaskStageIDByName(taskStages, tfe.PrePlan); stageID != nil {
if err := b.waitTaskStage(stopCtx, cancelCtx, op, r, *stageID, "Pre-plan Tasks"); err != nil {
return r, err
}
}

r, err = b.waitForRun(stopCtx, cancelCtx, op, "plan", r, w)
if err != nil {
return r, err
Expand Down Expand Up @@ -324,20 +346,9 @@ in order to capture the filesystem context the remote workspace expects:
}

// Retrieve the run to get its current status.
runID := r.ID
r, err = b.client.Runs.ReadWithOptions(stopCtx, runID, &tfe.RunReadOptions{
Include: []tfe.RunIncludeOpt{tfe.RunTaskStages},
})
r, err = b.client.Runs.Read(stopCtx, r.ID)
if err != nil {
// This error would be expected for older versions of TFE that do not allow
// fetching task_stages.
if strings.HasSuffix(err.Error(), "Invalid include parameter") {
r, err = b.client.Runs.Read(stopCtx, runID)
}

if err != nil {
return r, generalError("Failed to retrieve run", err)
}
return r, generalError("Failed to retrieve run", err)
}

// If the run is canceled or errored, we still continue to the
Expand All @@ -346,18 +357,8 @@ in order to capture the filesystem context the remote workspace expects:
// status of the run will be "errored", but there is still policy
// information which should be shown.

// Await post-plan run tasks
integration := &IntegrationContext{
B: b,
StopContext: stopCtx,
CancelContext: cancelCtx,
Op: op,
Run: r,
}

if stageID := getTaskStageIDByName(r.TaskStages, tfe.PostPlan); stageID != nil {
err = b.runTasks(integration, integration.BeginOutput("Run Tasks (post-plan)"), *stageID)
if err != nil {
if stageID := getTaskStageIDByName(taskStages, tfe.PostPlan); stageID != nil {
if err := b.waitTaskStage(stopCtx, cancelCtx, op, r, *stageID, "Post-plan Tasks"); err != nil {
return r, err
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/cloud/backend_runTasks.go
Expand Up @@ -52,7 +52,7 @@ func (b *Cloud) runTasksWithTaskResults(context *IntegrationContext, output Inte
stage, err := fetchTaskStage(b, context.StopContext)

if err != nil {
return false, generalError("Failed to retrieve pre-apply task stage", err)
return false, generalError("Failed to retrieve task stage", err)
}

summary := summarizeTaskResults(stage.TaskResults)
Expand Down