Run apply -refresh-state instead of refresh
When a user runs `terraform refresh` we give them an error message that tells them to run `terraform apply -refresh-state`. We could just run that command for them, though. That is what this PR does.
This commit is contained in:
parent
bf02b5cb53
commit
edbc84420c
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/hashicorp/terraform-svchost/disco"
|
"github.com/hashicorp/terraform-svchost/disco"
|
||||||
"github.com/hashicorp/terraform/internal/backend"
|
"github.com/hashicorp/terraform/internal/backend"
|
||||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||||
|
"github.com/hashicorp/terraform/internal/plans"
|
||||||
"github.com/hashicorp/terraform/internal/states/remote"
|
"github.com/hashicorp/terraform/internal/states/remote"
|
||||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||||
"github.com/hashicorp/terraform/internal/terraform"
|
"github.com/hashicorp/terraform/internal/terraform"
|
||||||
|
@ -643,9 +644,16 @@ func (b *Cloud) Operation(ctx context.Context, op *backend.Operation) (*backend.
|
||||||
case backend.OperationTypeApply:
|
case backend.OperationTypeApply:
|
||||||
f = b.opApply
|
f = b.opApply
|
||||||
case backend.OperationTypeRefresh:
|
case backend.OperationTypeRefresh:
|
||||||
return nil, fmt.Errorf(
|
// The `terraform refresh` command has been deprecated in favor of `terraform apply -refresh-state`.
|
||||||
"\n\nThe \"refresh\" operation is not supported when using Terraform Cloud. " +
|
// Rather than respond with an error telling the user to run the other command we can just run
|
||||||
"Use \"terraform apply -refresh-only\" instead.")
|
// that command instead. We will tell the user what we are doing, and then do it.
|
||||||
|
if b.CLI != nil {
|
||||||
|
b.CLI.Output(b.Colorize().Color(strings.TrimSpace(refreshToApplyRefresh) + "\n"))
|
||||||
|
}
|
||||||
|
op.PlanMode = plans.RefreshOnlyMode
|
||||||
|
op.PlanRefresh = true
|
||||||
|
op.AutoApprove = true
|
||||||
|
f = b.opApply
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"\n\nTerraform Cloud does not support the %q operation.", op.Type)
|
"\n\nTerraform Cloud does not support the %q operation.", op.Type)
|
||||||
|
@ -982,6 +990,8 @@ const operationNotCanceled = `
|
||||||
[reset][red]The remote operation was not cancelled.[reset]
|
[reset][red]The remote operation was not cancelled.[reset]
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const refreshToApplyRefresh = `[bold][yellow]Proceeding with 'terraform apply -refresh-only -auto-approve'.[reset]`
|
||||||
|
|
||||||
var (
|
var (
|
||||||
workspaceConfigurationHelp = fmt.Sprintf(
|
workspaceConfigurationHelp = fmt.Sprintf(
|
||||||
`The 'workspaces' block configures how Terraform CLI maps its workspaces for this single
|
`The 'workspaces' block configures how Terraform CLI maps its workspaces for this single
|
||||||
|
|
|
@ -87,7 +87,7 @@ func (b *Cloud) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operation
|
||||||
func (b *Cloud) plan(stopCtx, cancelCtx context.Context, op *backend.Operation, w *tfe.Workspace) (*tfe.Run, error) {
|
func (b *Cloud) plan(stopCtx, cancelCtx context.Context, op *backend.Operation, w *tfe.Workspace) (*tfe.Run, error) {
|
||||||
if b.CLI != nil {
|
if b.CLI != nil {
|
||||||
header := planDefaultHeader
|
header := planDefaultHeader
|
||||||
if op.Type == backend.OperationTypeApply {
|
if op.Type == backend.OperationTypeApply || op.Type == backend.OperationTypeRefresh {
|
||||||
header = applyDefaultHeader
|
header = applyDefaultHeader
|
||||||
}
|
}
|
||||||
b.CLI.Output(b.Colorize().Color(strings.TrimSpace(header) + "\n"))
|
b.CLI.Output(b.Colorize().Color(strings.TrimSpace(header) + "\n"))
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
package cloud
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/internal/backend"
|
||||||
|
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||||
|
"github.com/hashicorp/terraform/internal/command/clistate"
|
||||||
|
"github.com/hashicorp/terraform/internal/command/views"
|
||||||
|
"github.com/hashicorp/terraform/internal/initwd"
|
||||||
|
"github.com/hashicorp/terraform/internal/plans"
|
||||||
|
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||||
|
"github.com/hashicorp/terraform/internal/terminal"
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testOperationRefresh(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return testOperationRefreshWithTimeout(t, configDir, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testOperationRefreshWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
|
||||||
|
|
||||||
|
streams, done := terminal.StreamsForTesting(t)
|
||||||
|
view := views.NewView(streams)
|
||||||
|
stateLockerView := views.NewStateLocker(arguments.ViewHuman, view)
|
||||||
|
operationView := views.NewOperation(arguments.ViewHuman, false, view)
|
||||||
|
|
||||||
|
return &backend.Operation{
|
||||||
|
ConfigDir: configDir,
|
||||||
|
ConfigLoader: configLoader,
|
||||||
|
PlanRefresh: true,
|
||||||
|
StateLocker: clistate.NewLocker(timeout, stateLockerView),
|
||||||
|
Type: backend.OperationTypeRefresh,
|
||||||
|
View: operationView,
|
||||||
|
}, configCleanup, done
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloud_refreshBasicActuallyRunsApplyRefresh(t *testing.T) {
|
||||||
|
b, bCleanup := testBackendWithName(t)
|
||||||
|
defer bCleanup()
|
||||||
|
|
||||||
|
op, configCleanup, done := testOperationRefresh(t, "./testdata/refresh")
|
||||||
|
defer configCleanup()
|
||||||
|
defer done(t)
|
||||||
|
|
||||||
|
op.UIOut = b.CLI
|
||||||
|
b.CLIColor = b.cliColorize()
|
||||||
|
op.PlanMode = plans.RefreshOnlyMode
|
||||||
|
op.Workspace = testBackendSingleWorkspaceName
|
||||||
|
|
||||||
|
run, err := b.Operation(context.Background(), op)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error starting operation: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-run.Done()
|
||||||
|
if run.Result != backend.OperationSuccess {
|
||||||
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
||||||
|
if !strings.Contains(output, "Proceeding with 'terraform apply -refresh-only -auto-approve'") {
|
||||||
|
t.Fatalf("expected TFC header in output: %s", output)
|
||||||
|
}
|
||||||
|
|
||||||
|
stateMgr, _ := b.StateMgr(testBackendSingleWorkspaceName)
|
||||||
|
// An error suggests that the state was not unlocked after apply
|
||||||
|
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
|
||||||
|
t.Fatalf("unexpected error locking state after apply: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
resource "random_pet" "always_new" {
|
||||||
|
keepers = {
|
||||||
|
uuid = uuid() # Force a new name each time
|
||||||
|
}
|
||||||
|
length = 3
|
||||||
|
}
|
|
@ -17,10 +17,17 @@ type RefreshCommand struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RefreshCommand) Run(rawArgs []string) int {
|
func (c *RefreshCommand) Run(rawArgs []string) int {
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
// Parse and apply global view arguments
|
// Parse and apply global view arguments
|
||||||
common, rawArgs := arguments.ParseView(rawArgs)
|
common, rawArgs := arguments.ParseView(rawArgs)
|
||||||
c.View.Configure(common)
|
c.View.Configure(common)
|
||||||
|
|
||||||
|
// Propagate -no-color for the remote backend's legacy use of Ui. This
|
||||||
|
// should be removed when the remote backend is migrated to views.
|
||||||
|
c.Meta.color = !common.NoColor
|
||||||
|
c.Meta.Color = c.Meta.color
|
||||||
|
|
||||||
// Parse and validate flags
|
// Parse and validate flags
|
||||||
args, diags := arguments.ParseRefresh(rawArgs)
|
args, diags := arguments.ParseRefresh(rawArgs)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue