backend/remote: Handle cost estimation skipped due to targeting

The remote server might choose to skip running cost estimation for a
targeted plan, in which case we'll show a note about it in the UI and then
move on, rather than returning an "invalid status" error.

This new status isn't yet available in the go-tfe library as a constant,
so for now we have the string directly in our switch statement. This is
a pragmatic way to expedite getting the "critical path" of this feature
in place without blocking on changes to ancillary codebases. A subsequent
commit should switch this over to tfe.CostEstimateSkippedDueToTargeting
once that's available in a go-tfe release.
This commit is contained in:
Martin Atkins 2020-05-18 15:12:44 -07:00
parent 0eea4e7c62
commit 8e1615a802
3 changed files with 46 additions and 1 deletions

View File

@ -316,6 +316,10 @@ func (b *Remote) costEstimate(stopCtx, cancelCtx context.Context, op *backend.Op
b.CLI.Output(b.Colorize().Color("Waiting for cost estimate to complete..." + elapsed + "\n")) b.CLI.Output(b.Colorize().Color("Waiting for cost estimate to complete..." + elapsed + "\n"))
} }
continue continue
case "skipped_due_to_targeting": // TEMP: not available in the go-tfe library yet; will update this to be tfe.CostEstimateSkippedDueToTargeting once that's available.
b.CLI.Output("Not available for this plan, because it was created with the -target option.")
b.CLI.Output("\n------------------------------------------------------------------------")
return nil
case tfe.CostEstimateErrored: case tfe.CostEstimateErrored:
return fmt.Errorf(msgPrefix + " errored.") return fmt.Errorf(msgPrefix + " errored.")
case tfe.CostEstimateCanceled: case tfe.CostEstimateCanceled:

View File

@ -696,6 +696,11 @@ type mockRuns struct {
client *mockClient client *mockClient
runs map[string]*tfe.Run runs map[string]*tfe.Run
workspaces map[string][]*tfe.Run workspaces map[string][]*tfe.Run
// If modifyNewRun is non-nil, the create method will call it just before
// saving a new run in the runs map, so that a calling test can mimic
// side-effects that a real server might apply in certain situations.
modifyNewRun func(client *mockClient, options tfe.RunCreateOptions, run *tfe.Run)
} }
func newMockRuns(client *mockClient) *mockRuns { func newMockRuns(client *mockClient) *mockRuns {
@ -780,6 +785,12 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t
w.CurrentRun = r w.CurrentRun = r
} }
if m.modifyNewRun != nil {
// caller-provided callback may modify the run in-place to mimic
// side-effects that a real server might take in some situations.
m.modifyNewRun(m.client, options, r)
}
m.runs[r.ID] = r m.runs[r.ID] = r
m.workspaces[options.Workspace.ID] = append(m.workspaces[options.Workspace.ID], r) m.workspaces[options.Workspace.ID] = append(m.workspaces[options.Workspace.ID], r)

View File

@ -270,6 +270,29 @@ func TestRemote_planWithTarget(t *testing.T) {
b, bCleanup := testBackendDefault(t) b, bCleanup := testBackendDefault(t)
defer bCleanup() defer bCleanup()
// When the backend code creates a new run, we'll tweak it so that it
// has a cost estimation object with the "skipped_due_to_targeting" status,
// emulating how a real server is expected to behave in that case.
b.client.Runs.(*mockRuns).modifyNewRun = func(client *mockClient, options tfe.RunCreateOptions, run *tfe.Run) {
const fakeID = "fake"
// This is the cost estimate object embedded in the run itself which
// the backend will use to learn the ID to request from the cost
// estimates endpoint. It's pending to simulate what a freshly-created
// run is likely to look like.
run.CostEstimate = &tfe.CostEstimate{
ID: fakeID,
Status: "pending",
}
// The backend will then use the main cost estimation API to retrieve
// the same ID indicated in the object above, where we'll then return
// the status "skipped_due_to_targeting" to trigger the special skip
// message in the backend output.
client.CostEstimates.estimations[fakeID] = &tfe.CostEstimate{
ID: fakeID,
Status: "skipped_due_to_targeting",
}
}
op, configCleanup := testOperationPlan(t, "./testdata/plan") op, configCleanup := testOperationPlan(t, "./testdata/plan")
defer configCleanup() defer configCleanup()
@ -291,6 +314,13 @@ func TestRemote_planWithTarget(t *testing.T) {
t.Fatalf("expected plan to be non-empty") t.Fatalf("expected plan to be non-empty")
} }
// testBackendDefault above attached a "mock UI" to our backend, so we
// can retrieve its non-error output via the OutputWriter in-memory buffer.
gotOutput := b.CLI.(*cli.MockUi).OutputWriter.String()
if wantOutput := "Not available for this plan, because it was created with the -target option."; !strings.Contains(gotOutput, wantOutput) {
t.Errorf("missing message about skipped cost estimation\ngot:\n%s\nwant substring: %s", gotOutput, wantOutput)
}
// We should find a run inside the mock client that has the same // We should find a run inside the mock client that has the same
// target address we requested above. // target address we requested above.
runsAPI := b.client.Runs.(*mockRuns) runsAPI := b.client.Runs.(*mockRuns)
@ -303,7 +333,7 @@ func TestRemote_planWithTarget(t *testing.T) {
} }
if !strings.Contains(run.Message, "using -target") { if !strings.Contains(run.Message, "using -target") {
t.Fatalf("incorrrect Message on the created run: %s", run.Message) t.Errorf("incorrect Message on the created run: %s", run.Message)
} }
} }
} }