github.com/pulumi/terraform@v1.4.0/pkg/cloud/state_test.go (about)

     1  package cloud
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"io/ioutil"
     7  	"testing"
     8  
     9  	tfe "github.com/hashicorp/go-tfe"
    10  	"github.com/pulumi/terraform/pkg/states/statefile"
    11  	"github.com/pulumi/terraform/pkg/states/statemgr"
    12  )
    13  
    14  func TestState_impl(t *testing.T) {
    15  	var _ statemgr.Reader = new(State)
    16  	var _ statemgr.Writer = new(State)
    17  	var _ statemgr.Persister = new(State)
    18  	var _ statemgr.Refresher = new(State)
    19  	var _ statemgr.OutputReader = new(State)
    20  	var _ statemgr.Locker = new(State)
    21  }
    22  
    23  type ExpectedOutput struct {
    24  	Name      string
    25  	Sensitive bool
    26  	IsNull    bool
    27  }
    28  
    29  func TestState_GetRootOutputValues(t *testing.T) {
    30  	b, bCleanup := testBackendWithOutputs(t)
    31  	defer bCleanup()
    32  
    33  	state := &State{tfeClient: b.client, organization: b.organization, workspace: &tfe.Workspace{
    34  		ID: "ws-abcd",
    35  	}}
    36  	outputs, err := state.GetRootOutputValues()
    37  
    38  	if err != nil {
    39  		t.Fatalf("error returned from GetRootOutputValues: %s", err)
    40  	}
    41  
    42  	cases := []ExpectedOutput{
    43  		{
    44  			Name:      "sensitive_output",
    45  			Sensitive: true,
    46  			IsNull:    false,
    47  		},
    48  		{
    49  			Name:      "nonsensitive_output",
    50  			Sensitive: false,
    51  			IsNull:    false,
    52  		},
    53  		{
    54  			Name:      "object_output",
    55  			Sensitive: false,
    56  			IsNull:    false,
    57  		},
    58  		{
    59  			Name:      "list_output",
    60  			Sensitive: false,
    61  			IsNull:    false,
    62  		},
    63  	}
    64  
    65  	if len(outputs) != len(cases) {
    66  		t.Errorf("Expected %d item but %d were returned", len(cases), len(outputs))
    67  	}
    68  
    69  	for _, testCase := range cases {
    70  		so, ok := outputs[testCase.Name]
    71  		if !ok {
    72  			t.Fatalf("Expected key %s but it was not found", testCase.Name)
    73  		}
    74  		if so.Value.IsNull() != testCase.IsNull {
    75  			t.Errorf("Key %s does not match null expectation %v", testCase.Name, testCase.IsNull)
    76  		}
    77  		if so.Sensitive != testCase.Sensitive {
    78  			t.Errorf("Key %s does not match sensitive expectation %v", testCase.Name, testCase.Sensitive)
    79  		}
    80  	}
    81  }
    82  
    83  func TestState(t *testing.T) {
    84  	var buf bytes.Buffer
    85  	s := statemgr.TestFullInitialState()
    86  	sf := statefile.New(s, "stub-lineage", 2)
    87  	err := statefile.Write(sf, &buf)
    88  	if err != nil {
    89  		t.Fatalf("err: %s", err)
    90  	}
    91  	data := buf.Bytes()
    92  
    93  	state := testCloudState(t)
    94  
    95  	jsonState, err := ioutil.ReadFile("../command/testdata/show-json-state/sensitive-variables/output.json")
    96  	if err != nil {
    97  		t.Fatal(err)
    98  	}
    99  
   100  	jsonStateOutputs := []byte(`
   101  {
   102  	"outputs": {
   103  			"foo": {
   104  					"type": "string",
   105  					"value": "bar"
   106  			}
   107  	}
   108  }`)
   109  
   110  	if err := state.uploadState(state.lineage, state.serial, state.forcePush, data, jsonState, jsonStateOutputs); err != nil {
   111  		t.Fatalf("put: %s", err)
   112  	}
   113  
   114  	payload, err := state.getStatePayload()
   115  	if err != nil {
   116  		t.Fatalf("get: %s", err)
   117  	}
   118  	if !bytes.Equal(payload.Data, data) {
   119  		t.Fatalf("expected full state %q\n\ngot: %q", string(payload.Data), string(data))
   120  	}
   121  
   122  	if err := state.Delete(true); err != nil {
   123  		t.Fatalf("delete: %s", err)
   124  	}
   125  
   126  	p, err := state.getStatePayload()
   127  	if err != nil {
   128  		t.Fatalf("get: %s", err)
   129  	}
   130  	if p != nil {
   131  		t.Fatalf("expected empty state, got: %q", string(p.Data))
   132  	}
   133  }
   134  
   135  func TestCloudLocks(t *testing.T) {
   136  	back, bCleanup := testBackendWithName(t)
   137  	defer bCleanup()
   138  
   139  	a, err := back.StateMgr(testBackendSingleWorkspaceName)
   140  	if err != nil {
   141  		t.Fatalf("expected no error, got %v", err)
   142  	}
   143  	b, err := back.StateMgr(testBackendSingleWorkspaceName)
   144  	if err != nil {
   145  		t.Fatalf("expected no error, got %v", err)
   146  	}
   147  
   148  	lockerA, ok := a.(statemgr.Locker)
   149  	if !ok {
   150  		t.Fatal("client A not a statemgr.Locker")
   151  	}
   152  
   153  	lockerB, ok := b.(statemgr.Locker)
   154  	if !ok {
   155  		t.Fatal("client B not a statemgr.Locker")
   156  	}
   157  
   158  	infoA := statemgr.NewLockInfo()
   159  	infoA.Operation = "test"
   160  	infoA.Who = "clientA"
   161  
   162  	infoB := statemgr.NewLockInfo()
   163  	infoB.Operation = "test"
   164  	infoB.Who = "clientB"
   165  
   166  	lockIDA, err := lockerA.Lock(infoA)
   167  	if err != nil {
   168  		t.Fatal("unable to get initial lock:", err)
   169  	}
   170  
   171  	_, err = lockerB.Lock(infoB)
   172  	if err == nil {
   173  		lockerA.Unlock(lockIDA)
   174  		t.Fatal("client B obtained lock while held by client A")
   175  	}
   176  	if _, ok := err.(*statemgr.LockError); !ok {
   177  		t.Errorf("expected a LockError, but was %t: %s", err, err)
   178  	}
   179  
   180  	if err := lockerA.Unlock(lockIDA); err != nil {
   181  		t.Fatal("error unlocking client A", err)
   182  	}
   183  
   184  	lockIDB, err := lockerB.Lock(infoB)
   185  	if err != nil {
   186  		t.Fatal("unable to obtain lock from client B")
   187  	}
   188  
   189  	if lockIDB == lockIDA {
   190  		t.Fatalf("duplicate lock IDs: %q", lockIDB)
   191  	}
   192  
   193  	if err = lockerB.Unlock(lockIDB); err != nil {
   194  		t.Fatal("error unlocking client B:", err)
   195  	}
   196  }
   197  
   198  func TestDelete_SafeDeleteNotSupported(t *testing.T) {
   199  	state := testCloudState(t)
   200  	workspaceId := state.workspace.ID
   201  	state.workspace.Permissions.CanForceDelete = nil
   202  	state.workspace.ResourceCount = 5
   203  
   204  	// Typically delete(false) should safe-delete a cloud workspace, which should fail on this workspace with resources
   205  	// However, since we have set the workspace canForceDelete permission to nil, we should fall back to force delete
   206  	if err := state.Delete(false); err != nil {
   207  		t.Fatalf("delete: %s", err)
   208  	}
   209  	workspace, err := state.tfeClient.Workspaces.ReadByID(context.Background(), workspaceId)
   210  	if workspace != nil || err != tfe.ErrResourceNotFound {
   211  		t.Fatalf("workspace %s not deleted", workspaceId)
   212  	}
   213  }
   214  
   215  func TestDelete_ForceDelete(t *testing.T) {
   216  	state := testCloudState(t)
   217  	workspaceId := state.workspace.ID
   218  	state.workspace.Permissions.CanForceDelete = tfe.Bool(true)
   219  	state.workspace.ResourceCount = 5
   220  
   221  	if err := state.Delete(true); err != nil {
   222  		t.Fatalf("delete: %s", err)
   223  	}
   224  	workspace, err := state.tfeClient.Workspaces.ReadByID(context.Background(), workspaceId)
   225  	if workspace != nil || err != tfe.ErrResourceNotFound {
   226  		t.Fatalf("workspace %s not deleted", workspaceId)
   227  	}
   228  }
   229  
   230  func TestDelete_SafeDelete(t *testing.T) {
   231  	state := testCloudState(t)
   232  	workspaceId := state.workspace.ID
   233  	state.workspace.Permissions.CanForceDelete = tfe.Bool(false)
   234  	state.workspace.ResourceCount = 5
   235  
   236  	// safe-deleting a workspace with resources should fail
   237  	err := state.Delete(false)
   238  	if err == nil {
   239  		t.Fatalf("workspace should have failed to safe delete")
   240  	}
   241  
   242  	// safe-deleting a workspace with resources should succeed once it has no resources
   243  	state.workspace.ResourceCount = 0
   244  	if err = state.Delete(false); err != nil {
   245  		t.Fatalf("workspace safe-delete err: %s", err)
   246  	}
   247  
   248  	workspace, err := state.tfeClient.Workspaces.ReadByID(context.Background(), workspaceId)
   249  	if workspace != nil || err != tfe.ErrResourceNotFound {
   250  		t.Fatalf("workspace %s not deleted", workspaceId)
   251  	}
   252  }
   253  
   254  func TestState_PersistState(t *testing.T) {
   255  	cloudState := testCloudState(t)
   256  
   257  	t.Run("Initial PersistState", func(t *testing.T) {
   258  		if cloudState.readState != nil {
   259  			t.Fatal("expected nil initial readState")
   260  		}
   261  
   262  		err := cloudState.PersistState(nil)
   263  		if err != nil {
   264  			t.Fatalf("expected no error, got %q", err)
   265  		}
   266  
   267  		var expectedSerial uint64 = 1
   268  		if cloudState.readSerial != expectedSerial {
   269  			t.Fatalf("expected initial state readSerial to be %d, got %d", expectedSerial, cloudState.readSerial)
   270  		}
   271  	})
   272  }