github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/cloud/tfe_client_mock.go (about) 1 package cloud 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/base64" 7 "errors" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "math/rand" 12 "os" 13 "path/filepath" 14 "strings" 15 "sync" 16 "time" 17 18 tfe "github.com/hashicorp/go-tfe" 19 "github.com/mitchellh/copystructure" 20 21 tfversion "github.com/eliastor/durgaform/version" 22 ) 23 24 type MockClient struct { 25 Applies *MockApplies 26 ConfigurationVersions *MockConfigurationVersions 27 CostEstimates *MockCostEstimates 28 Organizations *MockOrganizations 29 Plans *MockPlans 30 PolicyChecks *MockPolicyChecks 31 Runs *MockRuns 32 StateVersions *MockStateVersions 33 StateVersionOutputs *MockStateVersionOutputs 34 Variables *MockVariables 35 Workspaces *MockWorkspaces 36 } 37 38 func NewMockClient() *MockClient { 39 c := &MockClient{} 40 c.Applies = newMockApplies(c) 41 c.ConfigurationVersions = newMockConfigurationVersions(c) 42 c.CostEstimates = newMockCostEstimates(c) 43 c.Organizations = newMockOrganizations(c) 44 c.Plans = newMockPlans(c) 45 c.PolicyChecks = newMockPolicyChecks(c) 46 c.Runs = newMockRuns(c) 47 c.StateVersions = newMockStateVersions(c) 48 c.StateVersionOutputs = newMockStateVersionOutputs(c) 49 c.Variables = newMockVariables(c) 50 c.Workspaces = newMockWorkspaces(c) 51 return c 52 } 53 54 type MockApplies struct { 55 client *MockClient 56 applies map[string]*tfe.Apply 57 logs map[string]string 58 } 59 60 func newMockApplies(client *MockClient) *MockApplies { 61 return &MockApplies{ 62 client: client, 63 applies: make(map[string]*tfe.Apply), 64 logs: make(map[string]string), 65 } 66 } 67 68 // create is a helper function to create a mock apply that uses the configured 69 // working directory to find the logfile. 70 func (m *MockApplies) create(cvID, workspaceID string) (*tfe.Apply, error) { 71 c, ok := m.client.ConfigurationVersions.configVersions[cvID] 72 if !ok { 73 return nil, tfe.ErrResourceNotFound 74 } 75 if c.Speculative { 76 // Speculative means its plan-only so we don't create a Apply. 77 return nil, nil 78 } 79 80 id := GenerateID("apply-") 81 url := fmt.Sprintf("https://app.durgaform.io/_archivist/%s", id) 82 83 a := &tfe.Apply{ 84 ID: id, 85 LogReadURL: url, 86 Status: tfe.ApplyPending, 87 } 88 89 w, ok := m.client.Workspaces.workspaceIDs[workspaceID] 90 if !ok { 91 return nil, tfe.ErrResourceNotFound 92 } 93 94 if w.AutoApply { 95 a.Status = tfe.ApplyRunning 96 } 97 98 m.logs[url] = filepath.Join( 99 m.client.ConfigurationVersions.uploadPaths[cvID], 100 w.WorkingDirectory, 101 "apply.log", 102 ) 103 m.applies[a.ID] = a 104 105 return a, nil 106 } 107 108 func (m *MockApplies) Read(ctx context.Context, applyID string) (*tfe.Apply, error) { 109 a, ok := m.applies[applyID] 110 if !ok { 111 return nil, tfe.ErrResourceNotFound 112 } 113 // Together with the mockLogReader this allows testing queued runs. 114 if a.Status == tfe.ApplyRunning { 115 a.Status = tfe.ApplyFinished 116 } 117 return a, nil 118 } 119 120 func (m *MockApplies) Logs(ctx context.Context, applyID string) (io.Reader, error) { 121 a, err := m.Read(ctx, applyID) 122 if err != nil { 123 return nil, err 124 } 125 126 logfile, ok := m.logs[a.LogReadURL] 127 if !ok { 128 return nil, tfe.ErrResourceNotFound 129 } 130 131 if _, err := os.Stat(logfile); os.IsNotExist(err) { 132 return bytes.NewBufferString("logfile does not exist"), nil 133 } 134 135 logs, err := ioutil.ReadFile(logfile) 136 if err != nil { 137 return nil, err 138 } 139 140 done := func() (bool, error) { 141 a, err := m.Read(ctx, applyID) 142 if err != nil { 143 return false, err 144 } 145 if a.Status != tfe.ApplyFinished { 146 return false, nil 147 } 148 return true, nil 149 } 150 151 return &mockLogReader{ 152 done: done, 153 logs: bytes.NewBuffer(logs), 154 }, nil 155 } 156 157 type MockConfigurationVersions struct { 158 client *MockClient 159 configVersions map[string]*tfe.ConfigurationVersion 160 uploadPaths map[string]string 161 uploadURLs map[string]*tfe.ConfigurationVersion 162 } 163 164 func newMockConfigurationVersions(client *MockClient) *MockConfigurationVersions { 165 return &MockConfigurationVersions{ 166 client: client, 167 configVersions: make(map[string]*tfe.ConfigurationVersion), 168 uploadPaths: make(map[string]string), 169 uploadURLs: make(map[string]*tfe.ConfigurationVersion), 170 } 171 } 172 173 func (m *MockConfigurationVersions) List(ctx context.Context, workspaceID string, options *tfe.ConfigurationVersionListOptions) (*tfe.ConfigurationVersionList, error) { 174 cvl := &tfe.ConfigurationVersionList{} 175 for _, cv := range m.configVersions { 176 cvl.Items = append(cvl.Items, cv) 177 } 178 179 cvl.Pagination = &tfe.Pagination{ 180 CurrentPage: 1, 181 NextPage: 1, 182 PreviousPage: 1, 183 TotalPages: 1, 184 TotalCount: len(cvl.Items), 185 } 186 187 return cvl, nil 188 } 189 190 func (m *MockConfigurationVersions) Create(ctx context.Context, workspaceID string, options tfe.ConfigurationVersionCreateOptions) (*tfe.ConfigurationVersion, error) { 191 id := GenerateID("cv-") 192 url := fmt.Sprintf("https://app.durgaform.io/_archivist/%s", id) 193 194 cv := &tfe.ConfigurationVersion{ 195 ID: id, 196 Status: tfe.ConfigurationPending, 197 UploadURL: url, 198 } 199 200 m.configVersions[cv.ID] = cv 201 m.uploadURLs[url] = cv 202 203 return cv, nil 204 } 205 206 func (m *MockConfigurationVersions) Read(ctx context.Context, cvID string) (*tfe.ConfigurationVersion, error) { 207 cv, ok := m.configVersions[cvID] 208 if !ok { 209 return nil, tfe.ErrResourceNotFound 210 } 211 return cv, nil 212 } 213 214 func (m *MockConfigurationVersions) ReadWithOptions(ctx context.Context, cvID string, options *tfe.ConfigurationVersionReadOptions) (*tfe.ConfigurationVersion, error) { 215 cv, ok := m.configVersions[cvID] 216 if !ok { 217 return nil, tfe.ErrResourceNotFound 218 } 219 return cv, nil 220 } 221 222 func (m *MockConfigurationVersions) Upload(ctx context.Context, url, path string) error { 223 cv, ok := m.uploadURLs[url] 224 if !ok { 225 return errors.New("404 not found") 226 } 227 m.uploadPaths[cv.ID] = path 228 cv.Status = tfe.ConfigurationUploaded 229 return nil 230 } 231 232 func (m *MockConfigurationVersions) Archive(ctx context.Context, cvID string) error { 233 panic("not implemented") 234 } 235 236 func (m *MockConfigurationVersions) Download(ctx context.Context, cvID string) ([]byte, error) { 237 panic("not implemented") 238 } 239 240 type MockCostEstimates struct { 241 client *MockClient 242 Estimations map[string]*tfe.CostEstimate 243 logs map[string]string 244 } 245 246 func newMockCostEstimates(client *MockClient) *MockCostEstimates { 247 return &MockCostEstimates{ 248 client: client, 249 Estimations: make(map[string]*tfe.CostEstimate), 250 logs: make(map[string]string), 251 } 252 } 253 254 // create is a helper function to create a mock cost estimation that uses the 255 // configured working directory to find the logfile. 256 func (m *MockCostEstimates) create(cvID, workspaceID string) (*tfe.CostEstimate, error) { 257 id := GenerateID("ce-") 258 259 ce := &tfe.CostEstimate{ 260 ID: id, 261 MatchedResourcesCount: 1, 262 ResourcesCount: 1, 263 DeltaMonthlyCost: "0.00", 264 ProposedMonthlyCost: "0.00", 265 Status: tfe.CostEstimateFinished, 266 } 267 268 w, ok := m.client.Workspaces.workspaceIDs[workspaceID] 269 if !ok { 270 return nil, tfe.ErrResourceNotFound 271 } 272 273 logfile := filepath.Join( 274 m.client.ConfigurationVersions.uploadPaths[cvID], 275 w.WorkingDirectory, 276 "cost-estimate.log", 277 ) 278 279 if _, err := os.Stat(logfile); os.IsNotExist(err) { 280 return nil, nil 281 } 282 283 m.logs[ce.ID] = logfile 284 m.Estimations[ce.ID] = ce 285 286 return ce, nil 287 } 288 289 func (m *MockCostEstimates) Read(ctx context.Context, costEstimateID string) (*tfe.CostEstimate, error) { 290 ce, ok := m.Estimations[costEstimateID] 291 if !ok { 292 return nil, tfe.ErrResourceNotFound 293 } 294 return ce, nil 295 } 296 297 func (m *MockCostEstimates) Logs(ctx context.Context, costEstimateID string) (io.Reader, error) { 298 ce, ok := m.Estimations[costEstimateID] 299 if !ok { 300 return nil, tfe.ErrResourceNotFound 301 } 302 303 logfile, ok := m.logs[ce.ID] 304 if !ok { 305 return nil, tfe.ErrResourceNotFound 306 } 307 308 if _, err := os.Stat(logfile); os.IsNotExist(err) { 309 return bytes.NewBufferString("logfile does not exist"), nil 310 } 311 312 logs, err := ioutil.ReadFile(logfile) 313 if err != nil { 314 return nil, err 315 } 316 317 ce.Status = tfe.CostEstimateFinished 318 319 return bytes.NewBuffer(logs), nil 320 } 321 322 type MockOrganizations struct { 323 client *MockClient 324 organizations map[string]*tfe.Organization 325 } 326 327 func newMockOrganizations(client *MockClient) *MockOrganizations { 328 return &MockOrganizations{ 329 client: client, 330 organizations: make(map[string]*tfe.Organization), 331 } 332 } 333 334 func (m *MockOrganizations) List(ctx context.Context, options *tfe.OrganizationListOptions) (*tfe.OrganizationList, error) { 335 orgl := &tfe.OrganizationList{} 336 for _, org := range m.organizations { 337 orgl.Items = append(orgl.Items, org) 338 } 339 340 orgl.Pagination = &tfe.Pagination{ 341 CurrentPage: 1, 342 NextPage: 1, 343 PreviousPage: 1, 344 TotalPages: 1, 345 TotalCount: len(orgl.Items), 346 } 347 348 return orgl, nil 349 } 350 351 // mockLogReader is a mock logreader that enables testing queued runs. 352 type mockLogReader struct { 353 done func() (bool, error) 354 logs *bytes.Buffer 355 } 356 357 func (m *mockLogReader) Read(l []byte) (int, error) { 358 for { 359 if written, err := m.read(l); err != io.ErrNoProgress { 360 return written, err 361 } 362 time.Sleep(1 * time.Millisecond) 363 } 364 } 365 366 func (m *mockLogReader) read(l []byte) (int, error) { 367 done, err := m.done() 368 if err != nil { 369 return 0, err 370 } 371 if !done { 372 return 0, io.ErrNoProgress 373 } 374 return m.logs.Read(l) 375 } 376 377 func (m *MockOrganizations) Create(ctx context.Context, options tfe.OrganizationCreateOptions) (*tfe.Organization, error) { 378 org := &tfe.Organization{Name: *options.Name} 379 m.organizations[org.Name] = org 380 return org, nil 381 } 382 383 func (m *MockOrganizations) Read(ctx context.Context, name string) (*tfe.Organization, error) { 384 org, ok := m.organizations[name] 385 if !ok { 386 return nil, tfe.ErrResourceNotFound 387 } 388 return org, nil 389 } 390 391 func (m *MockOrganizations) Update(ctx context.Context, name string, options tfe.OrganizationUpdateOptions) (*tfe.Organization, error) { 392 org, ok := m.organizations[name] 393 if !ok { 394 return nil, tfe.ErrResourceNotFound 395 } 396 org.Name = *options.Name 397 return org, nil 398 399 } 400 401 func (m *MockOrganizations) Delete(ctx context.Context, name string) error { 402 delete(m.organizations, name) 403 return nil 404 } 405 406 func (m *MockOrganizations) ReadCapacity(ctx context.Context, name string) (*tfe.Capacity, error) { 407 var pending, running int 408 for _, r := range m.client.Runs.Runs { 409 if r.Status == tfe.RunPending { 410 pending++ 411 continue 412 } 413 running++ 414 } 415 return &tfe.Capacity{Pending: pending, Running: running}, nil 416 } 417 418 func (m *MockOrganizations) ReadEntitlements(ctx context.Context, name string) (*tfe.Entitlements, error) { 419 return &tfe.Entitlements{ 420 Operations: true, 421 PrivateModuleRegistry: true, 422 Sentinel: true, 423 StateStorage: true, 424 Teams: true, 425 VCSIntegrations: true, 426 }, nil 427 } 428 429 func (m *MockOrganizations) ReadRunQueue(ctx context.Context, name string, options tfe.ReadRunQueueOptions) (*tfe.RunQueue, error) { 430 rq := &tfe.RunQueue{} 431 432 for _, r := range m.client.Runs.Runs { 433 rq.Items = append(rq.Items, r) 434 } 435 436 rq.Pagination = &tfe.Pagination{ 437 CurrentPage: 1, 438 NextPage: 1, 439 PreviousPage: 1, 440 TotalPages: 1, 441 TotalCount: len(rq.Items), 442 } 443 444 return rq, nil 445 } 446 447 type MockPlans struct { 448 client *MockClient 449 logs map[string]string 450 planOutputs map[string]string 451 plans map[string]*tfe.Plan 452 } 453 454 func newMockPlans(client *MockClient) *MockPlans { 455 return &MockPlans{ 456 client: client, 457 logs: make(map[string]string), 458 planOutputs: make(map[string]string), 459 plans: make(map[string]*tfe.Plan), 460 } 461 } 462 463 // create is a helper function to create a mock plan that uses the configured 464 // working directory to find the logfile. 465 func (m *MockPlans) create(cvID, workspaceID string) (*tfe.Plan, error) { 466 id := GenerateID("plan-") 467 url := fmt.Sprintf("https://app.durgaform.io/_archivist/%s", id) 468 469 p := &tfe.Plan{ 470 ID: id, 471 LogReadURL: url, 472 Status: tfe.PlanPending, 473 } 474 475 w, ok := m.client.Workspaces.workspaceIDs[workspaceID] 476 if !ok { 477 return nil, tfe.ErrResourceNotFound 478 } 479 480 m.logs[url] = filepath.Join( 481 m.client.ConfigurationVersions.uploadPaths[cvID], 482 w.WorkingDirectory, 483 "plan.log", 484 ) 485 m.plans[p.ID] = p 486 487 return p, nil 488 } 489 490 func (m *MockPlans) Read(ctx context.Context, planID string) (*tfe.Plan, error) { 491 p, ok := m.plans[planID] 492 if !ok { 493 return nil, tfe.ErrResourceNotFound 494 } 495 // Together with the mockLogReader this allows testing queued runs. 496 if p.Status == tfe.PlanRunning { 497 p.Status = tfe.PlanFinished 498 } 499 return p, nil 500 } 501 502 func (m *MockPlans) Logs(ctx context.Context, planID string) (io.Reader, error) { 503 p, err := m.Read(ctx, planID) 504 if err != nil { 505 return nil, err 506 } 507 508 logfile, ok := m.logs[p.LogReadURL] 509 if !ok { 510 return nil, tfe.ErrResourceNotFound 511 } 512 513 if _, err := os.Stat(logfile); os.IsNotExist(err) { 514 return bytes.NewBufferString("logfile does not exist"), nil 515 } 516 517 logs, err := ioutil.ReadFile(logfile) 518 if err != nil { 519 return nil, err 520 } 521 522 done := func() (bool, error) { 523 p, err := m.Read(ctx, planID) 524 if err != nil { 525 return false, err 526 } 527 if p.Status != tfe.PlanFinished { 528 return false, nil 529 } 530 return true, nil 531 } 532 533 return &mockLogReader{ 534 done: done, 535 logs: bytes.NewBuffer(logs), 536 }, nil 537 } 538 539 func (m *MockPlans) ReadJSONOutput(ctx context.Context, planID string) ([]byte, error) { 540 planOutput, ok := m.planOutputs[planID] 541 if !ok { 542 return nil, tfe.ErrResourceNotFound 543 } 544 545 return []byte(planOutput), nil 546 } 547 548 type MockPolicyChecks struct { 549 client *MockClient 550 checks map[string]*tfe.PolicyCheck 551 logs map[string]string 552 } 553 554 func newMockPolicyChecks(client *MockClient) *MockPolicyChecks { 555 return &MockPolicyChecks{ 556 client: client, 557 checks: make(map[string]*tfe.PolicyCheck), 558 logs: make(map[string]string), 559 } 560 } 561 562 // create is a helper function to create a mock policy check that uses the 563 // configured working directory to find the logfile. 564 func (m *MockPolicyChecks) create(cvID, workspaceID string) (*tfe.PolicyCheck, error) { 565 id := GenerateID("pc-") 566 567 pc := &tfe.PolicyCheck{ 568 ID: id, 569 Actions: &tfe.PolicyActions{}, 570 Permissions: &tfe.PolicyPermissions{}, 571 Scope: tfe.PolicyScopeOrganization, 572 Status: tfe.PolicyPending, 573 } 574 575 w, ok := m.client.Workspaces.workspaceIDs[workspaceID] 576 if !ok { 577 return nil, tfe.ErrResourceNotFound 578 } 579 580 logfile := filepath.Join( 581 m.client.ConfigurationVersions.uploadPaths[cvID], 582 w.WorkingDirectory, 583 "policy.log", 584 ) 585 586 if _, err := os.Stat(logfile); os.IsNotExist(err) { 587 return nil, nil 588 } 589 590 m.logs[pc.ID] = logfile 591 m.checks[pc.ID] = pc 592 593 return pc, nil 594 } 595 596 func (m *MockPolicyChecks) List(ctx context.Context, runID string, options *tfe.PolicyCheckListOptions) (*tfe.PolicyCheckList, error) { 597 _, ok := m.client.Runs.Runs[runID] 598 if !ok { 599 return nil, tfe.ErrResourceNotFound 600 } 601 602 pcl := &tfe.PolicyCheckList{} 603 for _, pc := range m.checks { 604 pcl.Items = append(pcl.Items, pc) 605 } 606 607 pcl.Pagination = &tfe.Pagination{ 608 CurrentPage: 1, 609 NextPage: 1, 610 PreviousPage: 1, 611 TotalPages: 1, 612 TotalCount: len(pcl.Items), 613 } 614 615 return pcl, nil 616 } 617 618 func (m *MockPolicyChecks) Read(ctx context.Context, policyCheckID string) (*tfe.PolicyCheck, error) { 619 pc, ok := m.checks[policyCheckID] 620 if !ok { 621 return nil, tfe.ErrResourceNotFound 622 } 623 624 logfile, ok := m.logs[pc.ID] 625 if !ok { 626 return nil, tfe.ErrResourceNotFound 627 } 628 629 if _, err := os.Stat(logfile); os.IsNotExist(err) { 630 return nil, fmt.Errorf("logfile does not exist") 631 } 632 633 logs, err := ioutil.ReadFile(logfile) 634 if err != nil { 635 return nil, err 636 } 637 638 switch { 639 case bytes.Contains(logs, []byte("Sentinel Result: true")): 640 pc.Status = tfe.PolicyPasses 641 case bytes.Contains(logs, []byte("Sentinel Result: false")): 642 switch { 643 case bytes.Contains(logs, []byte("hard-mandatory")): 644 pc.Status = tfe.PolicyHardFailed 645 case bytes.Contains(logs, []byte("soft-mandatory")): 646 pc.Actions.IsOverridable = true 647 pc.Permissions.CanOverride = true 648 pc.Status = tfe.PolicySoftFailed 649 } 650 default: 651 // As this is an unexpected state, we say the policy errored. 652 pc.Status = tfe.PolicyErrored 653 } 654 655 return pc, nil 656 } 657 658 func (m *MockPolicyChecks) Override(ctx context.Context, policyCheckID string) (*tfe.PolicyCheck, error) { 659 pc, ok := m.checks[policyCheckID] 660 if !ok { 661 return nil, tfe.ErrResourceNotFound 662 } 663 pc.Status = tfe.PolicyOverridden 664 return pc, nil 665 } 666 667 func (m *MockPolicyChecks) Logs(ctx context.Context, policyCheckID string) (io.Reader, error) { 668 pc, ok := m.checks[policyCheckID] 669 if !ok { 670 return nil, tfe.ErrResourceNotFound 671 } 672 673 logfile, ok := m.logs[pc.ID] 674 if !ok { 675 return nil, tfe.ErrResourceNotFound 676 } 677 678 if _, err := os.Stat(logfile); os.IsNotExist(err) { 679 return bytes.NewBufferString("logfile does not exist"), nil 680 } 681 682 logs, err := ioutil.ReadFile(logfile) 683 if err != nil { 684 return nil, err 685 } 686 687 switch { 688 case bytes.Contains(logs, []byte("Sentinel Result: true")): 689 pc.Status = tfe.PolicyPasses 690 case bytes.Contains(logs, []byte("Sentinel Result: false")): 691 switch { 692 case bytes.Contains(logs, []byte("hard-mandatory")): 693 pc.Status = tfe.PolicyHardFailed 694 case bytes.Contains(logs, []byte("soft-mandatory")): 695 pc.Actions.IsOverridable = true 696 pc.Permissions.CanOverride = true 697 pc.Status = tfe.PolicySoftFailed 698 } 699 default: 700 // As this is an unexpected state, we say the policy errored. 701 pc.Status = tfe.PolicyErrored 702 } 703 704 return bytes.NewBuffer(logs), nil 705 } 706 707 type MockRuns struct { 708 sync.Mutex 709 710 client *MockClient 711 Runs map[string]*tfe.Run 712 workspaces map[string][]*tfe.Run 713 714 // If ModifyNewRun is non-nil, the create method will call it just before 715 // saving a new run in the runs map, so that a calling test can mimic 716 // side-effects that a real server might apply in certain situations. 717 ModifyNewRun func(client *MockClient, options tfe.RunCreateOptions, run *tfe.Run) 718 } 719 720 func newMockRuns(client *MockClient) *MockRuns { 721 return &MockRuns{ 722 client: client, 723 Runs: make(map[string]*tfe.Run), 724 workspaces: make(map[string][]*tfe.Run), 725 } 726 } 727 728 func (m *MockRuns) List(ctx context.Context, workspaceID string, options *tfe.RunListOptions) (*tfe.RunList, error) { 729 m.Lock() 730 defer m.Unlock() 731 732 w, ok := m.client.Workspaces.workspaceIDs[workspaceID] 733 if !ok { 734 return nil, tfe.ErrResourceNotFound 735 } 736 737 rl := &tfe.RunList{} 738 for _, run := range m.workspaces[w.ID] { 739 rc, err := copystructure.Copy(run) 740 if err != nil { 741 panic(err) 742 } 743 rl.Items = append(rl.Items, rc.(*tfe.Run)) 744 } 745 746 rl.Pagination = &tfe.Pagination{ 747 CurrentPage: 1, 748 NextPage: 1, 749 PreviousPage: 1, 750 TotalPages: 1, 751 TotalCount: len(rl.Items), 752 } 753 754 return rl, nil 755 } 756 757 func (m *MockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*tfe.Run, error) { 758 m.Lock() 759 defer m.Unlock() 760 761 a, err := m.client.Applies.create(options.ConfigurationVersion.ID, options.Workspace.ID) 762 if err != nil { 763 return nil, err 764 } 765 766 ce, err := m.client.CostEstimates.create(options.ConfigurationVersion.ID, options.Workspace.ID) 767 if err != nil { 768 return nil, err 769 } 770 771 p, err := m.client.Plans.create(options.ConfigurationVersion.ID, options.Workspace.ID) 772 if err != nil { 773 return nil, err 774 } 775 776 pc, err := m.client.PolicyChecks.create(options.ConfigurationVersion.ID, options.Workspace.ID) 777 if err != nil { 778 return nil, err 779 } 780 781 r := &tfe.Run{ 782 ID: GenerateID("run-"), 783 Actions: &tfe.RunActions{IsCancelable: true}, 784 Apply: a, 785 CostEstimate: ce, 786 HasChanges: false, 787 Permissions: &tfe.RunPermissions{}, 788 Plan: p, 789 ReplaceAddrs: options.ReplaceAddrs, 790 Status: tfe.RunPending, 791 TargetAddrs: options.TargetAddrs, 792 } 793 794 if options.Message != nil { 795 r.Message = *options.Message 796 } 797 798 if pc != nil { 799 r.PolicyChecks = []*tfe.PolicyCheck{pc} 800 } 801 802 if options.IsDestroy != nil { 803 r.IsDestroy = *options.IsDestroy 804 } 805 806 if options.Refresh != nil { 807 r.Refresh = *options.Refresh 808 } 809 810 if options.RefreshOnly != nil { 811 r.RefreshOnly = *options.RefreshOnly 812 } 813 814 w, ok := m.client.Workspaces.workspaceIDs[options.Workspace.ID] 815 if !ok { 816 return nil, tfe.ErrResourceNotFound 817 } 818 if w.CurrentRun == nil { 819 w.CurrentRun = r 820 } 821 822 if m.ModifyNewRun != nil { 823 // caller-provided callback may modify the run in-place to mimic 824 // side-effects that a real server might take in some situations. 825 m.ModifyNewRun(m.client, options, r) 826 } 827 828 m.Runs[r.ID] = r 829 m.workspaces[options.Workspace.ID] = append(m.workspaces[options.Workspace.ID], r) 830 831 return r, nil 832 } 833 834 func (m *MockRuns) Read(ctx context.Context, runID string) (*tfe.Run, error) { 835 return m.ReadWithOptions(ctx, runID, nil) 836 } 837 838 func (m *MockRuns) ReadWithOptions(ctx context.Context, runID string, _ *tfe.RunReadOptions) (*tfe.Run, error) { 839 m.Lock() 840 defer m.Unlock() 841 842 r, ok := m.Runs[runID] 843 if !ok { 844 return nil, tfe.ErrResourceNotFound 845 } 846 847 pending := false 848 for _, r := range m.Runs { 849 if r.ID != runID && r.Status == tfe.RunPending { 850 pending = true 851 break 852 } 853 } 854 855 if !pending && r.Status == tfe.RunPending { 856 // Only update the status if there are no other pending runs. 857 r.Status = tfe.RunPlanning 858 r.Plan.Status = tfe.PlanRunning 859 } 860 861 logs, _ := ioutil.ReadFile(m.client.Plans.logs[r.Plan.LogReadURL]) 862 if r.Status == tfe.RunPlanning && r.Plan.Status == tfe.PlanFinished { 863 if r.IsDestroy || bytes.Contains(logs, []byte("1 to add, 0 to change, 0 to destroy")) { 864 r.Actions.IsCancelable = false 865 r.Actions.IsConfirmable = true 866 r.HasChanges = true 867 r.Permissions.CanApply = true 868 } 869 870 if bytes.Contains(logs, []byte("null_resource.foo: 1 error")) { 871 r.Actions.IsCancelable = false 872 r.HasChanges = false 873 r.Status = tfe.RunErrored 874 } 875 } 876 877 // we must return a copy for the client 878 rc, err := copystructure.Copy(r) 879 if err != nil { 880 panic(err) 881 } 882 883 return rc.(*tfe.Run), nil 884 } 885 886 func (m *MockRuns) Apply(ctx context.Context, runID string, options tfe.RunApplyOptions) error { 887 m.Lock() 888 defer m.Unlock() 889 890 r, ok := m.Runs[runID] 891 if !ok { 892 return tfe.ErrResourceNotFound 893 } 894 if r.Status != tfe.RunPending { 895 // Only update the status if the run is not pending anymore. 896 r.Status = tfe.RunApplying 897 r.Actions.IsConfirmable = false 898 r.Apply.Status = tfe.ApplyRunning 899 } 900 return nil 901 } 902 903 func (m *MockRuns) Cancel(ctx context.Context, runID string, options tfe.RunCancelOptions) error { 904 panic("not implemented") 905 } 906 907 func (m *MockRuns) ForceCancel(ctx context.Context, runID string, options tfe.RunForceCancelOptions) error { 908 panic("not implemented") 909 } 910 911 func (m *MockRuns) Discard(ctx context.Context, runID string, options tfe.RunDiscardOptions) error { 912 m.Lock() 913 defer m.Unlock() 914 915 r, ok := m.Runs[runID] 916 if !ok { 917 return tfe.ErrResourceNotFound 918 } 919 r.Status = tfe.RunDiscarded 920 r.Actions.IsConfirmable = false 921 return nil 922 } 923 924 type MockStateVersions struct { 925 client *MockClient 926 states map[string][]byte 927 stateVersions map[string]*tfe.StateVersion 928 workspaces map[string][]string 929 outputStates map[string][]byte 930 } 931 932 func newMockStateVersions(client *MockClient) *MockStateVersions { 933 return &MockStateVersions{ 934 client: client, 935 states: make(map[string][]byte), 936 stateVersions: make(map[string]*tfe.StateVersion), 937 workspaces: make(map[string][]string), 938 outputStates: make(map[string][]byte), 939 } 940 } 941 942 func (m *MockStateVersions) List(ctx context.Context, options *tfe.StateVersionListOptions) (*tfe.StateVersionList, error) { 943 svl := &tfe.StateVersionList{} 944 for _, sv := range m.stateVersions { 945 svl.Items = append(svl.Items, sv) 946 } 947 948 svl.Pagination = &tfe.Pagination{ 949 CurrentPage: 1, 950 NextPage: 1, 951 PreviousPage: 1, 952 TotalPages: 1, 953 TotalCount: len(svl.Items), 954 } 955 956 return svl, nil 957 } 958 959 func (m *MockStateVersions) Create(ctx context.Context, workspaceID string, options tfe.StateVersionCreateOptions) (*tfe.StateVersion, error) { 960 id := GenerateID("sv-") 961 runID := os.Getenv("TFE_RUN_ID") 962 url := fmt.Sprintf("https://app.durgaform.io/_archivist/%s", id) 963 964 if runID != "" && (options.Run == nil || runID != options.Run.ID) { 965 return nil, fmt.Errorf("option.Run.ID does not contain the ID exported by TFE_RUN_ID") 966 } 967 968 sv := &tfe.StateVersion{ 969 ID: id, 970 DownloadURL: url, 971 Serial: *options.Serial, 972 } 973 974 state, err := base64.StdEncoding.DecodeString(*options.State) 975 if err != nil { 976 return nil, err 977 } 978 979 m.states[sv.DownloadURL] = state 980 m.outputStates[sv.ID] = []byte(*options.JSONStateOutputs) 981 m.stateVersions[sv.ID] = sv 982 m.workspaces[workspaceID] = append(m.workspaces[workspaceID], sv.ID) 983 984 return sv, nil 985 } 986 987 func (m *MockStateVersions) Read(ctx context.Context, svID string) (*tfe.StateVersion, error) { 988 return m.ReadWithOptions(ctx, svID, nil) 989 } 990 991 func (m *MockStateVersions) ReadWithOptions(ctx context.Context, svID string, options *tfe.StateVersionReadOptions) (*tfe.StateVersion, error) { 992 sv, ok := m.stateVersions[svID] 993 if !ok { 994 return nil, tfe.ErrResourceNotFound 995 } 996 return sv, nil 997 } 998 999 func (m *MockStateVersions) ReadCurrent(ctx context.Context, workspaceID string) (*tfe.StateVersion, error) { 1000 return m.ReadCurrentWithOptions(ctx, workspaceID, nil) 1001 } 1002 1003 func (m *MockStateVersions) ReadCurrentWithOptions(ctx context.Context, workspaceID string, options *tfe.StateVersionCurrentOptions) (*tfe.StateVersion, error) { 1004 w, ok := m.client.Workspaces.workspaceIDs[workspaceID] 1005 if !ok { 1006 return nil, tfe.ErrResourceNotFound 1007 } 1008 1009 svs, ok := m.workspaces[w.ID] 1010 if !ok || len(svs) == 0 { 1011 return nil, tfe.ErrResourceNotFound 1012 } 1013 1014 sv, ok := m.stateVersions[svs[len(svs)-1]] 1015 if !ok { 1016 return nil, tfe.ErrResourceNotFound 1017 } 1018 1019 return sv, nil 1020 } 1021 1022 func (m *MockStateVersions) Download(ctx context.Context, url string) ([]byte, error) { 1023 state, ok := m.states[url] 1024 if !ok { 1025 return nil, tfe.ErrResourceNotFound 1026 } 1027 return state, nil 1028 } 1029 1030 func (m *MockStateVersions) ListOutputs(ctx context.Context, svID string, options *tfe.StateVersionOutputsListOptions) (*tfe.StateVersionOutputsList, error) { 1031 panic("not implemented") 1032 } 1033 1034 type MockStateVersionOutputs struct { 1035 client *MockClient 1036 outputs map[string]*tfe.StateVersionOutput 1037 } 1038 1039 func newMockStateVersionOutputs(client *MockClient) *MockStateVersionOutputs { 1040 return &MockStateVersionOutputs{ 1041 client: client, 1042 outputs: make(map[string]*tfe.StateVersionOutput), 1043 } 1044 } 1045 1046 // This is a helper function in order to create mocks to be read later 1047 func (m *MockStateVersionOutputs) create(id string, svo *tfe.StateVersionOutput) { 1048 m.outputs[id] = svo 1049 } 1050 1051 func (m *MockStateVersionOutputs) Read(ctx context.Context, outputID string) (*tfe.StateVersionOutput, error) { 1052 result, ok := m.outputs[outputID] 1053 if !ok { 1054 return nil, tfe.ErrResourceNotFound 1055 } 1056 1057 return result, nil 1058 } 1059 1060 func (m *MockStateVersionOutputs) ReadCurrent(ctx context.Context, workspaceID string) (*tfe.StateVersionOutputsList, error) { 1061 svl := &tfe.StateVersionOutputsList{} 1062 for _, sv := range m.outputs { 1063 svl.Items = append(svl.Items, sv) 1064 } 1065 1066 svl.Pagination = &tfe.Pagination{ 1067 CurrentPage: 1, 1068 NextPage: 1, 1069 PreviousPage: 1, 1070 TotalPages: 1, 1071 TotalCount: len(svl.Items), 1072 } 1073 1074 return svl, nil 1075 } 1076 1077 type MockVariables struct { 1078 client *MockClient 1079 workspaces map[string]*tfe.VariableList 1080 } 1081 1082 var _ tfe.Variables = (*MockVariables)(nil) 1083 1084 func newMockVariables(client *MockClient) *MockVariables { 1085 return &MockVariables{ 1086 client: client, 1087 workspaces: make(map[string]*tfe.VariableList), 1088 } 1089 } 1090 1091 func (m *MockVariables) List(ctx context.Context, workspaceID string, options *tfe.VariableListOptions) (*tfe.VariableList, error) { 1092 vl := m.workspaces[workspaceID] 1093 return vl, nil 1094 } 1095 1096 func (m *MockVariables) Create(ctx context.Context, workspaceID string, options tfe.VariableCreateOptions) (*tfe.Variable, error) { 1097 v := &tfe.Variable{ 1098 ID: GenerateID("var-"), 1099 Key: *options.Key, 1100 Category: *options.Category, 1101 } 1102 if options.Value != nil { 1103 v.Value = *options.Value 1104 } 1105 if options.HCL != nil { 1106 v.HCL = *options.HCL 1107 } 1108 if options.Sensitive != nil { 1109 v.Sensitive = *options.Sensitive 1110 } 1111 1112 workspace := workspaceID 1113 1114 if m.workspaces[workspace] == nil { 1115 m.workspaces[workspace] = &tfe.VariableList{} 1116 } 1117 1118 vl := m.workspaces[workspace] 1119 vl.Items = append(vl.Items, v) 1120 1121 return v, nil 1122 } 1123 1124 func (m *MockVariables) Read(ctx context.Context, workspaceID string, variableID string) (*tfe.Variable, error) { 1125 panic("not implemented") 1126 } 1127 1128 func (m *MockVariables) Update(ctx context.Context, workspaceID string, variableID string, options tfe.VariableUpdateOptions) (*tfe.Variable, error) { 1129 panic("not implemented") 1130 } 1131 1132 func (m *MockVariables) Delete(ctx context.Context, workspaceID string, variableID string) error { 1133 panic("not implemented") 1134 } 1135 1136 type MockWorkspaces struct { 1137 client *MockClient 1138 workspaceIDs map[string]*tfe.Workspace 1139 workspaceNames map[string]*tfe.Workspace 1140 } 1141 1142 func newMockWorkspaces(client *MockClient) *MockWorkspaces { 1143 return &MockWorkspaces{ 1144 client: client, 1145 workspaceIDs: make(map[string]*tfe.Workspace), 1146 workspaceNames: make(map[string]*tfe.Workspace), 1147 } 1148 } 1149 1150 func (m *MockWorkspaces) List(ctx context.Context, organization string, options *tfe.WorkspaceListOptions) (*tfe.WorkspaceList, error) { 1151 wl := &tfe.WorkspaceList{} 1152 // Get all the workspaces that match the Search value 1153 searchValue := "" 1154 var ws []*tfe.Workspace 1155 var tags []string 1156 1157 if options != nil { 1158 if len(options.Search) > 0 { 1159 searchValue = options.Search 1160 } 1161 if len(options.Tags) > 0 { 1162 tags = strings.Split(options.Tags, ",") 1163 } 1164 } 1165 1166 for _, w := range m.workspaceIDs { 1167 wTags := make(map[string]struct{}) 1168 for _, wTag := range w.Tags { 1169 wTags[wTag.Name] = struct{}{} 1170 } 1171 1172 if strings.Contains(w.Name, searchValue) { 1173 tagsSatisfied := true 1174 for _, tag := range tags { 1175 if _, ok := wTags[tag]; !ok { 1176 tagsSatisfied = false 1177 } 1178 } 1179 if tagsSatisfied { 1180 ws = append(ws, w) 1181 } 1182 } 1183 } 1184 1185 // Return an empty result if we have no matches. 1186 if len(ws) == 0 { 1187 wl.Pagination = &tfe.Pagination{ 1188 CurrentPage: 1, 1189 } 1190 return wl, nil 1191 } 1192 1193 numPages := (len(ws) / 20) + 1 1194 currentPage := 1 1195 if options != nil { 1196 if options.PageNumber != 0 { 1197 currentPage = options.PageNumber 1198 } 1199 } 1200 previousPage := currentPage - 1 1201 nextPage := currentPage + 1 1202 1203 for i := ((currentPage - 1) * 20); i < ((currentPage-1)*20)+20; i++ { 1204 if i > (len(ws) - 1) { 1205 break 1206 } 1207 wl.Items = append(wl.Items, ws[i]) 1208 } 1209 1210 wl.Pagination = &tfe.Pagination{ 1211 CurrentPage: currentPage, 1212 NextPage: nextPage, 1213 PreviousPage: previousPage, 1214 TotalPages: numPages, 1215 TotalCount: len(wl.Items), 1216 } 1217 1218 return wl, nil 1219 } 1220 1221 func (m *MockWorkspaces) Create(ctx context.Context, organization string, options tfe.WorkspaceCreateOptions) (*tfe.Workspace, error) { 1222 // for TestCloud_setUnavailableDurgaformVersion 1223 if *options.Name == "unavailable-durgaform-version" && options.TerraformVersion != nil { 1224 return nil, fmt.Errorf("requested Durgaform version not available in this TFC instance") 1225 } 1226 if strings.HasSuffix(*options.Name, "no-operations") { 1227 options.Operations = tfe.Bool(false) 1228 options.ExecutionMode = tfe.String("local") 1229 } else if options.Operations == nil { 1230 options.Operations = tfe.Bool(true) 1231 options.ExecutionMode = tfe.String("remote") 1232 } 1233 w := &tfe.Workspace{ 1234 ID: GenerateID("ws-"), 1235 Name: *options.Name, 1236 ExecutionMode: *options.ExecutionMode, 1237 Operations: *options.Operations, 1238 Permissions: &tfe.WorkspacePermissions{ 1239 CanQueueApply: true, 1240 CanQueueRun: true, 1241 }, 1242 } 1243 if options.AutoApply != nil { 1244 w.AutoApply = *options.AutoApply 1245 } 1246 if options.VCSRepo != nil { 1247 w.VCSRepo = &tfe.VCSRepo{} 1248 } 1249 if options.TerraformVersion != nil { 1250 w.TerraformVersion = *options.TerraformVersion 1251 } else { 1252 w.TerraformVersion = tfversion.String() 1253 } 1254 var tags []*tfe.Tag 1255 for _, tag := range options.Tags { 1256 tags = append(tags, tag) 1257 w.TagNames = append(w.TagNames, tag.Name) 1258 } 1259 w.Tags = tags 1260 m.workspaceIDs[w.ID] = w 1261 m.workspaceNames[w.Name] = w 1262 return w, nil 1263 } 1264 1265 func (m *MockWorkspaces) Read(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) { 1266 // custom error for TestCloud_plan500 in backend_plan_test.go 1267 if workspace == "network-error" { 1268 return nil, errors.New("I'm a little teacup") 1269 } 1270 1271 w, ok := m.workspaceNames[workspace] 1272 if !ok { 1273 return nil, tfe.ErrResourceNotFound 1274 } 1275 return w, nil 1276 } 1277 1278 func (m *MockWorkspaces) ReadByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { 1279 w, ok := m.workspaceIDs[workspaceID] 1280 if !ok { 1281 return nil, tfe.ErrResourceNotFound 1282 } 1283 return w, nil 1284 } 1285 1286 func (m *MockWorkspaces) ReadWithOptions(ctx context.Context, organization string, workspace string, options *tfe.WorkspaceReadOptions) (*tfe.Workspace, error) { 1287 panic("not implemented") 1288 } 1289 1290 func (m *MockWorkspaces) ReadByIDWithOptions(ctx context.Context, workspaceID string, options *tfe.WorkspaceReadOptions) (*tfe.Workspace, error) { 1291 w, ok := m.workspaceIDs[workspaceID] 1292 if !ok { 1293 return nil, tfe.ErrResourceNotFound 1294 } 1295 return w, nil 1296 } 1297 1298 func (m *MockWorkspaces) Update(ctx context.Context, organization, workspace string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) { 1299 w, ok := m.workspaceNames[workspace] 1300 if !ok { 1301 return nil, tfe.ErrResourceNotFound 1302 } 1303 1304 err := updateMockWorkspaceAttributes(w, options) 1305 if err != nil { 1306 return nil, err 1307 } 1308 1309 delete(m.workspaceNames, workspace) 1310 m.workspaceNames[w.Name] = w 1311 1312 return w, nil 1313 } 1314 1315 func (m *MockWorkspaces) UpdateByID(ctx context.Context, workspaceID string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) { 1316 w, ok := m.workspaceIDs[workspaceID] 1317 if !ok { 1318 return nil, tfe.ErrResourceNotFound 1319 } 1320 1321 originalName := w.Name 1322 err := updateMockWorkspaceAttributes(w, options) 1323 if err != nil { 1324 return nil, err 1325 } 1326 1327 delete(m.workspaceNames, originalName) 1328 m.workspaceNames[w.Name] = w 1329 1330 return w, nil 1331 } 1332 1333 func updateMockWorkspaceAttributes(w *tfe.Workspace, options tfe.WorkspaceUpdateOptions) error { 1334 // for TestCloud_setUnavailableDurgaformVersion 1335 if w.Name == "unavailable-durgaform-version" && options.TerraformVersion != nil { 1336 return fmt.Errorf("requested Durgaform version not available in this TFC instance") 1337 } 1338 1339 if options.Operations != nil { 1340 w.Operations = *options.Operations 1341 } 1342 if options.ExecutionMode != nil { 1343 w.ExecutionMode = *options.ExecutionMode 1344 } 1345 if options.Name != nil { 1346 w.Name = *options.Name 1347 } 1348 if options.TerraformVersion != nil { 1349 w.TerraformVersion = *options.TerraformVersion 1350 } 1351 if options.WorkingDirectory != nil { 1352 w.WorkingDirectory = *options.WorkingDirectory 1353 } 1354 return nil 1355 } 1356 1357 func (m *MockWorkspaces) Delete(ctx context.Context, organization, workspace string) error { 1358 if w, ok := m.workspaceNames[workspace]; ok { 1359 delete(m.workspaceIDs, w.ID) 1360 } 1361 delete(m.workspaceNames, workspace) 1362 return nil 1363 } 1364 1365 func (m *MockWorkspaces) DeleteByID(ctx context.Context, workspaceID string) error { 1366 if w, ok := m.workspaceIDs[workspaceID]; ok { 1367 delete(m.workspaceIDs, w.Name) 1368 } 1369 delete(m.workspaceIDs, workspaceID) 1370 return nil 1371 } 1372 1373 func (m *MockWorkspaces) RemoveVCSConnection(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) { 1374 w, ok := m.workspaceNames[workspace] 1375 if !ok { 1376 return nil, tfe.ErrResourceNotFound 1377 } 1378 w.VCSRepo = nil 1379 return w, nil 1380 } 1381 1382 func (m *MockWorkspaces) RemoveVCSConnectionByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { 1383 w, ok := m.workspaceIDs[workspaceID] 1384 if !ok { 1385 return nil, tfe.ErrResourceNotFound 1386 } 1387 w.VCSRepo = nil 1388 return w, nil 1389 } 1390 1391 func (m *MockWorkspaces) Lock(ctx context.Context, workspaceID string, options tfe.WorkspaceLockOptions) (*tfe.Workspace, error) { 1392 w, ok := m.workspaceIDs[workspaceID] 1393 if !ok { 1394 return nil, tfe.ErrResourceNotFound 1395 } 1396 if w.Locked { 1397 return nil, tfe.ErrWorkspaceLocked 1398 } 1399 w.Locked = true 1400 return w, nil 1401 } 1402 1403 func (m *MockWorkspaces) Unlock(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { 1404 w, ok := m.workspaceIDs[workspaceID] 1405 if !ok { 1406 return nil, tfe.ErrResourceNotFound 1407 } 1408 if !w.Locked { 1409 return nil, tfe.ErrWorkspaceNotLocked 1410 } 1411 w.Locked = false 1412 return w, nil 1413 } 1414 1415 func (m *MockWorkspaces) ForceUnlock(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { 1416 w, ok := m.workspaceIDs[workspaceID] 1417 if !ok { 1418 return nil, tfe.ErrResourceNotFound 1419 } 1420 if !w.Locked { 1421 return nil, tfe.ErrWorkspaceNotLocked 1422 } 1423 w.Locked = false 1424 return w, nil 1425 } 1426 1427 func (m *MockWorkspaces) AssignSSHKey(ctx context.Context, workspaceID string, options tfe.WorkspaceAssignSSHKeyOptions) (*tfe.Workspace, error) { 1428 panic("not implemented") 1429 } 1430 1431 func (m *MockWorkspaces) UnassignSSHKey(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { 1432 panic("not implemented") 1433 } 1434 1435 func (m *MockWorkspaces) ListRemoteStateConsumers(ctx context.Context, workspaceID string, options *tfe.RemoteStateConsumersListOptions) (*tfe.WorkspaceList, error) { 1436 panic("not implemented") 1437 } 1438 1439 func (m *MockWorkspaces) AddRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceAddRemoteStateConsumersOptions) error { 1440 panic("not implemented") 1441 } 1442 1443 func (m *MockWorkspaces) RemoveRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceRemoveRemoteStateConsumersOptions) error { 1444 panic("not implemented") 1445 } 1446 1447 func (m *MockWorkspaces) UpdateRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceUpdateRemoteStateConsumersOptions) error { 1448 panic("not implemented") 1449 } 1450 1451 func (m *MockWorkspaces) Readme(ctx context.Context, workspaceID string) (io.Reader, error) { 1452 panic("not implemented") 1453 } 1454 1455 func (m *MockWorkspaces) ListTags(ctx context.Context, workspaceID string, options *tfe.WorkspaceTagListOptions) (*tfe.TagList, error) { 1456 panic("not implemented") 1457 } 1458 1459 func (m *MockWorkspaces) AddTags(ctx context.Context, workspaceID string, options tfe.WorkspaceAddTagsOptions) error { 1460 return nil 1461 } 1462 1463 func (m *MockWorkspaces) RemoveTags(ctx context.Context, workspaceID string, options tfe.WorkspaceRemoveTagsOptions) error { 1464 panic("not implemented") 1465 } 1466 1467 const alphanumeric = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 1468 1469 func GenerateID(s string) string { 1470 b := make([]byte, 16) 1471 for i := range b { 1472 b[i] = alphanumeric[rand.Intn(len(alphanumeric))] 1473 } 1474 return s + string(b) 1475 }