github.com/opentofu/opentofu@v1.7.1/internal/cloud/tfe_client_mock.go (about)

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