github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/cloud/tfe_client_mock.go (about)

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