kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/backend/remote/backend_mock.go (about)

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