github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/cloud/tfe_client_mock.go (about)

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