mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-01 11:47:07 -06:00
4ecb07bf8f
Signed-off-by: Marcin Białoń <mbialon@spacelift.io>
218 lines
7.6 KiB
Go
218 lines
7.6 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package cloud
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
tfe "github.com/hashicorp/go-tfe"
|
|
"github.com/placeholderplaceholderplaceholder/opentf/internal/plans"
|
|
)
|
|
|
|
// A brief discourse on the theory of testing for this feature. Doing
|
|
// `terraform show cloudplan.tfplan` relies on the correctness of the following
|
|
// behaviors:
|
|
//
|
|
// 1. TFC API returns redacted or unredacted plan JSON on request, if permission
|
|
// requirements are met and the run is in a condition where that JSON exists.
|
|
// 2. Cloud.ShowPlanForRun() makes correct API calls, calculates metadata
|
|
// properly given a tfe.Run, and returns either a cloudplan.RemotePlanJSON or an err.
|
|
// 3. The Show command instantiates Cloud backend when given a cloud planfile,
|
|
// calls .ShowPlanForRun() on it, and passes result to Display() impls.
|
|
// 4. Display() impls yield the correct output when given a cloud plan json biscuit.
|
|
//
|
|
// 1 is axiomatic and outside our domain. 3 is regrettably totally untestable
|
|
// unless we refactor the Meta command to enable stubbing out a backend factory
|
|
// or something, which seems inadvisable at this juncture. 4 is exercised over
|
|
// in internal/command/views/show_test.go. And thus, this file only cares about
|
|
// item 2.
|
|
|
|
// 404 on run: special error message
|
|
func TestCloud_showMissingRun(t *testing.T) {
|
|
b, bCleanup := testBackendWithName(t)
|
|
defer bCleanup()
|
|
mockSROWorkspace(t, b, testBackendSingleWorkspaceName)
|
|
|
|
absentRunID := "run-WwwwXxxxYyyyZzzz"
|
|
_, err := b.ShowPlanForRun(context.Background(), absentRunID, "app.terraform.io", true)
|
|
if !strings.Contains(err.Error(), "opentf login") {
|
|
t.Fatalf("expected error message to suggest checking your login status, instead got: %s", err)
|
|
}
|
|
}
|
|
|
|
// If redacted json is available but unredacted is not
|
|
func TestCloud_showMissingUnredactedJson(t *testing.T) {
|
|
b, mc, bCleanup := testBackendAndMocksWithName(t)
|
|
defer bCleanup()
|
|
mockSROWorkspace(t, b, testBackendSingleWorkspaceName)
|
|
|
|
ctx := context.Background()
|
|
|
|
runID, err := testCloudRunForShow(mc, "./testdata/plan-json-basic-no-unredacted", tfe.RunPlannedAndSaved, tfe.PlanFinished)
|
|
if err != nil {
|
|
t.Fatalf("failed to init test data: %s", err)
|
|
}
|
|
// Showing the human-formatted plan should still work as expected!
|
|
redacted, err := b.ShowPlanForRun(ctx, runID, "app.terraform.io", true)
|
|
if err != nil {
|
|
t.Fatalf("failed to show plan for human, even though redacted json should be present: %s", err)
|
|
}
|
|
if !strings.Contains(string(redacted.JSONBytes), `"plan_format_version":`) {
|
|
t.Fatalf("show for human doesn't include expected redacted json content")
|
|
}
|
|
// Should be marked as containing changes and non-errored
|
|
canNotApply := false
|
|
errored := false
|
|
for _, opt := range redacted.Qualities {
|
|
if opt == plans.NoChanges {
|
|
canNotApply = true
|
|
}
|
|
if opt == plans.Errored {
|
|
errored = true
|
|
}
|
|
}
|
|
if canNotApply || errored {
|
|
t.Fatalf("expected neither errored nor can't-apply in opts, instead got: %#v", redacted.Qualities)
|
|
}
|
|
|
|
// But show -json should result in a special error.
|
|
_, err = b.ShowPlanForRun(ctx, runID, "app.terraform.io", false)
|
|
if err == nil {
|
|
t.Fatalf("unexpected success: reading unredacted json without admin permissions should have errored")
|
|
}
|
|
if !strings.Contains(err.Error(), "admin") {
|
|
t.Fatalf("expected error message to suggest your permissions are wrong, instead got: %s", err)
|
|
}
|
|
}
|
|
|
|
// If both kinds of json are available, both kinds of show should work
|
|
func TestCloud_showIncludesUnredactedJson(t *testing.T) {
|
|
b, mc, bCleanup := testBackendAndMocksWithName(t)
|
|
defer bCleanup()
|
|
mockSROWorkspace(t, b, testBackendSingleWorkspaceName)
|
|
|
|
ctx := context.Background()
|
|
|
|
runID, err := testCloudRunForShow(mc, "./testdata/plan-json-basic", tfe.RunPlannedAndSaved, tfe.PlanFinished)
|
|
if err != nil {
|
|
t.Fatalf("failed to init test data: %s", err)
|
|
}
|
|
// Showing the human-formatted plan should work as expected:
|
|
redacted, err := b.ShowPlanForRun(ctx, runID, "app.terraform.io", true)
|
|
if err != nil {
|
|
t.Fatalf("failed to show plan for human, even though redacted json should be present: %s", err)
|
|
}
|
|
if !strings.Contains(string(redacted.JSONBytes), `"plan_format_version":`) {
|
|
t.Fatalf("show for human doesn't include expected redacted json content")
|
|
}
|
|
// Showing the external json plan format should work as expected:
|
|
unredacted, err := b.ShowPlanForRun(ctx, runID, "app.terraform.io", false)
|
|
if err != nil {
|
|
t.Fatalf("failed to show plan for robot, even though unredacted json should be present: %s", err)
|
|
}
|
|
if !strings.Contains(string(unredacted.JSONBytes), `"format_version":`) {
|
|
t.Fatalf("show for robot doesn't include expected unredacted json content")
|
|
}
|
|
}
|
|
|
|
func TestCloud_showNoChanges(t *testing.T) {
|
|
b, mc, bCleanup := testBackendAndMocksWithName(t)
|
|
defer bCleanup()
|
|
mockSROWorkspace(t, b, testBackendSingleWorkspaceName)
|
|
|
|
ctx := context.Background()
|
|
|
|
runID, err := testCloudRunForShow(mc, "./testdata/plan-json-no-changes", tfe.RunPlannedAndSaved, tfe.PlanFinished)
|
|
if err != nil {
|
|
t.Fatalf("failed to init test data: %s", err)
|
|
}
|
|
// Showing the human-formatted plan should work as expected:
|
|
redacted, err := b.ShowPlanForRun(ctx, runID, "app.terraform.io", true)
|
|
if err != nil {
|
|
t.Fatalf("failed to show plan for human, even though redacted json should be present: %s", err)
|
|
}
|
|
// Should be marked as no changes
|
|
canNotApply := false
|
|
for _, opt := range redacted.Qualities {
|
|
if opt == plans.NoChanges {
|
|
canNotApply = true
|
|
}
|
|
}
|
|
if !canNotApply {
|
|
t.Fatalf("expected opts to include CanNotApply, instead got: %#v", redacted.Qualities)
|
|
}
|
|
}
|
|
|
|
func TestCloud_showFooterNotConfirmable(t *testing.T) {
|
|
b, mc, bCleanup := testBackendAndMocksWithName(t)
|
|
defer bCleanup()
|
|
mockSROWorkspace(t, b, testBackendSingleWorkspaceName)
|
|
|
|
ctx := context.Background()
|
|
|
|
runID, err := testCloudRunForShow(mc, "./testdata/plan-json-full", tfe.RunDiscarded, tfe.PlanFinished)
|
|
if err != nil {
|
|
t.Fatalf("failed to init test data: %s", err)
|
|
}
|
|
|
|
// A little more custom run tweaking:
|
|
mc.Runs.Runs[runID].Actions.IsConfirmable = false
|
|
|
|
// Showing the human-formatted plan should work as expected:
|
|
redacted, err := b.ShowPlanForRun(ctx, runID, "app.terraform.io", true)
|
|
if err != nil {
|
|
t.Fatalf("failed to show plan for human, even though redacted json should be present: %s", err)
|
|
}
|
|
|
|
// Footer should mention that you can't apply it:
|
|
if !strings.Contains(redacted.RunFooter, "not confirmable") {
|
|
t.Fatalf("footer should call out that run isn't confirmable, instead got: %s", redacted.RunFooter)
|
|
}
|
|
}
|
|
|
|
func testCloudRunForShow(mc *MockClient, configDir string, runStatus tfe.RunStatus, planStatus tfe.PlanStatus) (string, error) {
|
|
ctx := context.Background()
|
|
|
|
// get workspace ID
|
|
wsID := mc.Workspaces.workspaceNames[testBackendSingleWorkspaceName].ID
|
|
// create and upload config version
|
|
cvOpts := tfe.ConfigurationVersionCreateOptions{
|
|
AutoQueueRuns: tfe.Bool(false),
|
|
Speculative: tfe.Bool(false),
|
|
}
|
|
cv, err := mc.ConfigurationVersions.Create(ctx, wsID, cvOpts)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
absDir, err := filepath.Abs(configDir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
err = mc.ConfigurationVersions.Upload(ctx, cv.UploadURL, absDir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// create run
|
|
rOpts := tfe.RunCreateOptions{
|
|
PlanOnly: tfe.Bool(false),
|
|
IsDestroy: tfe.Bool(false),
|
|
RefreshOnly: tfe.Bool(false),
|
|
ConfigurationVersion: cv,
|
|
Workspace: &tfe.Workspace{ID: wsID},
|
|
}
|
|
r, err := mc.Runs.Create(ctx, rOpts)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// mess with statuses (this is what requires full access to mock client)
|
|
mc.Runs.Runs[r.ID].Status = runStatus
|
|
mc.Plans.plans[r.Plan.ID].Status = planStatus
|
|
|
|
// return the ID
|
|
return r.ID, nil
|
|
}
|