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