github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote/backend_mock.go (about)

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