Commit 16f1f3b7 authored by Martin Atkins's avatar Martin Atkins
Browse files

backend/remote: Support -target on plan and apply

Previously we did not allow -target to be used with the remote backend
because there was no way to send the targets to Terraform Cloud/Enterprise
via the API.

There is now an attribute in the request for creating a plan that allows
us to send target addresses, so we'll remove that restriction and copy
the given target addresses into the API request.
parent db4f3f8b
No related merge requests found
Showing with 42 additions and 30 deletions
+42 -30
......@@ -67,14 +67,6 @@ func (b *Remote) opApply(stopCtx, cancelCtx context.Context, op *backend.Operati
))
}
if op.Targets != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Resource targeting is currently not supported",
`The "remote" backend does not support resource targeting at this time.`,
))
}
if b.hasExplicitVariableValues(op) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
......
......@@ -9,6 +9,7 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/backend"
......@@ -278,16 +279,23 @@ func TestRemote_applyWithTarget(t *testing.T) {
}
<-run.Done()
if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail")
if run.Result != backend.OperationSuccess {
t.Fatal("expected apply operation to succeed")
}
if !run.PlanEmpty {
t.Fatalf("expected plan to be empty")
if run.PlanEmpty {
t.Fatalf("expected plan to be non-empty")
}
errOutput := b.CLI.(*cli.MockUi).ErrorWriter.String()
if !strings.Contains(errOutput, "targeting is currently not supported") {
t.Fatalf("expected a targeting error, got: %v", errOutput)
// We should find a run inside the mock client that has the same
// target address we requested above.
runsAPI := b.client.Runs.(*mockRuns)
if got, want := len(runsAPI.runs), 1; got != want {
t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
}
for _, run := range runsAPI.runs {
if diff := cmp.Diff([]string{"null_resource.foo"}, run.TargetAddrs); diff != "" {
t.Errorf("wrong TargetAddrs in the created run\n%s", diff)
}
}
}
......
......@@ -757,6 +757,7 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t
Permissions: &tfe.RunPermissions{},
Plan: p,
Status: tfe.RunPending,
TargetAddrs: options.TargetAddrs,
}
if pc != nil {
......
......@@ -70,14 +70,6 @@ func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operatio
))
}
if op.Targets != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Resource targeting is currently not supported",
`The "remote" backend does not support resource targeting at this time.`,
))
}
if b.hasExplicitVariableValues(op) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
......@@ -224,6 +216,17 @@ in order to capture the filesystem context the remote workspace expects:
Workspace: w,
}
if len(op.Targets) != 0 {
runOptions.TargetAddrs = make([]string, 0, len(op.Targets))
for _, addr := range op.Targets {
// The API client wants the normal string representation of a
// target address, which will ultimately get inserted into a
// -target option when Terraform CLI is launched in the
// Cloud/Enterprise execution environment.
runOptions.TargetAddrs = append(runOptions.TargetAddrs, addr.String())
}
}
r, err := b.client.Runs.Create(stopCtx, runOptions)
if err != nil {
return r, generalError("Failed to create run", err)
......
......@@ -9,6 +9,7 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/backend"
......@@ -283,16 +284,23 @@ func TestRemote_planWithTarget(t *testing.T) {
}
<-run.Done()
if run.Result == backend.OperationSuccess {
t.Fatal("expected plan operation to fail")
if run.Result != backend.OperationSuccess {
t.Fatal("expected plan operation to succeed")
}
if !run.PlanEmpty {
t.Fatalf("expected plan to be empty")
if run.PlanEmpty {
t.Fatalf("expected plan to be non-empty")
}
errOutput := b.CLI.(*cli.MockUi).ErrorWriter.String()
if !strings.Contains(errOutput, "targeting is currently not supported") {
t.Fatalf("expected a targeting error, got: %v", errOutput)
// We should find a run inside the mock client that has the same
// target address we requested above.
runsAPI := b.client.Runs.(*mockRuns)
if got, want := len(runsAPI.runs), 1; got != want {
t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
}
for _, run := range runsAPI.runs {
if diff := cmp.Diff([]string{"null_resource.foo"}, run.TargetAddrs); diff != "" {
t.Errorf("wrong TargetAddrs in the created run\n%s", diff)
}
}
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment