1package tfe
2
3import (
4	"context"
5	"crypto/md5"
6	"encoding/base64"
7	"fmt"
8	"io/ioutil"
9	"os"
10	"testing"
11	"time"
12
13	"github.com/hashicorp/go-uuid"
14)
15
16const badIdentifier = "! / nope"
17
18// Memoize test account details
19var _testAccountDetails *TestAccountDetails
20
21func testClient(t *testing.T) *Client {
22	client, err := NewClient(nil)
23	if err != nil {
24		t.Fatal(err)
25	}
26
27	return client
28}
29
30func fetchTestAccountDetails(t *testing.T, client *Client) *TestAccountDetails {
31	if _testAccountDetails == nil {
32		_testAccountDetails = FetchTestAccountDetails(t, client)
33	}
34	return _testAccountDetails
35}
36
37func createAgentPool(t *testing.T, client *Client, org *Organization) (*AgentPool, func()) {
38	var orgCleanup func()
39
40	if org == nil {
41		org, orgCleanup = createOrganization(t, client)
42	}
43
44	ctx := context.Background()
45	pool, err := client.AgentPools.Create(ctx, org.Name, AgentPoolCreateOptions{
46		Name: String(randomString(t)),
47	})
48	if err != nil {
49		t.Fatal(err)
50	}
51
52	return pool, func() {
53		if err := client.AgentPools.Delete(ctx, pool.ID); err != nil {
54			t.Errorf("Error destroying agent pool! WARNING: Dangling resources "+
55				"may exist! The full error is shown below.\n\n"+
56				"Agent pool ID: %s\nError: %s", pool.ID, err)
57		}
58
59		if orgCleanup != nil {
60			orgCleanup()
61		}
62	}
63}
64
65func createAgentToken(t *testing.T, client *Client, ap *AgentPool) (*AgentToken, func()) {
66	var apCleanup func()
67
68	if ap == nil {
69		ap, apCleanup = createAgentPool(t, client, nil)
70	}
71
72	ctx := context.Background()
73	at, err := client.AgentTokens.Generate(ctx, ap.ID, AgentTokenGenerateOptions{
74		Description: String(randomString(t)),
75	})
76	if err != nil {
77		t.Fatal(err)
78	}
79
80	return at, func() {
81		if err := client.AgentTokens.Delete(ctx, at.ID); err != nil {
82			t.Errorf("Error destroying agent token! WARNING: Dangling resources\n"+
83				"may exist! The full error is shown below.\n\n"+
84				"AgentToken: %s\nError: %s", at.ID, err)
85		}
86
87		if apCleanup != nil {
88			apCleanup()
89		}
90	}
91}
92
93func createConfigurationVersion(t *testing.T, client *Client, w *Workspace) (*ConfigurationVersion, func()) {
94	var wCleanup func()
95
96	if w == nil {
97		w, wCleanup = createWorkspace(t, client, nil)
98	}
99
100	ctx := context.Background()
101	cv, err := client.ConfigurationVersions.Create(
102		ctx,
103		w.ID,
104		ConfigurationVersionCreateOptions{AutoQueueRuns: Bool(false)},
105	)
106	if err != nil {
107		t.Fatal(err)
108	}
109
110	return cv, func() {
111		if wCleanup != nil {
112			wCleanup()
113		}
114	}
115}
116
117func createUploadedConfigurationVersion(t *testing.T, client *Client, w *Workspace) (*ConfigurationVersion, func()) {
118	cv, cvCleanup := createConfigurationVersion(t, client, w)
119
120	ctx := context.Background()
121	err := client.ConfigurationVersions.Upload(ctx, cv.UploadURL, "test-fixtures/config-version")
122	if err != nil {
123		cvCleanup()
124		t.Fatal(err)
125	}
126
127	for i := 0; ; i++ {
128		cv, err = client.ConfigurationVersions.Read(ctx, cv.ID)
129		if err != nil {
130			cvCleanup()
131			t.Fatal(err)
132		}
133
134		if cv.Status == ConfigurationUploaded {
135			break
136		}
137
138		if i > 10 {
139			cvCleanup()
140			t.Fatal("Timeout waiting for the configuration version to be uploaded")
141		}
142
143		time.Sleep(1 * time.Second)
144	}
145
146	return cv, cvCleanup
147}
148
149func createNotificationConfiguration(t *testing.T, client *Client, w *Workspace, options *NotificationConfigurationCreateOptions) (*NotificationConfiguration, func()) {
150	var wCleanup func()
151
152	if w == nil {
153		w, wCleanup = createWorkspace(t, client, nil)
154	}
155
156	if options == nil {
157		options = &NotificationConfigurationCreateOptions{
158			DestinationType: NotificationDestination(NotificationDestinationTypeGeneric),
159			Enabled:         Bool(false),
160			Name:            String(randomString(t)),
161			Token:           String(randomString(t)),
162			URL:             String("http://example.com"),
163			Triggers:        []string{NotificationTriggerCreated},
164		}
165	}
166
167	ctx := context.Background()
168	nc, err := client.NotificationConfigurations.Create(
169		ctx,
170		w.ID,
171		*options,
172	)
173	if err != nil {
174		t.Fatal(err)
175	}
176
177	return nc, func() {
178		if err := client.NotificationConfigurations.Delete(ctx, nc.ID); err != nil {
179			t.Errorf("Error destroying notification configuration! WARNING: Dangling\n"+
180				"resources may exist! The full error is shown below.\n\n"+
181				"NotificationConfiguration: %s\nError: %s", nc.ID, err)
182		}
183
184		if wCleanup != nil {
185			wCleanup()
186		}
187	}
188}
189
190func createPolicySetParameter(t *testing.T, client *Client, ps *PolicySet) (*PolicySetParameter, func()) {
191	var psCleanup func()
192
193	if ps == nil {
194		ps, psCleanup = createPolicySet(t, client, nil, nil, nil)
195	}
196
197	ctx := context.Background()
198	v, err := client.PolicySetParameters.Create(ctx, ps.ID, PolicySetParameterCreateOptions{
199		Key:      String(randomString(t)),
200		Value:    String(randomString(t)),
201		Category: Category(CategoryPolicySet),
202	})
203	if err != nil {
204		t.Fatal(err)
205	}
206
207	return v, func() {
208		if err := client.PolicySetParameters.Delete(ctx, ps.ID, v.ID); err != nil {
209			t.Errorf("Error destroying variable! WARNING: Dangling resources\n"+
210				"may exist! The full error is shown below.\n\n"+
211				"Parameter: %s\nError: %s", v.Key, err)
212		}
213
214		if psCleanup != nil {
215			psCleanup()
216		}
217	}
218}
219
220func createPolicySet(t *testing.T, client *Client, org *Organization, policies []*Policy, workspaces []*Workspace) (*PolicySet, func()) {
221	var orgCleanup func()
222
223	if org == nil {
224		org, orgCleanup = createOrganization(t, client)
225	}
226
227	ctx := context.Background()
228	ps, err := client.PolicySets.Create(ctx, org.Name, PolicySetCreateOptions{
229		Name:       String(randomString(t)),
230		Policies:   policies,
231		Workspaces: workspaces,
232	})
233	if err != nil {
234		t.Fatal(err)
235	}
236
237	return ps, func() {
238		if err := client.PolicySets.Delete(ctx, ps.ID); err != nil {
239			t.Errorf("Error destroying policy set! WARNING: Dangling resources\n"+
240				"may exist! The full error is shown below.\n\n"+
241				"PolicySet: %s\nError: %s", ps.ID, err)
242		}
243
244		if orgCleanup != nil {
245			orgCleanup()
246		}
247	}
248}
249
250func createPolicy(t *testing.T, client *Client, org *Organization) (*Policy, func()) {
251	var orgCleanup func()
252
253	if org == nil {
254		org, orgCleanup = createOrganization(t, client)
255	}
256
257	name := randomString(t)
258	options := PolicyCreateOptions{
259		Name: String(name),
260		Enforce: []*EnforcementOptions{
261			{
262				Path: String(name + ".sentinel"),
263				Mode: EnforcementMode(EnforcementSoft),
264			},
265		},
266	}
267
268	ctx := context.Background()
269	p, err := client.Policies.Create(ctx, org.Name, options)
270	if err != nil {
271		t.Fatal(err)
272	}
273
274	return p, func() {
275		if err := client.Policies.Delete(ctx, p.ID); err != nil {
276			t.Errorf("Error destroying policy! WARNING: Dangling resources\n"+
277				"may exist! The full error is shown below.\n\n"+
278				"Policy: %s\nError: %s", p.ID, err)
279		}
280
281		if orgCleanup != nil {
282			orgCleanup()
283		}
284	}
285}
286
287func createUploadedPolicy(t *testing.T, client *Client, pass bool, org *Organization) (*Policy, func()) {
288	var orgCleanup func()
289
290	if org == nil {
291		org, orgCleanup = createOrganization(t, client)
292	}
293
294	p, pCleanup := createPolicy(t, client, org)
295
296	ctx := context.Background()
297	err := client.Policies.Upload(ctx, p.ID, []byte(fmt.Sprintf("main = rule { %t }", pass)))
298	if err != nil {
299		t.Fatal(err)
300	}
301
302	p, err = client.Policies.Read(ctx, p.ID)
303	if err != nil {
304		t.Fatal(err)
305	}
306
307	return p, func() {
308		pCleanup()
309
310		if orgCleanup != nil {
311			orgCleanup()
312		}
313	}
314}
315
316func createOAuthClient(t *testing.T, client *Client, org *Organization) (*OAuthClient, func()) {
317	var orgCleanup func()
318
319	if org == nil {
320		org, orgCleanup = createOrganization(t, client)
321	}
322
323	githubToken := os.Getenv("GITHUB_TOKEN")
324	if githubToken == "" {
325		t.Skip("Export a valid GITHUB_TOKEN before running this test!")
326	}
327
328	options := OAuthClientCreateOptions{
329		APIURL:          String("https://api.github.com"),
330		HTTPURL:         String("https://github.com"),
331		OAuthToken:      String(githubToken),
332		ServiceProvider: ServiceProvider(ServiceProviderGithub),
333	}
334
335	ctx := context.Background()
336	oc, err := client.OAuthClients.Create(ctx, org.Name, options)
337	if err != nil {
338		t.Fatal(err)
339	}
340
341	// This currently panics as the token will not be there when the client is
342	// created. To get a token, the client needs to be connected through the UI
343	// first. So the test using this (TestOAuthTokensList) is currently disabled.
344	return oc, func() {
345		if err := client.OAuthClients.Delete(ctx, oc.ID); err != nil {
346			t.Errorf("Error destroying OAuth client! WARNING: Dangling resources\n"+
347				"may exist! The full error is shown below.\n\n"+
348				"OAuthClient: %s\nError: %s", oc.ID, err)
349		}
350
351		if orgCleanup != nil {
352			orgCleanup()
353		}
354	}
355}
356
357func createOAuthToken(t *testing.T, client *Client, org *Organization) (*OAuthToken, func()) {
358	ocTest, ocTestCleanup := createOAuthClient(t, client, org)
359	return ocTest.OAuthTokens[0], ocTestCleanup
360}
361
362func createOrganization(t *testing.T, client *Client) (*Organization, func()) {
363	ctx := context.Background()
364	org, err := client.Organizations.Create(ctx, OrganizationCreateOptions{
365		Name:  String("tst-" + randomString(t)),
366		Email: String(fmt.Sprintf("%s@tfe.local", randomString(t))),
367	})
368	if err != nil {
369		t.Fatal(err)
370	}
371
372	return org, func() {
373		if err := client.Organizations.Delete(ctx, org.Name); err != nil {
374			t.Errorf("Error destroying organization! WARNING: Dangling resources\n"+
375				"may exist! The full error is shown below.\n\n"+
376				"Organization: %s\nError: %s", org.Name, err)
377		}
378	}
379}
380
381func createOrganizationMembership(t *testing.T, client *Client, org *Organization) (*OrganizationMembership, func()) {
382	var orgCleanup func()
383
384	if org == nil {
385		org, orgCleanup = createOrganization(t, client)
386	}
387
388	ctx := context.Background()
389	mem, err := client.OrganizationMemberships.Create(ctx, org.Name, OrganizationMembershipCreateOptions{
390		Email: String(fmt.Sprintf("%s@tfe.local", randomString(t))),
391	})
392	if err != nil {
393		t.Fatal(err)
394	}
395
396	return mem, func() {
397		if err := client.OrganizationMemberships.Delete(ctx, mem.ID); err != nil {
398			t.Errorf("Error destroying membership! WARNING: Dangling resources\n"+
399				"may exist! The full error is shown below.\n\n"+
400				"Membership: %s\nError: %s", mem.ID, err)
401		}
402
403		if orgCleanup != nil {
404			orgCleanup()
405		}
406	}
407}
408
409func createOrganizationToken(t *testing.T, client *Client, org *Organization) (*OrganizationToken, func()) {
410	var orgCleanup func()
411
412	if org == nil {
413		org, orgCleanup = createOrganization(t, client)
414	}
415
416	ctx := context.Background()
417	tk, err := client.OrganizationTokens.Generate(ctx, org.Name)
418	if err != nil {
419		t.Fatal(err)
420	}
421
422	return tk, func() {
423		if err := client.OrganizationTokens.Delete(ctx, org.Name); err != nil {
424			t.Errorf("Error destroying organization token! WARNING: Dangling resources\n"+
425				"may exist! The full error is shown below.\n\n"+
426				"OrganizationToken: %s\nError: %s", tk.ID, err)
427		}
428
429		if orgCleanup != nil {
430			orgCleanup()
431		}
432	}
433}
434
435func createRunTrigger(t *testing.T, client *Client, w *Workspace, sourceable *Workspace) (*RunTrigger, func()) {
436	var wCleanup func()
437	var sourceableCleanup func()
438
439	if w == nil {
440		w, wCleanup = createWorkspace(t, client, nil)
441	}
442
443	if sourceable == nil {
444		sourceable, sourceableCleanup = createWorkspace(t, client, nil)
445	}
446
447	ctx := context.Background()
448	rt, err := client.RunTriggers.Create(
449		ctx,
450		w.ID,
451		RunTriggerCreateOptions{
452			Sourceable: sourceable,
453		},
454	)
455	if err != nil {
456		t.Fatal(err)
457	}
458
459	return rt, func() {
460		if err := client.RunTriggers.Delete(ctx, rt.ID); err != nil {
461			t.Errorf("Error destroying run trigger! WARNING: Dangling\n"+
462				"resources may exist! The full error is shown below.\n\n"+
463				"RunTrigger: %s\nError: %s", rt.ID, err)
464		}
465
466		if wCleanup != nil {
467			wCleanup()
468		}
469
470		if sourceableCleanup != nil {
471			sourceableCleanup()
472		}
473	}
474}
475
476func createRun(t *testing.T, client *Client, w *Workspace) (*Run, func()) {
477	var wCleanup func()
478
479	if w == nil {
480		w, wCleanup = createWorkspace(t, client, nil)
481	}
482
483	cv, cvCleanup := createUploadedConfigurationVersion(t, client, w)
484
485	ctx := context.Background()
486	r, err := client.Runs.Create(ctx, RunCreateOptions{
487		ConfigurationVersion: cv,
488		Workspace:            w,
489	})
490	if err != nil {
491		t.Fatal(err)
492	}
493
494	return r, func() {
495		if wCleanup != nil {
496			wCleanup()
497		} else {
498			cvCleanup()
499		}
500	}
501}
502
503func createPlannedRun(t *testing.T, client *Client, w *Workspace) (*Run, func()) {
504	r, rCleanup := createRun(t, client, w)
505
506	var err error
507	ctx := context.Background()
508	for i := 0; ; i++ {
509		r, err = client.Runs.Read(ctx, r.ID)
510		if err != nil {
511			t.Fatal(err)
512		}
513
514		switch r.Status {
515		case RunPlanned, RunCostEstimated, RunPolicyChecked, RunPolicyOverride:
516			return r, rCleanup
517		}
518
519		if i > 45 {
520			rCleanup()
521			t.Fatal("Timeout waiting for run to be planned")
522		}
523
524		time.Sleep(1 * time.Second)
525	}
526}
527
528func createCostEstimatedRun(t *testing.T, client *Client, w *Workspace) (*Run, func()) {
529	r, rCleanup := createRun(t, client, w)
530
531	var err error
532	ctx := context.Background()
533	for i := 0; ; i++ {
534		r, err = client.Runs.Read(ctx, r.ID)
535		if err != nil {
536			t.Fatal(err)
537		}
538
539		switch r.Status {
540		case RunCostEstimated, RunPolicyChecked, RunPolicyOverride:
541			return r, rCleanup
542		}
543
544		if i > 45 {
545			rCleanup()
546			t.Fatal("Timeout waiting for run to be cost estimated")
547		}
548
549		time.Sleep(2 * time.Second)
550	}
551}
552
553func createAppliedRun(t *testing.T, client *Client, w *Workspace) (*Run, func()) {
554	r, rCleanup := createPlannedRun(t, client, w)
555	ctx := context.Background()
556
557	err := client.Runs.Apply(ctx, r.ID, RunApplyOptions{})
558	if err != nil {
559		t.Fatal(err)
560	}
561
562	for i := 0; ; i++ {
563		r, err = client.Runs.Read(ctx, r.ID)
564		if err != nil {
565			t.Fatal(err)
566		}
567
568		if r.Status == RunApplied {
569			return r, rCleanup
570		}
571
572		if i > 45 {
573			rCleanup()
574			t.Fatal("Timeout waiting for run to be applied")
575		}
576
577		time.Sleep(1 * time.Second)
578	}
579}
580
581func createPlanExport(t *testing.T, client *Client, r *Run) (*PlanExport, func()) {
582	var rCleanup func()
583
584	if r == nil {
585		r, rCleanup = createPlannedRun(t, client, nil)
586	}
587
588	ctx := context.Background()
589	pe, err := client.PlanExports.Create(ctx, PlanExportCreateOptions{
590		Plan:     r.Plan,
591		DataType: PlanExportType(PlanExportSentinelMockBundleV0),
592	})
593	if err != nil {
594		t.Fatal(err)
595	}
596
597	for i := 0; ; i++ {
598		pe, err := client.PlanExports.Read(ctx, pe.ID)
599		if err != nil {
600			t.Fatal(err)
601		}
602
603		if pe.Status == PlanExportFinished {
604			return pe, func() {
605				if rCleanup != nil {
606					rCleanup()
607				}
608			}
609		}
610
611		if i > 45 {
612			rCleanup()
613			t.Fatal("Timeout waiting for plan export to finish")
614		}
615
616		time.Sleep(1 * time.Second)
617	}
618}
619
620func createRegistryModule(t *testing.T, client *Client, org *Organization) (*RegistryModule, func()) {
621	var orgCleanup func()
622
623	if org == nil {
624		org, orgCleanup = createOrganization(t, client)
625	}
626
627	ctx := context.Background()
628
629	options := RegistryModuleCreateOptions{
630		Name:     String("name"),
631		Provider: String("provider"),
632	}
633	rm, err := client.RegistryModules.Create(ctx, org.Name, options)
634	if err != nil {
635		t.Fatal(err)
636	}
637
638	return rm, func() {
639		if err := client.RegistryModules.Delete(ctx, org.Name, rm.Name); err != nil {
640			t.Errorf("Error destroying registry module! WARNING: Dangling resources\n"+
641				"may exist! The full error is shown below.\n\n"+
642				"Registry Module: %s\nError: %s", rm.Name, err)
643		}
644
645		if orgCleanup != nil {
646			orgCleanup()
647		}
648	}
649}
650
651func createRegistryModuleWithVersion(t *testing.T, client *Client, org *Organization) (*RegistryModule, func()) {
652	var orgCleanup func()
653
654	if org == nil {
655		org, orgCleanup = createOrganization(t, client)
656	}
657
658	ctx := context.Background()
659
660	options := RegistryModuleCreateOptions{
661		Name:     String("name"),
662		Provider: String("provider"),
663	}
664	rm, err := client.RegistryModules.Create(ctx, org.Name, options)
665	if err != nil {
666		t.Fatal(err)
667	}
668
669	optionsModuleVersion := RegistryModuleCreateVersionOptions{
670		Version: String("1.0.0"),
671	}
672	_, err = client.RegistryModules.CreateVersion(ctx, org.Name, rm.Name, rm.Provider, optionsModuleVersion)
673	if err != nil {
674		t.Fatal(err)
675	}
676
677	rm, err = client.RegistryModules.Read(ctx, org.Name, rm.Name, rm.Provider)
678	if err != nil {
679		t.Fatal(err)
680	}
681
682	return rm, func() {
683		if err := client.RegistryModules.Delete(ctx, org.Name, rm.Name); err != nil {
684			t.Errorf("Error destroying registry module! WARNING: Dangling resources\n"+
685				"may exist! The full error is shown below.\n\n"+
686				"Registry Module: %s\nError: %s", rm.Name, err)
687		}
688
689		if orgCleanup != nil {
690			orgCleanup()
691		}
692	}
693}
694
695func createSSHKey(t *testing.T, client *Client, org *Organization) (*SSHKey, func()) {
696	var orgCleanup func()
697
698	if org == nil {
699		org, orgCleanup = createOrganization(t, client)
700	}
701
702	ctx := context.Background()
703	key, err := client.SSHKeys.Create(ctx, org.Name, SSHKeyCreateOptions{
704		Name:  String(randomString(t)),
705		Value: String(randomString(t)),
706	})
707	if err != nil {
708		t.Fatal(err)
709	}
710
711	return key, func() {
712		if err := client.SSHKeys.Delete(ctx, key.ID); err != nil {
713			t.Errorf("Error destroying SSH key! WARNING: Dangling resources\n"+
714				"may exist! The full error is shown below.\n\n"+
715				"SSHKey: %s\nError: %s", key.Name, err)
716		}
717
718		if orgCleanup != nil {
719			orgCleanup()
720		}
721	}
722}
723
724func createStateVersion(t *testing.T, client *Client, serial int64, w *Workspace) (*StateVersion, func()) {
725	var wCleanup func()
726
727	if w == nil {
728		w, wCleanup = createWorkspace(t, client, nil)
729	}
730
731	state, err := ioutil.ReadFile("test-fixtures/state-version/terraform.tfstate")
732	if err != nil {
733		t.Fatal(err)
734	}
735
736	ctx := context.Background()
737
738	_, err = client.Workspaces.Lock(ctx, w.ID, WorkspaceLockOptions{})
739	if err != nil {
740		t.Fatal(err)
741	}
742	defer func() {
743		_, err := client.Workspaces.Unlock(ctx, w.ID)
744		if err != nil {
745			t.Fatal(err)
746		}
747	}()
748
749	sv, err := client.StateVersions.Create(ctx, w.ID, StateVersionCreateOptions{
750		MD5:    String(fmt.Sprintf("%x", md5.Sum(state))),
751		Serial: Int64(serial),
752		State:  String(base64.StdEncoding.EncodeToString(state)),
753	})
754	if err != nil {
755		t.Fatal(err)
756	}
757
758	return sv, func() {
759		// There currently isn't a way to delete a state, so we
760		// can only cleanup by deleting the workspace.
761		if wCleanup != nil {
762			wCleanup()
763		}
764	}
765}
766
767func createTeam(t *testing.T, client *Client, org *Organization) (*Team, func()) {
768	var orgCleanup func()
769
770	if org == nil {
771		org, orgCleanup = createOrganization(t, client)
772	}
773
774	ctx := context.Background()
775	tm, err := client.Teams.Create(ctx, org.Name, TeamCreateOptions{
776		Name:               String(randomString(t)),
777		OrganizationAccess: &OrganizationAccessOptions{ManagePolicies: Bool(true)},
778	})
779	if err != nil {
780		t.Fatal(err)
781	}
782
783	return tm, func() {
784		if err := client.Teams.Delete(ctx, tm.ID); err != nil {
785			t.Errorf("Error destroying team! WARNING: Dangling resources\n"+
786				"may exist! The full error is shown below.\n\n"+
787				"Team: %s\nError: %s", tm.Name, err)
788		}
789
790		if orgCleanup != nil {
791			orgCleanup()
792		}
793	}
794}
795
796func createTeamAccess(t *testing.T, client *Client, tm *Team, w *Workspace, org *Organization) (*TeamAccess, func()) {
797	var orgCleanup, tmCleanup func()
798
799	if org == nil {
800		org, orgCleanup = createOrganization(t, client)
801	}
802
803	if tm == nil {
804		tm, tmCleanup = createTeam(t, client, org)
805	}
806
807	if w == nil {
808		w, _ = createWorkspace(t, client, org)
809	}
810
811	ctx := context.Background()
812	ta, err := client.TeamAccess.Add(ctx, TeamAccessAddOptions{
813		Access:    Access(AccessAdmin),
814		Team:      tm,
815		Workspace: w,
816	})
817	if err != nil {
818		t.Fatal(err)
819	}
820
821	return ta, func() {
822		if err := client.TeamAccess.Remove(ctx, ta.ID); err != nil {
823			t.Errorf("Error removing team access! WARNING: Dangling resources\n"+
824				"may exist! The full error is shown below.\n\n"+
825				"TeamAccess: %s\nError: %s", ta.ID, err)
826		}
827
828		if tmCleanup != nil {
829			tmCleanup()
830		}
831
832		if orgCleanup != nil {
833			orgCleanup()
834		}
835	}
836}
837
838func createTeamToken(t *testing.T, client *Client, tm *Team) (*TeamToken, func()) {
839	var tmCleanup func()
840
841	if tm == nil {
842		tm, tmCleanup = createTeam(t, client, nil)
843	}
844
845	ctx := context.Background()
846	tt, err := client.TeamTokens.Generate(ctx, tm.ID)
847	if err != nil {
848		t.Fatal(err)
849	}
850
851	return tt, func() {
852		if err := client.TeamTokens.Delete(ctx, tm.ID); err != nil {
853			t.Errorf("Error destroying team token! WARNING: Dangling resources\n"+
854				"may exist! The full error is shown below.\n\n"+
855				"TeamToken: %s\nError: %s", tm.ID, err)
856		}
857
858		if tmCleanup != nil {
859			tmCleanup()
860		}
861	}
862}
863
864func createVariable(t *testing.T, client *Client, w *Workspace) (*Variable, func()) {
865	var wCleanup func()
866
867	if w == nil {
868		w, wCleanup = createWorkspace(t, client, nil)
869	}
870
871	ctx := context.Background()
872	v, err := client.Variables.Create(ctx, w.ID, VariableCreateOptions{
873		Key:         String(randomString(t)),
874		Value:       String(randomString(t)),
875		Category:    Category(CategoryTerraform),
876		Description: String(randomString(t)),
877	})
878	if err != nil {
879		t.Fatal(err)
880	}
881
882	return v, func() {
883		if err := client.Variables.Delete(ctx, w.ID, v.ID); err != nil {
884			t.Errorf("Error destroying variable! WARNING: Dangling resources\n"+
885				"may exist! The full error is shown below.\n\n"+
886				"Variable: %s\nError: %s", v.Key, err)
887		}
888
889		if wCleanup != nil {
890			wCleanup()
891		}
892	}
893}
894
895func createWorkspace(t *testing.T, client *Client, org *Organization) (*Workspace, func()) {
896	var orgCleanup func()
897
898	if org == nil {
899		org, orgCleanup = createOrganization(t, client)
900	}
901
902	ctx := context.Background()
903	w, err := client.Workspaces.Create(ctx, org.Name, WorkspaceCreateOptions{
904		Name: String(randomString(t)),
905	})
906	if err != nil {
907		t.Fatal(err)
908	}
909
910	return w, func() {
911		if err := client.Workspaces.Delete(ctx, org.Name, w.Name); err != nil {
912			t.Errorf("Error destroying workspace! WARNING: Dangling resources\n"+
913				"may exist! The full error is shown below.\n\n"+
914				"Workspace: %s\nError: %s", w.Name, err)
915		}
916
917		if orgCleanup != nil {
918			orgCleanup()
919		}
920	}
921}
922
923func createWorkspaceWithVCS(t *testing.T, client *Client, org *Organization) (*Workspace, func()) {
924	var orgCleanup func()
925
926	if org == nil {
927		org, orgCleanup = createOrganization(t, client)
928	}
929
930	oc, ocCleanup := createOAuthToken(t, client, org)
931
932	githubIdentifier := os.Getenv("GITHUB_POLICY_SET_IDENTIFIER")
933	if githubIdentifier == "" {
934		t.Fatal("Export a valid GITHUB_POLICY_SET_IDENTIFIER before running this test!")
935	}
936
937	options := WorkspaceCreateOptions{
938		Name: String(randomString(t)),
939		VCSRepo: &VCSRepoOptions{
940			Identifier:   String(githubIdentifier),
941			OAuthTokenID: String(oc.ID),
942		},
943	}
944
945	ctx := context.Background()
946	w, err := client.Workspaces.Create(ctx, org.Name, options)
947	if err != nil {
948		t.Fatal(err)
949	}
950
951	return w, func() {
952		if err := client.Workspaces.Delete(ctx, org.Name, w.Name); err != nil {
953			t.Errorf("Error destroying workspace! WARNING: Dangling resources\n"+
954				"may exist! The full error is shown below.\n\n"+
955				"Workspace: %s\nError: %s", w.Name, err)
956		}
957
958		if ocCleanup != nil {
959			ocCleanup()
960		}
961
962		if orgCleanup != nil {
963			orgCleanup()
964		}
965	}
966}
967
968func randomString(t *testing.T) string {
969	v, err := uuid.GenerateUUID()
970	if err != nil {
971		t.Fatal(err)
972	}
973	return v
974}
975