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