github.com/kevinklinger/open_terraform@v1.3.6/noninternal/cloud/tfe_client_mock.go (about)

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