github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/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/hashicorp/terraform/internal/states/statefile" 11 "github.com/hashicorp/terraform/internal/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 }