From c7a023a95c75bac5db5f8b1bc90c1b06fb69bc87 Mon Sep 17 00:00:00 2001 From: Paul Thrasher Date: Wed, 24 Apr 2019 16:40:13 -0700 Subject: [PATCH] update test for new go-tfe version Signed-off-by: Paul Thrasher --- backend/remote/backend_common.go | 6 - backend/remote/backend_mock.go | 131 +++++++++++++++++- backend/remote/backend_plan_test.go | 30 ++++ .../test-fixtures/cost-estimation/ce.log | 6 + .../test-fixtures/cost-estimation/main.tf | 1 + .../test-fixtures/cost-estimation/plan.log | 21 +++ backend/remote/testing.go | 1 + 7 files changed, 183 insertions(+), 13 deletions(-) create mode 100644 backend/remote/test-fixtures/cost-estimation/ce.log create mode 100644 backend/remote/test-fixtures/cost-estimation/main.tf create mode 100644 backend/remote/test-fixtures/cost-estimation/plan.log diff --git a/backend/remote/backend_common.go b/backend/remote/backend_common.go index 91a13b763..d53b0a791 100644 --- a/backend/remote/backend_common.go +++ b/backend/remote/backend_common.go @@ -273,12 +273,6 @@ func (b *Remote) costEstimation(stopCtx, cancelCtx context.Context, op *backend. default: return fmt.Errorf("Unknown or unexpected cost estimation state: %s", ce.Status) } - - if b.CLI != nil { - b.CLI.Output("------------------------------------------------------------------------") - } - - return nil } func (b *Remote) checkPolicy(stopCtx, cancelCtx context.Context, op *backend.Operation, r *tfe.Run) error { diff --git a/backend/remote/backend_mock.go b/backend/remote/backend_mock.go index 8985dd0e3..59a1a1393 100644 --- a/backend/remote/backend_mock.go +++ b/backend/remote/backend_mock.go @@ -21,6 +21,7 @@ import ( type mockClient struct { Applies *mockApplies ConfigurationVersions *mockConfigurationVersions + CostEstimations *mockCostEstimations Organizations *mockOrganizations Plans *mockPlans PolicyChecks *mockPolicyChecks @@ -33,6 +34,7 @@ func newMockClient() *mockClient { c := &mockClient{} c.Applies = newMockApplies(c) c.ConfigurationVersions = newMockConfigurationVersions(c) + c.CostEstimations = newMockCostEstimations(c) c.Organizations = newMockOrganizations(c) c.Plans = newMockPlans(c) c.PolicyChecks = newMockPolicyChecks(c) @@ -212,6 +214,111 @@ func (m *mockConfigurationVersions) Upload(ctx context.Context, url, path string return nil } +type mockCostEstimations struct { + client *mockClient + estimations map[string]*tfe.CostEstimation + logs map[string]string +} + +func newMockCostEstimations(client *mockClient) *mockCostEstimations { + return &mockCostEstimations{ + client: client, + estimations: make(map[string]*tfe.CostEstimation), + logs: make(map[string]string), + } +} + +// create is a helper function to create a mock plan that uses the configured +// working directory to find the logfile. +func (m *mockCostEstimations) create(cvID, workspaceID string) (*tfe.CostEstimation, error) { + id := generateID("ce-") + + ce := &tfe.CostEstimation{ + ID: id, + Status: tfe.CostEstimationQueued, + } + + w, ok := m.client.Workspaces.workspaceIDs[workspaceID] + if !ok { + return nil, tfe.ErrResourceNotFound + } + + logfile := filepath.Join( + m.client.ConfigurationVersions.uploadPaths[cvID], + w.WorkingDirectory, + "ce.log", + ) + + if _, err := os.Stat(logfile); os.IsNotExist(err) { + return nil, nil + } + + m.logs[ce.ID] = logfile + m.estimations[ce.ID] = ce + + return ce, nil +} + +func (m *mockCostEstimations) Read(ctx context.Context, costEstimationID string) (*tfe.CostEstimation, error) { + ce, ok := m.estimations[costEstimationID] + if !ok { + return nil, tfe.ErrResourceNotFound + } + + logfile, ok := m.logs[ce.ID] + if !ok { + return nil, tfe.ErrResourceNotFound + } + + if _, err := os.Stat(logfile); os.IsNotExist(err) { + return nil, fmt.Errorf("logfile does not exist") + } + + logs, err := ioutil.ReadFile(logfile) + if err != nil { + return nil, err + } + + if bytes.Contains(logs, []byte("SKU")) { + ce.Status = tfe.CostEstimationFinished + } else { + // As this is an unexpected state, we say the estimation errored. + ce.Status = tfe.CostEstimationErrored + } + + return ce, nil +} + +func (m *mockCostEstimations) Logs(ctx context.Context, costEstimationID string) (io.Reader, error) { + ce, ok := m.estimations[costEstimationID] + if !ok { + return nil, tfe.ErrResourceNotFound + } + + logfile, ok := m.logs[ce.ID] + if !ok { + return nil, tfe.ErrResourceNotFound + } + + if _, err := os.Stat(logfile); os.IsNotExist(err) { + return bytes.NewBufferString("logfile does not exist"), nil + } + + logs, err := ioutil.ReadFile(logfile) + if err != nil { + return nil, err + } + + if bytes.Contains(logs, []byte("SKU")) { + ce.Status = tfe.CostEstimationFinished + } else { + // As this is an unexpected state, we say the estimation errored. + ce.Status = tfe.CostEstimationErrored + } + + return bytes.NewBuffer(logs), nil +} + // mockInput is a mock implementation of terraform.UIInput. type mockInput struct { answers map[string]string @@ -652,19 +759,25 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t return nil, err } + ce, err := m.client.CostEstimations.create(options.ConfigurationVersion.ID, options.Workspace.ID) + if err != nil { + return nil, err + } + pc, err := m.client.PolicyChecks.create(options.ConfigurationVersion.ID, options.Workspace.ID) if err != nil { return nil, err } r := &tfe.Run{ - ID: generateID("run-"), - Actions: &tfe.RunActions{IsCancelable: true}, - Apply: a, - HasChanges: false, - Permissions: &tfe.RunPermissions{}, - Plan: p, - Status: tfe.RunPending, + ID: generateID("run-"), + Actions: &tfe.RunActions{IsCancelable: true}, + Apply: a, + CostEstimation: ce, + HasChanges: false, + Permissions: &tfe.RunPermissions{}, + Plan: p, + Status: tfe.RunPending, } if pc != nil { @@ -1038,6 +1151,10 @@ func (m *mockWorkspaces) UnassignSSHKey(ctx context.Context, workspaceID string) panic("not implemented") } +func (m *mockWorkspaces) RemoveVCSConnection(ctx context.Context, organizationID string, workspaceID string) (*tfe.Workspace, error) { + panic("not implemented") +} + const alphanumeric = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" func generateID(s string) string { diff --git a/backend/remote/backend_plan_test.go b/backend/remote/backend_plan_test.go index 389444200..d89ef8685 100644 --- a/backend/remote/backend_plan_test.go +++ b/backend/remote/backend_plan_test.go @@ -655,6 +655,36 @@ func TestRemote_planWithWorkingDirectory(t *testing.T) { } } +func TestRemote_costEstimationFinish(t *testing.T) { + b := testBackendDefault(t) + + op, configCleanup := testOperationPlan(t, "./test-fixtures/cost-estimation") + defer configCleanup() + + op.Workspace = backend.DefaultStateName + + 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, "Running plan in the remote backend") { + t.Fatalf("expected remote backend header in output: %s", output) + } + if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { + t.Fatalf("expected plan summary in output: %s", output) + } + if !strings.Contains(output, "SKU") { + t.Fatalf("expected cost estimation result in output: %s", output) + } +} + func TestRemote_planPolicyPass(t *testing.T) { b, bCleanup := testBackendDefault(t) defer bCleanup() diff --git a/backend/remote/test-fixtures/cost-estimation/ce.log b/backend/remote/test-fixtures/cost-estimation/ce.log new file mode 100644 index 000000000..d73c8915b --- /dev/null +++ b/backend/remote/test-fixtures/cost-estimation/ce.log @@ -0,0 +1,6 @@ ++---------+------+-----+-------------+----------------------+ +| PRODUCT | NAME | SKU | DESCRIPTION | DELTA | ++---------+------+-----+-------------+----------------------+ ++---------+------+-----+-------------+----------------------+ +| TOTAL | $0.000 USD / 720 HRS | ++---------+------+-----+-------------+----------------------+ \ No newline at end of file diff --git a/backend/remote/test-fixtures/cost-estimation/main.tf b/backend/remote/test-fixtures/cost-estimation/main.tf new file mode 100644 index 000000000..3911a2a9b --- /dev/null +++ b/backend/remote/test-fixtures/cost-estimation/main.tf @@ -0,0 +1 @@ +resource "null_resource" "foo" {} diff --git a/backend/remote/test-fixtures/cost-estimation/plan.log b/backend/remote/test-fixtures/cost-estimation/plan.log new file mode 100644 index 000000000..5849e5759 --- /dev/null +++ b/backend/remote/test-fixtures/cost-estimation/plan.log @@ -0,0 +1,21 @@ +Terraform v0.11.7 + +Configuring remote state backend... +Initializing Terraform configuration... +Refreshing Terraform state in-memory prior to plan... +The refreshed state will be used to calculate this plan, but will not be +persisted to local or remote state storage. + +------------------------------------------------------------------------ + +An execution plan has been generated and is shown below. +Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + + + null_resource.foo + id: + + +Plan: 1 to add, 0 to change, 0 to destroy. diff --git a/backend/remote/testing.go b/backend/remote/testing.go index 09c541897..2213ba18c 100644 --- a/backend/remote/testing.go +++ b/backend/remote/testing.go @@ -115,6 +115,7 @@ func testBackend(t *testing.T, obj cty.Value) (*Remote, func()) { b.CLI = cli.NewMockUi() b.client.Applies = mc.Applies b.client.ConfigurationVersions = mc.ConfigurationVersions + b.client.CostEstimations = mc.CostEstimations b.client.Organizations = mc.Organizations b.client.Plans = mc.Plans b.client.PolicyChecks = mc.PolicyChecks