diff --git a/go.mod b/go.mod index 646309e2a..9074b31d8 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-plugin v1.4.3 github.com/hashicorp/go-retryablehttp v0.5.2 - github.com/hashicorp/go-tfe v0.15.0 + github.com/hashicorp/go-tfe v0.18.1-0.20210902165242-26689edbfddf github.com/hashicorp/go-uuid v1.0.1 github.com/hashicorp/go-version v1.2.1 github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f @@ -143,9 +143,9 @@ require ( github.com/hashicorp/go-msgpack v0.5.4 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-slug v0.4.1 // indirect + github.com/hashicorp/go-slug v0.7.0 // indirect github.com/hashicorp/golang-lru v0.5.1 // indirect - github.com/hashicorp/jsonapi v0.0.0-20210518035559-1e50d74c8db3 // indirect + github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d // indirect github.com/hashicorp/serf v0.9.5 // indirect github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect github.com/huandu/xstrings v1.3.2 // indirect diff --git a/go.sum b/go.sum index bd8b487fb..4a2064597 100644 --- a/go.sum +++ b/go.sum @@ -370,13 +370,13 @@ github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5O github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= -github.com/hashicorp/go-slug v0.4.1 h1:/jAo8dNuLgSImoLXaX7Od7QB4TfYCVPam+OpAt5bZqc= -github.com/hashicorp/go-slug v0.4.1/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8= +github.com/hashicorp/go-slug v0.7.0 h1:8HIi6oreWPtnhpYd8lIGQBgp4rXzDWQTOhfILZm+nok= +github.com/hashicorp/go-slug v0.7.0/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4= github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-tfe v0.15.0 h1:vdnz1NjOhvmap+cj8iPsL8SbS4iFFVuNYFkGpF5SdoA= -github.com/hashicorp/go-tfe v0.15.0/go.mod h1:c8glB5p6XzocEWLNkuy5RxcjqN5X2PpY6NF3f2W6nIo= +github.com/hashicorp/go-tfe v0.18.1-0.20210902165242-26689edbfddf h1:Tn5cI9kacNyO40ztxmwfAaHrOGd7dELLSAueV2Xfv38= +github.com/hashicorp/go-tfe v0.18.1-0.20210902165242-26689edbfddf/go.mod h1:7lChm1Mjsh0ofrUNkP8MHljUFrnKNZNTw36S6qSbJZU= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -393,8 +393,8 @@ github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90= github.com/hashicorp/hcl/v2 v2.10.1 h1:h4Xx4fsrRE26ohAk/1iGF/JBqRQbyUqu5Lvj60U54ys= github.com/hashicorp/hcl/v2 v2.10.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= -github.com/hashicorp/jsonapi v0.0.0-20210518035559-1e50d74c8db3 h1:mzwkutymYIXR5oQT9YnfbLuuw7LZmksiHKRPUTN5ijo= -github.com/hashicorp/jsonapi v0.0.0-20210518035559-1e50d74c8db3/go.mod h1:Yog5+CPEM3c99L1CL2CFCYoSzgWm5vTU58idbRUaLik= +github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d h1:9ARUJJ1VVynB176G1HCwleORqCaXm/Vx0uUi0dL26I0= +github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d/go.mod h1:Yog5+CPEM3c99L1CL2CFCYoSzgWm5vTU58idbRUaLik= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g= @@ -610,8 +610,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.232 h1:kwsWbh4rEw42ZDe9/812ebhbwNZxlQyZ2sTmxBOKhN4= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.232/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= diff --git a/internal/backend/remote/backend_apply_test.go b/internal/backend/remote/backend_apply_test.go index e15fe0bb3..9b4286010 100644 --- a/internal/backend/remote/backend_apply_test.go +++ b/internal/backend/remote/backend_apply_test.go @@ -14,6 +14,7 @@ import ( version "github.com/hashicorp/go-version" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/backend" + "github.com/hashicorp/terraform/internal/cloud" "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/command/clistate" "github.com/hashicorp/terraform/internal/command/views" @@ -308,11 +309,11 @@ func TestRemote_applyWithoutRefresh(t *testing.T) { // We should find a run inside the mock client that has refresh set // to false. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*cloud.MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff(false, run.Refresh); diff != "" { t.Errorf("wrong Refresh setting in the created run\n%s", diff) } @@ -377,11 +378,11 @@ func TestRemote_applyWithRefreshOnly(t *testing.T) { // We should find a run inside the mock client that has refresh-only set // to true. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*cloud.MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff(true, run.RefreshOnly); diff != "" { t.Errorf("wrong RefreshOnly setting in the created run\n%s", diff) } @@ -448,11 +449,11 @@ func TestRemote_applyWithTarget(t *testing.T) { // We should find a run inside the mock client that has the same // target address we requested above. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*cloud.MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff([]string{"null_resource.foo"}, run.TargetAddrs); diff != "" { t.Errorf("wrong TargetAddrs in the created run\n%s", diff) } @@ -523,11 +524,11 @@ func TestRemote_applyWithReplace(t *testing.T) { // We should find a run inside the mock client that has the same // refresh address we requested above. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*cloud.MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff([]string{"null_resource.foo"}, run.ReplaceAddrs); diff != "" { t.Errorf("wrong ReplaceAddrs in the created run\n%s", diff) } diff --git a/internal/backend/remote/backend_mock.go b/internal/backend/remote/backend_mock.go deleted file mode 100644 index abf150f7c..000000000 --- a/internal/backend/remote/backend_mock.go +++ /dev/null @@ -1,1364 +0,0 @@ -package remote - -import ( - "bytes" - "context" - "encoding/base64" - "errors" - "fmt" - "io" - "io/ioutil" - "math/rand" - "os" - "path/filepath" - "strings" - "sync" - "time" - - tfe "github.com/hashicorp/go-tfe" - "github.com/hashicorp/terraform/internal/terraform" - tfversion "github.com/hashicorp/terraform/version" - "github.com/mitchellh/copystructure" -) - -type mockClient struct { - Applies *mockApplies - ConfigurationVersions *mockConfigurationVersions - CostEstimates *mockCostEstimates - Organizations *mockOrganizations - Plans *mockPlans - PolicyChecks *mockPolicyChecks - Runs *mockRuns - StateVersions *mockStateVersions - Variables *mockVariables - Workspaces *mockWorkspaces -} - -func newMockClient() *mockClient { - c := &mockClient{} - c.Applies = newMockApplies(c) - c.ConfigurationVersions = newMockConfigurationVersions(c) - c.CostEstimates = newMockCostEstimates(c) - c.Organizations = newMockOrganizations(c) - c.Plans = newMockPlans(c) - c.PolicyChecks = newMockPolicyChecks(c) - c.Runs = newMockRuns(c) - c.StateVersions = newMockStateVersions(c) - c.Variables = newMockVariables(c) - c.Workspaces = newMockWorkspaces(c) - return c -} - -type mockApplies struct { - client *mockClient - applies map[string]*tfe.Apply - logs map[string]string -} - -func newMockApplies(client *mockClient) *mockApplies { - return &mockApplies{ - client: client, - applies: make(map[string]*tfe.Apply), - logs: make(map[string]string), - } -} - -// create is a helper function to create a mock apply that uses the configured -// working directory to find the logfile. -func (m *mockApplies) create(cvID, workspaceID string) (*tfe.Apply, error) { - c, ok := m.client.ConfigurationVersions.configVersions[cvID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - if c.Speculative { - // Speculative means its plan-only so we don't create a Apply. - return nil, nil - } - - id := generateID("apply-") - url := fmt.Sprintf("https://app.terraform.io/_archivist/%s", id) - - a := &tfe.Apply{ - ID: id, - LogReadURL: url, - Status: tfe.ApplyPending, - } - - w, ok := m.client.Workspaces.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - if w.AutoApply { - a.Status = tfe.ApplyRunning - } - - m.logs[url] = filepath.Join( - m.client.ConfigurationVersions.uploadPaths[cvID], - w.WorkingDirectory, - "apply.log", - ) - m.applies[a.ID] = a - - return a, nil -} - -func (m *mockApplies) Read(ctx context.Context, applyID string) (*tfe.Apply, error) { - a, ok := m.applies[applyID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - // Together with the mockLogReader this allows testing queued runs. - if a.Status == tfe.ApplyRunning { - a.Status = tfe.ApplyFinished - } - return a, nil -} - -func (m *mockApplies) Logs(ctx context.Context, applyID string) (io.Reader, error) { - a, err := m.Read(ctx, applyID) - if err != nil { - return nil, err - } - - logfile, ok := m.logs[a.LogReadURL] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - if _, err := os.Stat(logfile); os.IsNotExist(err) { - return bytes.NewBufferString("logfile does not exist"), nil - } - - logs, err := ioutil.ReadFile(logfile) - if err != nil { - return nil, err - } - - done := func() (bool, error) { - a, err := m.Read(ctx, applyID) - if err != nil { - return false, err - } - if a.Status != tfe.ApplyFinished { - return false, nil - } - return true, nil - } - - return &mockLogReader{ - done: done, - logs: bytes.NewBuffer(logs), - }, nil -} - -type mockConfigurationVersions struct { - client *mockClient - configVersions map[string]*tfe.ConfigurationVersion - uploadPaths map[string]string - uploadURLs map[string]*tfe.ConfigurationVersion -} - -func newMockConfigurationVersions(client *mockClient) *mockConfigurationVersions { - return &mockConfigurationVersions{ - client: client, - configVersions: make(map[string]*tfe.ConfigurationVersion), - uploadPaths: make(map[string]string), - uploadURLs: make(map[string]*tfe.ConfigurationVersion), - } -} - -func (m *mockConfigurationVersions) List(ctx context.Context, workspaceID string, options tfe.ConfigurationVersionListOptions) (*tfe.ConfigurationVersionList, error) { - cvl := &tfe.ConfigurationVersionList{} - for _, cv := range m.configVersions { - cvl.Items = append(cvl.Items, cv) - } - - cvl.Pagination = &tfe.Pagination{ - CurrentPage: 1, - NextPage: 1, - PreviousPage: 1, - TotalPages: 1, - TotalCount: len(cvl.Items), - } - - return cvl, nil -} - -func (m *mockConfigurationVersions) Create(ctx context.Context, workspaceID string, options tfe.ConfigurationVersionCreateOptions) (*tfe.ConfigurationVersion, error) { - id := generateID("cv-") - url := fmt.Sprintf("https://app.terraform.io/_archivist/%s", id) - - cv := &tfe.ConfigurationVersion{ - ID: id, - Status: tfe.ConfigurationPending, - UploadURL: url, - } - - m.configVersions[cv.ID] = cv - m.uploadURLs[url] = cv - - return cv, nil -} - -func (m *mockConfigurationVersions) Read(ctx context.Context, cvID string) (*tfe.ConfigurationVersion, error) { - cv, ok := m.configVersions[cvID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - return cv, nil -} - -func (m *mockConfigurationVersions) Upload(ctx context.Context, url, path string) error { - cv, ok := m.uploadURLs[url] - if !ok { - return errors.New("404 not found") - } - m.uploadPaths[cv.ID] = path - cv.Status = tfe.ConfigurationUploaded - return nil -} - -type mockCostEstimates struct { - client *mockClient - estimations map[string]*tfe.CostEstimate - logs map[string]string -} - -func newMockCostEstimates(client *mockClient) *mockCostEstimates { - return &mockCostEstimates{ - client: client, - estimations: make(map[string]*tfe.CostEstimate), - logs: make(map[string]string), - } -} - -// create is a helper function to create a mock cost estimation that uses the -// configured working directory to find the logfile. -func (m *mockCostEstimates) create(cvID, workspaceID string) (*tfe.CostEstimate, error) { - id := generateID("ce-") - - ce := &tfe.CostEstimate{ - ID: id, - MatchedResourcesCount: 1, - ResourcesCount: 1, - DeltaMonthlyCost: "0.00", - ProposedMonthlyCost: "0.00", - Status: tfe.CostEstimateFinished, - } - - w, ok := m.client.Workspaces.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - logfile := filepath.Join( - m.client.ConfigurationVersions.uploadPaths[cvID], - w.WorkingDirectory, - "cost-estimate.log", - ) - - if _, err := os.Stat(logfile); os.IsNotExist(err) { - return nil, nil - } - - m.logs[ce.ID] = logfile - m.estimations[ce.ID] = ce - - return ce, nil -} - -func (m *mockCostEstimates) Read(ctx context.Context, costEstimateID string) (*tfe.CostEstimate, error) { - ce, ok := m.estimations[costEstimateID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - return ce, nil -} - -func (m *mockCostEstimates) Logs(ctx context.Context, costEstimateID string) (io.Reader, error) { - ce, ok := m.estimations[costEstimateID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - logfile, ok := m.logs[ce.ID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - if _, err := os.Stat(logfile); os.IsNotExist(err) { - return bytes.NewBufferString("logfile does not exist"), nil - } - - logs, err := ioutil.ReadFile(logfile) - if err != nil { - return nil, err - } - - ce.Status = tfe.CostEstimateFinished - - return bytes.NewBuffer(logs), nil -} - -// mockInput is a mock implementation of terraform.UIInput. -type mockInput struct { - answers map[string]string -} - -func (m *mockInput) Input(ctx context.Context, opts *terraform.InputOpts) (string, error) { - v, ok := m.answers[opts.Id] - if !ok { - return "", fmt.Errorf("unexpected input request in test: %s", opts.Id) - } - if v == "wait-for-external-update" { - select { - case <-ctx.Done(): - case <-time.After(time.Minute): - } - } - delete(m.answers, opts.Id) - return v, nil -} - -type mockOrganizations struct { - client *mockClient - organizations map[string]*tfe.Organization -} - -func newMockOrganizations(client *mockClient) *mockOrganizations { - return &mockOrganizations{ - client: client, - organizations: make(map[string]*tfe.Organization), - } -} - -func (m *mockOrganizations) List(ctx context.Context, options tfe.OrganizationListOptions) (*tfe.OrganizationList, error) { - orgl := &tfe.OrganizationList{} - for _, org := range m.organizations { - orgl.Items = append(orgl.Items, org) - } - - orgl.Pagination = &tfe.Pagination{ - CurrentPage: 1, - NextPage: 1, - PreviousPage: 1, - TotalPages: 1, - TotalCount: len(orgl.Items), - } - - return orgl, nil -} - -// mockLogReader is a mock logreader that enables testing queued runs. -type mockLogReader struct { - done func() (bool, error) - logs *bytes.Buffer -} - -func (m *mockLogReader) Read(l []byte) (int, error) { - for { - if written, err := m.read(l); err != io.ErrNoProgress { - return written, err - } - time.Sleep(1 * time.Millisecond) - } -} - -func (m *mockLogReader) read(l []byte) (int, error) { - done, err := m.done() - if err != nil { - return 0, err - } - if !done { - return 0, io.ErrNoProgress - } - return m.logs.Read(l) -} - -func (m *mockOrganizations) Create(ctx context.Context, options tfe.OrganizationCreateOptions) (*tfe.Organization, error) { - org := &tfe.Organization{Name: *options.Name} - m.organizations[org.Name] = org - return org, nil -} - -func (m *mockOrganizations) Read(ctx context.Context, name string) (*tfe.Organization, error) { - org, ok := m.organizations[name] - if !ok { - return nil, tfe.ErrResourceNotFound - } - return org, nil -} - -func (m *mockOrganizations) Update(ctx context.Context, name string, options tfe.OrganizationUpdateOptions) (*tfe.Organization, error) { - org, ok := m.organizations[name] - if !ok { - return nil, tfe.ErrResourceNotFound - } - org.Name = *options.Name - return org, nil - -} - -func (m *mockOrganizations) Delete(ctx context.Context, name string) error { - delete(m.organizations, name) - return nil -} - -func (m *mockOrganizations) Capacity(ctx context.Context, name string) (*tfe.Capacity, error) { - var pending, running int - for _, r := range m.client.Runs.runs { - if r.Status == tfe.RunPending { - pending++ - continue - } - running++ - } - return &tfe.Capacity{Pending: pending, Running: running}, nil -} - -func (m *mockOrganizations) Entitlements(ctx context.Context, name string) (*tfe.Entitlements, error) { - return &tfe.Entitlements{ - Operations: true, - PrivateModuleRegistry: true, - Sentinel: true, - StateStorage: true, - Teams: true, - VCSIntegrations: true, - }, nil -} - -func (m *mockOrganizations) RunQueue(ctx context.Context, name string, options tfe.RunQueueOptions) (*tfe.RunQueue, error) { - rq := &tfe.RunQueue{} - - for _, r := range m.client.Runs.runs { - rq.Items = append(rq.Items, r) - } - - rq.Pagination = &tfe.Pagination{ - CurrentPage: 1, - NextPage: 1, - PreviousPage: 1, - TotalPages: 1, - TotalCount: len(rq.Items), - } - - return rq, nil -} - -type mockPlans struct { - client *mockClient - logs map[string]string - planOutputs map[string]string - plans map[string]*tfe.Plan -} - -func newMockPlans(client *mockClient) *mockPlans { - return &mockPlans{ - client: client, - logs: make(map[string]string), - planOutputs: make(map[string]string), - plans: make(map[string]*tfe.Plan), - } -} - -// create is a helper function to create a mock plan that uses the configured -// working directory to find the logfile. -func (m *mockPlans) create(cvID, workspaceID string) (*tfe.Plan, error) { - id := generateID("plan-") - url := fmt.Sprintf("https://app.terraform.io/_archivist/%s", id) - - p := &tfe.Plan{ - ID: id, - LogReadURL: url, - Status: tfe.PlanPending, - } - - w, ok := m.client.Workspaces.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - m.logs[url] = filepath.Join( - m.client.ConfigurationVersions.uploadPaths[cvID], - w.WorkingDirectory, - "plan.log", - ) - m.plans[p.ID] = p - - return p, nil -} - -func (m *mockPlans) Read(ctx context.Context, planID string) (*tfe.Plan, error) { - p, ok := m.plans[planID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - // Together with the mockLogReader this allows testing queued runs. - if p.Status == tfe.PlanRunning { - p.Status = tfe.PlanFinished - } - return p, nil -} - -func (m *mockPlans) Logs(ctx context.Context, planID string) (io.Reader, error) { - p, err := m.Read(ctx, planID) - if err != nil { - return nil, err - } - - logfile, ok := m.logs[p.LogReadURL] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - if _, err := os.Stat(logfile); os.IsNotExist(err) { - return bytes.NewBufferString("logfile does not exist"), nil - } - - logs, err := ioutil.ReadFile(logfile) - if err != nil { - return nil, err - } - - done := func() (bool, error) { - p, err := m.Read(ctx, planID) - if err != nil { - return false, err - } - if p.Status != tfe.PlanFinished { - return false, nil - } - return true, nil - } - - return &mockLogReader{ - done: done, - logs: bytes.NewBuffer(logs), - }, nil -} - -func (m *mockPlans) JSONOutput(ctx context.Context, planID string) ([]byte, error) { - planOutput, ok := m.planOutputs[planID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - return []byte(planOutput), nil -} - -type mockPolicyChecks struct { - client *mockClient - checks map[string]*tfe.PolicyCheck - logs map[string]string -} - -func newMockPolicyChecks(client *mockClient) *mockPolicyChecks { - return &mockPolicyChecks{ - client: client, - checks: make(map[string]*tfe.PolicyCheck), - logs: make(map[string]string), - } -} - -// create is a helper function to create a mock policy check that uses the -// configured working directory to find the logfile. -func (m *mockPolicyChecks) create(cvID, workspaceID string) (*tfe.PolicyCheck, error) { - id := generateID("pc-") - - pc := &tfe.PolicyCheck{ - ID: id, - Actions: &tfe.PolicyActions{}, - Permissions: &tfe.PolicyPermissions{}, - Scope: tfe.PolicyScopeOrganization, - Status: tfe.PolicyPending, - } - - w, ok := m.client.Workspaces.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - logfile := filepath.Join( - m.client.ConfigurationVersions.uploadPaths[cvID], - w.WorkingDirectory, - "policy.log", - ) - - if _, err := os.Stat(logfile); os.IsNotExist(err) { - return nil, nil - } - - m.logs[pc.ID] = logfile - m.checks[pc.ID] = pc - - return pc, nil -} - -func (m *mockPolicyChecks) List(ctx context.Context, runID string, options tfe.PolicyCheckListOptions) (*tfe.PolicyCheckList, error) { - _, ok := m.client.Runs.runs[runID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - pcl := &tfe.PolicyCheckList{} - for _, pc := range m.checks { - pcl.Items = append(pcl.Items, pc) - } - - pcl.Pagination = &tfe.Pagination{ - CurrentPage: 1, - NextPage: 1, - PreviousPage: 1, - TotalPages: 1, - TotalCount: len(pcl.Items), - } - - return pcl, nil -} - -func (m *mockPolicyChecks) Read(ctx context.Context, policyCheckID string) (*tfe.PolicyCheck, error) { - pc, ok := m.checks[policyCheckID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - logfile, ok := m.logs[pc.ID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - if _, err := os.Stat(logfile); os.IsNotExist(err) { - return nil, fmt.Errorf("logfile does not exist") - } - - logs, err := ioutil.ReadFile(logfile) - if err != nil { - return nil, err - } - - switch { - case bytes.Contains(logs, []byte("Sentinel Result: true")): - pc.Status = tfe.PolicyPasses - case bytes.Contains(logs, []byte("Sentinel Result: false")): - switch { - case bytes.Contains(logs, []byte("hard-mandatory")): - pc.Status = tfe.PolicyHardFailed - case bytes.Contains(logs, []byte("soft-mandatory")): - pc.Actions.IsOverridable = true - pc.Permissions.CanOverride = true - pc.Status = tfe.PolicySoftFailed - } - default: - // As this is an unexpected state, we say the policy errored. - pc.Status = tfe.PolicyErrored - } - - return pc, nil -} - -func (m *mockPolicyChecks) Override(ctx context.Context, policyCheckID string) (*tfe.PolicyCheck, error) { - pc, ok := m.checks[policyCheckID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - pc.Status = tfe.PolicyOverridden - return pc, nil -} - -func (m *mockPolicyChecks) Logs(ctx context.Context, policyCheckID string) (io.Reader, error) { - pc, ok := m.checks[policyCheckID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - logfile, ok := m.logs[pc.ID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - if _, err := os.Stat(logfile); os.IsNotExist(err) { - return bytes.NewBufferString("logfile does not exist"), nil - } - - logs, err := ioutil.ReadFile(logfile) - if err != nil { - return nil, err - } - - switch { - case bytes.Contains(logs, []byte("Sentinel Result: true")): - pc.Status = tfe.PolicyPasses - case bytes.Contains(logs, []byte("Sentinel Result: false")): - switch { - case bytes.Contains(logs, []byte("hard-mandatory")): - pc.Status = tfe.PolicyHardFailed - case bytes.Contains(logs, []byte("soft-mandatory")): - pc.Actions.IsOverridable = true - pc.Permissions.CanOverride = true - pc.Status = tfe.PolicySoftFailed - } - default: - // As this is an unexpected state, we say the policy errored. - pc.Status = tfe.PolicyErrored - } - - return bytes.NewBuffer(logs), nil -} - -type mockRuns struct { - sync.Mutex - - client *mockClient - runs map[string]*tfe.Run - workspaces map[string][]*tfe.Run - - // If modifyNewRun is non-nil, the create method will call it just before - // saving a new run in the runs map, so that a calling test can mimic - // side-effects that a real server might apply in certain situations. - modifyNewRun func(client *mockClient, options tfe.RunCreateOptions, run *tfe.Run) -} - -func newMockRuns(client *mockClient) *mockRuns { - return &mockRuns{ - client: client, - runs: make(map[string]*tfe.Run), - workspaces: make(map[string][]*tfe.Run), - } -} - -func (m *mockRuns) List(ctx context.Context, workspaceID string, options tfe.RunListOptions) (*tfe.RunList, error) { - m.Lock() - defer m.Unlock() - - w, ok := m.client.Workspaces.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - rl := &tfe.RunList{} - for _, run := range m.workspaces[w.ID] { - rc, err := copystructure.Copy(run) - if err != nil { - panic(err) - } - rl.Items = append(rl.Items, rc.(*tfe.Run)) - } - - rl.Pagination = &tfe.Pagination{ - CurrentPage: 1, - NextPage: 1, - PreviousPage: 1, - TotalPages: 1, - TotalCount: len(rl.Items), - } - - return rl, nil -} - -func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*tfe.Run, error) { - m.Lock() - defer m.Unlock() - - a, err := m.client.Applies.create(options.ConfigurationVersion.ID, options.Workspace.ID) - if err != nil { - return nil, err - } - - ce, err := m.client.CostEstimates.create(options.ConfigurationVersion.ID, options.Workspace.ID) - if err != nil { - return nil, err - } - - p, err := m.client.Plans.create(options.ConfigurationVersion.ID, options.Workspace.ID) - if err != nil { - return nil, err - } - - pc, err := m.client.PolicyChecks.create(options.ConfigurationVersion.ID, options.Workspace.ID) - if err != nil { - return nil, err - } - - r := &tfe.Run{ - ID: generateID("run-"), - Actions: &tfe.RunActions{IsCancelable: true}, - Apply: a, - CostEstimate: ce, - HasChanges: false, - Permissions: &tfe.RunPermissions{}, - Plan: p, - ReplaceAddrs: options.ReplaceAddrs, - Status: tfe.RunPending, - TargetAddrs: options.TargetAddrs, - } - - if options.Message != nil { - r.Message = *options.Message - } - - if pc != nil { - r.PolicyChecks = []*tfe.PolicyCheck{pc} - } - - if options.IsDestroy != nil { - r.IsDestroy = *options.IsDestroy - } - - if options.Refresh != nil { - r.Refresh = *options.Refresh - } - - if options.RefreshOnly != nil { - r.RefreshOnly = *options.RefreshOnly - } - - w, ok := m.client.Workspaces.workspaceIDs[options.Workspace.ID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - if w.CurrentRun == nil { - w.CurrentRun = r - } - - if m.modifyNewRun != nil { - // caller-provided callback may modify the run in-place to mimic - // side-effects that a real server might take in some situations. - m.modifyNewRun(m.client, options, r) - } - - m.runs[r.ID] = r - m.workspaces[options.Workspace.ID] = append(m.workspaces[options.Workspace.ID], r) - - return r, nil -} - -func (m *mockRuns) Read(ctx context.Context, runID string) (*tfe.Run, error) { - return m.ReadWithOptions(ctx, runID, nil) -} - -func (m *mockRuns) ReadWithOptions(ctx context.Context, runID string, _ *tfe.RunReadOptions) (*tfe.Run, error) { - m.Lock() - defer m.Unlock() - - r, ok := m.runs[runID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - pending := false - for _, r := range m.runs { - if r.ID != runID && r.Status == tfe.RunPending { - pending = true - break - } - } - - if !pending && r.Status == tfe.RunPending { - // Only update the status if there are no other pending runs. - r.Status = tfe.RunPlanning - r.Plan.Status = tfe.PlanRunning - } - - logs, _ := ioutil.ReadFile(m.client.Plans.logs[r.Plan.LogReadURL]) - if r.Status == tfe.RunPlanning && r.Plan.Status == tfe.PlanFinished { - if r.IsDestroy || bytes.Contains(logs, []byte("1 to add, 0 to change, 0 to destroy")) { - r.Actions.IsCancelable = false - r.Actions.IsConfirmable = true - r.HasChanges = true - r.Permissions.CanApply = true - } - - if bytes.Contains(logs, []byte("null_resource.foo: 1 error")) { - r.Actions.IsCancelable = false - r.HasChanges = false - r.Status = tfe.RunErrored - } - } - - // we must return a copy for the client - rc, err := copystructure.Copy(r) - if err != nil { - panic(err) - } - - return rc.(*tfe.Run), nil -} - -func (m *mockRuns) Apply(ctx context.Context, runID string, options tfe.RunApplyOptions) error { - m.Lock() - defer m.Unlock() - - r, ok := m.runs[runID] - if !ok { - return tfe.ErrResourceNotFound - } - if r.Status != tfe.RunPending { - // Only update the status if the run is not pending anymore. - r.Status = tfe.RunApplying - r.Actions.IsConfirmable = false - r.Apply.Status = tfe.ApplyRunning - } - return nil -} - -func (m *mockRuns) Cancel(ctx context.Context, runID string, options tfe.RunCancelOptions) error { - panic("not implemented") -} - -func (m *mockRuns) ForceCancel(ctx context.Context, runID string, options tfe.RunForceCancelOptions) error { - panic("not implemented") -} - -func (m *mockRuns) Discard(ctx context.Context, runID string, options tfe.RunDiscardOptions) error { - m.Lock() - defer m.Unlock() - - r, ok := m.runs[runID] - if !ok { - return tfe.ErrResourceNotFound - } - r.Status = tfe.RunDiscarded - r.Actions.IsConfirmable = false - return nil -} - -type mockStateVersions struct { - client *mockClient - states map[string][]byte - stateVersions map[string]*tfe.StateVersion - workspaces map[string][]string -} - -func newMockStateVersions(client *mockClient) *mockStateVersions { - return &mockStateVersions{ - client: client, - states: make(map[string][]byte), - stateVersions: make(map[string]*tfe.StateVersion), - workspaces: make(map[string][]string), - } -} - -func (m *mockStateVersions) List(ctx context.Context, options tfe.StateVersionListOptions) (*tfe.StateVersionList, error) { - svl := &tfe.StateVersionList{} - for _, sv := range m.stateVersions { - svl.Items = append(svl.Items, sv) - } - - svl.Pagination = &tfe.Pagination{ - CurrentPage: 1, - NextPage: 1, - PreviousPage: 1, - TotalPages: 1, - TotalCount: len(svl.Items), - } - - return svl, nil -} - -func (m *mockStateVersions) Create(ctx context.Context, workspaceID string, options tfe.StateVersionCreateOptions) (*tfe.StateVersion, error) { - id := generateID("sv-") - runID := os.Getenv("TFE_RUN_ID") - url := fmt.Sprintf("https://app.terraform.io/_archivist/%s", id) - - if runID != "" && (options.Run == nil || runID != options.Run.ID) { - return nil, fmt.Errorf("option.Run.ID does not contain the ID exported by TFE_RUN_ID") - } - - sv := &tfe.StateVersion{ - ID: id, - DownloadURL: url, - Serial: *options.Serial, - } - - state, err := base64.StdEncoding.DecodeString(*options.State) - if err != nil { - return nil, err - } - - m.states[sv.DownloadURL] = state - m.stateVersions[sv.ID] = sv - m.workspaces[workspaceID] = append(m.workspaces[workspaceID], sv.ID) - - return sv, nil -} - -func (m *mockStateVersions) Read(ctx context.Context, svID string) (*tfe.StateVersion, error) { - return m.ReadWithOptions(ctx, svID, nil) -} - -func (m *mockStateVersions) ReadWithOptions(ctx context.Context, svID string, options *tfe.StateVersionReadOptions) (*tfe.StateVersion, error) { - sv, ok := m.stateVersions[svID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - return sv, nil -} - -func (m *mockStateVersions) Current(ctx context.Context, workspaceID string) (*tfe.StateVersion, error) { - return m.CurrentWithOptions(ctx, workspaceID, nil) -} - -func (m *mockStateVersions) CurrentWithOptions(ctx context.Context, workspaceID string, options *tfe.StateVersionCurrentOptions) (*tfe.StateVersion, error) { - w, ok := m.client.Workspaces.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - svs, ok := m.workspaces[w.ID] - if !ok || len(svs) == 0 { - return nil, tfe.ErrResourceNotFound - } - - sv, ok := m.stateVersions[svs[len(svs)-1]] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - return sv, nil -} - -func (m *mockStateVersions) Download(ctx context.Context, url string) ([]byte, error) { - state, ok := m.states[url] - if !ok { - return nil, tfe.ErrResourceNotFound - } - return state, nil -} - -type mockVariables struct { - client *mockClient - workspaces map[string]*tfe.VariableList -} - -var _ tfe.Variables = (*mockVariables)(nil) - -func newMockVariables(client *mockClient) *mockVariables { - return &mockVariables{ - client: client, - workspaces: make(map[string]*tfe.VariableList), - } -} - -func (m *mockVariables) List(ctx context.Context, workspaceID string, options tfe.VariableListOptions) (*tfe.VariableList, error) { - vl := m.workspaces[workspaceID] - return vl, nil -} - -func (m *mockVariables) Create(ctx context.Context, workspaceID string, options tfe.VariableCreateOptions) (*tfe.Variable, error) { - v := &tfe.Variable{ - ID: generateID("var-"), - Key: *options.Key, - Category: *options.Category, - } - if options.Value != nil { - v.Value = *options.Value - } - if options.HCL != nil { - v.HCL = *options.HCL - } - if options.Sensitive != nil { - v.Sensitive = *options.Sensitive - } - - workspace := workspaceID - - if m.workspaces[workspace] == nil { - m.workspaces[workspace] = &tfe.VariableList{} - } - - vl := m.workspaces[workspace] - vl.Items = append(vl.Items, v) - - return v, nil -} - -func (m *mockVariables) Read(ctx context.Context, workspaceID string, variableID string) (*tfe.Variable, error) { - panic("not implemented") -} - -func (m *mockVariables) Update(ctx context.Context, workspaceID string, variableID string, options tfe.VariableUpdateOptions) (*tfe.Variable, error) { - panic("not implemented") -} - -func (m *mockVariables) Delete(ctx context.Context, workspaceID string, variableID string) error { - panic("not implemented") -} - -type mockWorkspaces struct { - client *mockClient - workspaceIDs map[string]*tfe.Workspace - workspaceNames map[string]*tfe.Workspace -} - -func newMockWorkspaces(client *mockClient) *mockWorkspaces { - return &mockWorkspaces{ - client: client, - workspaceIDs: make(map[string]*tfe.Workspace), - workspaceNames: make(map[string]*tfe.Workspace), - } -} - -func (m *mockWorkspaces) List(ctx context.Context, organization string, options tfe.WorkspaceListOptions) (*tfe.WorkspaceList, error) { - dummyWorkspaces := 10 - wl := &tfe.WorkspaceList{} - - // Get the prefix from the search options. - prefix := "" - if options.Search != nil { - prefix = *options.Search - } - - // Get all the workspaces that match the prefix. - var ws []*tfe.Workspace - for _, w := range m.workspaceIDs { - if strings.HasPrefix(w.Name, prefix) { - ws = append(ws, w) - } - } - - // Return an empty result if we have no matches. - if len(ws) == 0 { - wl.Pagination = &tfe.Pagination{ - CurrentPage: 1, - } - return wl, nil - } - - // Return dummy workspaces for the first page to test pagination. - if options.PageNumber <= 1 { - for i := 0; i < dummyWorkspaces; i++ { - wl.Items = append(wl.Items, &tfe.Workspace{ - ID: generateID("ws-"), - Name: fmt.Sprintf("dummy-workspace-%d", i), - }) - } - - wl.Pagination = &tfe.Pagination{ - CurrentPage: 1, - NextPage: 2, - TotalPages: 2, - TotalCount: len(wl.Items) + len(ws), - } - - return wl, nil - } - - // Return the actual workspaces that matched as the second page. - wl.Items = ws - wl.Pagination = &tfe.Pagination{ - CurrentPage: 2, - PreviousPage: 1, - TotalPages: 2, - TotalCount: len(wl.Items) + dummyWorkspaces, - } - - return wl, nil -} - -func (m *mockWorkspaces) Create(ctx context.Context, organization string, options tfe.WorkspaceCreateOptions) (*tfe.Workspace, error) { - if strings.HasSuffix(*options.Name, "no-operations") { - options.Operations = tfe.Bool(false) - } else if options.Operations == nil { - options.Operations = tfe.Bool(true) - } - w := &tfe.Workspace{ - ID: generateID("ws-"), - Name: *options.Name, - Operations: *options.Operations, - Permissions: &tfe.WorkspacePermissions{ - CanQueueApply: true, - CanQueueRun: true, - }, - } - if options.AutoApply != nil { - w.AutoApply = *options.AutoApply - } - if options.VCSRepo != nil { - w.VCSRepo = &tfe.VCSRepo{} - } - if options.TerraformVersion != nil { - w.TerraformVersion = *options.TerraformVersion - } else { - w.TerraformVersion = tfversion.String() - } - m.workspaceIDs[w.ID] = w - m.workspaceNames[w.Name] = w - return w, nil -} - -func (m *mockWorkspaces) Read(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) { - // custom error for TestRemote_plan500 in backend_plan_test.go - if workspace == "network-error" { - return nil, errors.New("I'm a little teacup") - } - - w, ok := m.workspaceNames[workspace] - if !ok { - return nil, tfe.ErrResourceNotFound - } - return w, nil -} - -func (m *mockWorkspaces) ReadByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { - w, ok := m.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - return w, nil -} - -func (m *mockWorkspaces) Update(ctx context.Context, organization, workspace string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) { - w, ok := m.workspaceNames[workspace] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - if options.Operations != nil { - w.Operations = *options.Operations - } - if options.Name != nil { - w.Name = *options.Name - } - if options.TerraformVersion != nil { - w.TerraformVersion = *options.TerraformVersion - } - if options.WorkingDirectory != nil { - w.WorkingDirectory = *options.WorkingDirectory - } - - delete(m.workspaceNames, workspace) - m.workspaceNames[w.Name] = w - - return w, nil -} - -func (m *mockWorkspaces) UpdateByID(ctx context.Context, workspaceID string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) { - w, ok := m.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - - if options.Name != nil { - w.Name = *options.Name - } - if options.TerraformVersion != nil { - w.TerraformVersion = *options.TerraformVersion - } - if options.WorkingDirectory != nil { - w.WorkingDirectory = *options.WorkingDirectory - } - - delete(m.workspaceNames, w.Name) - m.workspaceNames[w.Name] = w - - return w, nil -} - -func (m *mockWorkspaces) Delete(ctx context.Context, organization, workspace string) error { - if w, ok := m.workspaceNames[workspace]; ok { - delete(m.workspaceIDs, w.ID) - } - delete(m.workspaceNames, workspace) - return nil -} - -func (m *mockWorkspaces) DeleteByID(ctx context.Context, workspaceID string) error { - if w, ok := m.workspaceIDs[workspaceID]; ok { - delete(m.workspaceIDs, w.Name) - } - delete(m.workspaceIDs, workspaceID) - return nil -} - -func (m *mockWorkspaces) RemoveVCSConnection(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) { - w, ok := m.workspaceNames[workspace] - if !ok { - return nil, tfe.ErrResourceNotFound - } - w.VCSRepo = nil - return w, nil -} - -func (m *mockWorkspaces) RemoveVCSConnectionByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { - w, ok := m.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - w.VCSRepo = nil - return w, nil -} - -func (m *mockWorkspaces) Lock(ctx context.Context, workspaceID string, options tfe.WorkspaceLockOptions) (*tfe.Workspace, error) { - w, ok := m.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - if w.Locked { - return nil, tfe.ErrWorkspaceLocked - } - w.Locked = true - return w, nil -} - -func (m *mockWorkspaces) Unlock(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { - w, ok := m.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - if !w.Locked { - return nil, tfe.ErrWorkspaceNotLocked - } - w.Locked = false - return w, nil -} - -func (m *mockWorkspaces) ForceUnlock(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { - w, ok := m.workspaceIDs[workspaceID] - if !ok { - return nil, tfe.ErrResourceNotFound - } - if !w.Locked { - return nil, tfe.ErrWorkspaceNotLocked - } - w.Locked = false - return w, nil -} - -func (m *mockWorkspaces) AssignSSHKey(ctx context.Context, workspaceID string, options tfe.WorkspaceAssignSSHKeyOptions) (*tfe.Workspace, error) { - panic("not implemented") -} - -func (m *mockWorkspaces) UnassignSSHKey(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { - panic("not implemented") -} - -func (m *mockWorkspaces) RemoteStateConsumers(ctx context.Context, workspaceID string) (*tfe.WorkspaceList, error) { - panic("not implemented") -} - -func (m *mockWorkspaces) AddRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceAddRemoteStateConsumersOptions) error { - panic("not implemented") -} - -func (m *mockWorkspaces) RemoveRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceRemoveRemoteStateConsumersOptions) error { - panic("not implemented") -} - -func (m *mockWorkspaces) UpdateRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceUpdateRemoteStateConsumersOptions) error { - panic("not implemented") -} - -func (m *mockWorkspaces) Readme(ctx context.Context, workspaceID string) (io.Reader, error) { - panic("not implemented") -} - -const alphanumeric = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - -func generateID(s string) string { - b := make([]byte, 16) - for i := range b { - b[i] = alphanumeric[rand.Intn(len(alphanumeric))] - } - return s + string(b) -} diff --git a/internal/backend/remote/backend_plan_test.go b/internal/backend/remote/backend_plan_test.go index 147c68e9d..95aacea97 100644 --- a/internal/backend/remote/backend_plan_test.go +++ b/internal/backend/remote/backend_plan_test.go @@ -13,6 +13,7 @@ import ( tfe "github.com/hashicorp/go-tfe" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/backend" + "github.com/hashicorp/terraform/internal/cloud" "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/command/clistate" "github.com/hashicorp/terraform/internal/command/views" @@ -313,11 +314,11 @@ func TestRemote_planWithoutRefresh(t *testing.T) { // We should find a run inside the mock client that has refresh set // to false. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*cloud.MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff(false, run.Refresh); diff != "" { t.Errorf("wrong Refresh setting in the created run\n%s", diff) } @@ -382,11 +383,11 @@ func TestRemote_planWithRefreshOnly(t *testing.T) { // We should find a run inside the mock client that has refresh-only set // to true. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*cloud.MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff(true, run.RefreshOnly); diff != "" { t.Errorf("wrong RefreshOnly setting in the created run\n%s", diff) } @@ -432,7 +433,7 @@ func TestRemote_planWithTarget(t *testing.T) { // When the backend code creates a new run, we'll tweak it so that it // has a cost estimation object with the "skipped_due_to_targeting" status, // emulating how a real server is expected to behave in that case. - b.client.Runs.(*mockRuns).modifyNewRun = func(client *mockClient, options tfe.RunCreateOptions, run *tfe.Run) { + b.client.Runs.(*cloud.MockRuns).ModifyNewRun = func(client *cloud.MockClient, options tfe.RunCreateOptions, run *tfe.Run) { const fakeID = "fake" // This is the cost estimate object embedded in the run itself which // the backend will use to learn the ID to request from the cost @@ -446,7 +447,7 @@ func TestRemote_planWithTarget(t *testing.T) { // the same ID indicated in the object above, where we'll then return // the status "skipped_due_to_targeting" to trigger the special skip // message in the backend output. - client.CostEstimates.estimations[fakeID] = &tfe.CostEstimate{ + client.CostEstimates.Estimations[fakeID] = &tfe.CostEstimate{ ID: fakeID, Status: "skipped_due_to_targeting", } @@ -483,11 +484,11 @@ func TestRemote_planWithTarget(t *testing.T) { // We should find a run inside the mock client that has the same // target address we requested above. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*cloud.MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff([]string{"null_resource.foo"}, run.TargetAddrs); diff != "" { t.Errorf("wrong TargetAddrs in the created run\n%s", diff) } @@ -558,11 +559,11 @@ func TestRemote_planWithReplace(t *testing.T) { // We should find a run inside the mock client that has the same // refresh address we requested above. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*cloud.MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff([]string{"null_resource.foo"}, run.ReplaceAddrs); diff != "" { t.Errorf("wrong ReplaceAddrs in the created run\n%s", diff) } diff --git a/internal/backend/remote/backend_state_test.go b/internal/backend/remote/backend_state_test.go index 3a1769c30..0503936b8 100644 --- a/internal/backend/remote/backend_state_test.go +++ b/internal/backend/remote/backend_state_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/hashicorp/terraform/internal/backend" + "github.com/hashicorp/terraform/internal/cloud" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/states/remote" "github.com/hashicorp/terraform/internal/states/statefile" @@ -39,7 +40,7 @@ func TestRemoteClient_stateLock(t *testing.T) { func TestRemoteClient_withRunID(t *testing.T) { // Set the TFE_RUN_ID environment variable before creating the client! - if err := os.Setenv("TFE_RUN_ID", generateID("run-")); err != nil { + if err := os.Setenv("TFE_RUN_ID", cloud.GenerateID("run-")); err != nil { t.Fatalf("error setting env var TFE_RUN_ID: %v", err) } diff --git a/internal/backend/remote/testing.go b/internal/backend/remote/testing.go index f3c66941a..7dbb9e9b2 100644 --- a/internal/backend/remote/testing.go +++ b/internal/backend/remote/testing.go @@ -8,12 +8,14 @@ import ( "net/http/httptest" "path" "testing" + "time" tfe "github.com/hashicorp/go-tfe" svchost "github.com/hashicorp/terraform-svchost" "github.com/hashicorp/terraform-svchost/auth" "github.com/hashicorp/terraform-svchost/disco" "github.com/hashicorp/terraform/internal/backend" + "github.com/hashicorp/terraform/internal/cloud" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/httpclient" @@ -39,6 +41,26 @@ var ( }) ) +// mockInput is a mock implementation of terraform.UIInput. +type mockInput struct { + answers map[string]string +} + +func (m *mockInput) Input(ctx context.Context, opts *terraform.InputOpts) (string, error) { + v, ok := m.answers[opts.Id] + if !ok { + return "", fmt.Errorf("unexpected input request in test: %s", opts.Id) + } + if v == "wait-for-external-update" { + select { + case <-ctx.Done(): + case <-time.After(time.Minute): + } + } + delete(m.answers, opts.Id) + return v, nil +} + func testInput(t *testing.T, answers map[string]string) *mockInput { return &mockInput{answers: answers} } @@ -111,7 +133,7 @@ func testBackend(t *testing.T, obj cty.Value) (*Remote, func()) { } // Get a new mock client. - mc := newMockClient() + mc := cloud.NewMockClient() // Replace the services we use with our mock services. b.CLI = cli.NewMockUi() diff --git a/internal/cloud/backend_apply_test.go b/internal/cloud/backend_apply_test.go index 140dafa8a..1100c31dc 100644 --- a/internal/cloud/backend_apply_test.go +++ b/internal/cloud/backend_apply_test.go @@ -299,11 +299,11 @@ func TestCloud_applyWithoutRefresh(t *testing.T) { // We should find a run inside the mock client that has refresh set // to false. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff(false, run.Refresh); diff != "" { t.Errorf("wrong Refresh setting in the created run\n%s", diff) } @@ -368,11 +368,11 @@ func TestCloud_applyWithRefreshOnly(t *testing.T) { // We should find a run inside the mock client that has refresh-only set // to true. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff(true, run.RefreshOnly); diff != "" { t.Errorf("wrong RefreshOnly setting in the created run\n%s", diff) } @@ -439,11 +439,11 @@ func TestCloud_applyWithTarget(t *testing.T) { // We should find a run inside the mock client that has the same // target address we requested above. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff([]string{"null_resource.foo"}, run.TargetAddrs); diff != "" { t.Errorf("wrong TargetAddrs in the created run\n%s", diff) } @@ -514,11 +514,11 @@ func TestCloud_applyWithReplace(t *testing.T) { // We should find a run inside the mock client that has the same // refresh address we requested above. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff([]string{"null_resource.foo"}, run.ReplaceAddrs); diff != "" { t.Errorf("wrong ReplaceAddrs in the created run\n%s", diff) } diff --git a/internal/cloud/backend_plan_test.go b/internal/cloud/backend_plan_test.go index 790c8cc03..cd836b46d 100644 --- a/internal/cloud/backend_plan_test.go +++ b/internal/cloud/backend_plan_test.go @@ -304,11 +304,11 @@ func TestCloud_planWithoutRefresh(t *testing.T) { // We should find a run inside the mock client that has refresh set // to false. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff(false, run.Refresh); diff != "" { t.Errorf("wrong Refresh setting in the created run\n%s", diff) } @@ -373,11 +373,11 @@ func TestCloud_planWithRefreshOnly(t *testing.T) { // We should find a run inside the mock client that has refresh-only set // to true. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff(true, run.RefreshOnly); diff != "" { t.Errorf("wrong RefreshOnly setting in the created run\n%s", diff) } @@ -423,7 +423,7 @@ func TestCloud_planWithTarget(t *testing.T) { // When the backend code creates a new run, we'll tweak it so that it // has a cost estimation object with the "skipped_due_to_targeting" status, // emulating how a real server is expected to behave in that case. - b.client.Runs.(*mockRuns).modifyNewRun = func(client *mockClient, options tfe.RunCreateOptions, run *tfe.Run) { + b.client.Runs.(*MockRuns).ModifyNewRun = func(client *MockClient, options tfe.RunCreateOptions, run *tfe.Run) { const fakeID = "fake" // This is the cost estimate object embedded in the run itself which // the backend will use to learn the ID to request from the cost @@ -437,7 +437,7 @@ func TestCloud_planWithTarget(t *testing.T) { // the same ID indicated in the object above, where we'll then return // the status "skipped_due_to_targeting" to trigger the special skip // message in the backend output. - client.CostEstimates.estimations[fakeID] = &tfe.CostEstimate{ + client.CostEstimates.Estimations[fakeID] = &tfe.CostEstimate{ ID: fakeID, Status: "skipped_due_to_targeting", } @@ -474,11 +474,11 @@ func TestCloud_planWithTarget(t *testing.T) { // We should find a run inside the mock client that has the same // target address we requested above. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff([]string{"null_resource.foo"}, run.TargetAddrs); diff != "" { t.Errorf("wrong TargetAddrs in the created run\n%s", diff) } @@ -549,11 +549,11 @@ func TestCloud_planWithReplace(t *testing.T) { // We should find a run inside the mock client that has the same // refresh address we requested above. - runsAPI := b.client.Runs.(*mockRuns) - if got, want := len(runsAPI.runs), 1; got != want { + runsAPI := b.client.Runs.(*MockRuns) + if got, want := len(runsAPI.Runs), 1; got != want { t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want) } - for _, run := range runsAPI.runs { + for _, run := range runsAPI.Runs { if diff := cmp.Diff([]string{"null_resource.foo"}, run.ReplaceAddrs); diff != "" { t.Errorf("wrong ReplaceAddrs in the created run\n%s", diff) } diff --git a/internal/cloud/backend_state_test.go b/internal/cloud/backend_state_test.go index dc2a45723..a94c60c6e 100644 --- a/internal/cloud/backend_state_test.go +++ b/internal/cloud/backend_state_test.go @@ -39,7 +39,7 @@ func TestRemoteClient_stateLock(t *testing.T) { func TestRemoteClient_withRunID(t *testing.T) { // Set the TFE_RUN_ID environment variable before creating the client! - if err := os.Setenv("TFE_RUN_ID", generateID("run-")); err != nil { + if err := os.Setenv("TFE_RUN_ID", GenerateID("run-")); err != nil { t.Fatalf("error setting env var TFE_RUN_ID: %v", err) } diff --git a/internal/cloud/testing.go b/internal/cloud/testing.go index 73668b2b0..7bb145b3c 100644 --- a/internal/cloud/testing.go +++ b/internal/cloud/testing.go @@ -8,6 +8,7 @@ import ( "net/http/httptest" "path" "testing" + "time" tfe "github.com/hashicorp/go-tfe" svchost "github.com/hashicorp/terraform-svchost" @@ -39,6 +40,26 @@ var ( }) ) +// mockInput is a mock implementation of terraform.UIInput. +type mockInput struct { + answers map[string]string +} + +func (m *mockInput) Input(ctx context.Context, opts *terraform.InputOpts) (string, error) { + v, ok := m.answers[opts.Id] + if !ok { + return "", fmt.Errorf("unexpected input request in test: %s", opts.Id) + } + if v == "wait-for-external-update" { + select { + case <-ctx.Done(): + case <-time.After(time.Minute): + } + } + delete(m.answers, opts.Id) + return v, nil +} + func testInput(t *testing.T, answers map[string]string) *mockInput { return &mockInput{answers: answers} } @@ -111,7 +132,7 @@ func testBackend(t *testing.T, obj cty.Value) (*Cloud, func()) { } // Get a new mock client. - mc := newMockClient() + mc := NewMockClient() // Replace the services we use with our mock services. b.CLI = cli.NewMockUi() diff --git a/internal/cloud/backend_mock.go b/internal/cloud/tfe_client_mock.go similarity index 75% rename from internal/cloud/backend_mock.go rename to internal/cloud/tfe_client_mock.go index 0ee036bc5..d575d82e8 100644 --- a/internal/cloud/backend_mock.go +++ b/internal/cloud/tfe_client_mock.go @@ -16,26 +16,25 @@ import ( "time" tfe "github.com/hashicorp/go-tfe" - "github.com/hashicorp/terraform/internal/terraform" tfversion "github.com/hashicorp/terraform/version" "github.com/mitchellh/copystructure" ) -type mockClient struct { - Applies *mockApplies - ConfigurationVersions *mockConfigurationVersions - CostEstimates *mockCostEstimates - Organizations *mockOrganizations - Plans *mockPlans - PolicyChecks *mockPolicyChecks - Runs *mockRuns - StateVersions *mockStateVersions - Variables *mockVariables - Workspaces *mockWorkspaces +type MockClient struct { + Applies *MockApplies + ConfigurationVersions *MockConfigurationVersions + CostEstimates *MockCostEstimates + Organizations *MockOrganizations + Plans *MockPlans + PolicyChecks *MockPolicyChecks + Runs *MockRuns + StateVersions *MockStateVersions + Variables *MockVariables + Workspaces *MockWorkspaces } -func newMockClient() *mockClient { - c := &mockClient{} +func NewMockClient() *MockClient { + c := &MockClient{} c.Applies = newMockApplies(c) c.ConfigurationVersions = newMockConfigurationVersions(c) c.CostEstimates = newMockCostEstimates(c) @@ -49,14 +48,14 @@ func newMockClient() *mockClient { return c } -type mockApplies struct { - client *mockClient +type MockApplies struct { + client *MockClient applies map[string]*tfe.Apply logs map[string]string } -func newMockApplies(client *mockClient) *mockApplies { - return &mockApplies{ +func newMockApplies(client *MockClient) *MockApplies { + return &MockApplies{ client: client, applies: make(map[string]*tfe.Apply), logs: make(map[string]string), @@ -65,7 +64,7 @@ func newMockApplies(client *mockClient) *mockApplies { // create is a helper function to create a mock apply that uses the configured // working directory to find the logfile. -func (m *mockApplies) create(cvID, workspaceID string) (*tfe.Apply, error) { +func (m *MockApplies) create(cvID, workspaceID string) (*tfe.Apply, error) { c, ok := m.client.ConfigurationVersions.configVersions[cvID] if !ok { return nil, tfe.ErrResourceNotFound @@ -75,7 +74,7 @@ func (m *mockApplies) create(cvID, workspaceID string) (*tfe.Apply, error) { return nil, nil } - id := generateID("apply-") + id := GenerateID("apply-") url := fmt.Sprintf("https://app.terraform.io/_archivist/%s", id) a := &tfe.Apply{ @@ -103,7 +102,7 @@ func (m *mockApplies) create(cvID, workspaceID string) (*tfe.Apply, error) { return a, nil } -func (m *mockApplies) Read(ctx context.Context, applyID string) (*tfe.Apply, error) { +func (m *MockApplies) Read(ctx context.Context, applyID string) (*tfe.Apply, error) { a, ok := m.applies[applyID] if !ok { return nil, tfe.ErrResourceNotFound @@ -115,7 +114,7 @@ func (m *mockApplies) Read(ctx context.Context, applyID string) (*tfe.Apply, err return a, nil } -func (m *mockApplies) Logs(ctx context.Context, applyID string) (io.Reader, error) { +func (m *MockApplies) Logs(ctx context.Context, applyID string) (io.Reader, error) { a, err := m.Read(ctx, applyID) if err != nil { return nil, err @@ -152,15 +151,15 @@ func (m *mockApplies) Logs(ctx context.Context, applyID string) (io.Reader, erro }, nil } -type mockConfigurationVersions struct { - client *mockClient +type MockConfigurationVersions struct { + client *MockClient configVersions map[string]*tfe.ConfigurationVersion uploadPaths map[string]string uploadURLs map[string]*tfe.ConfigurationVersion } -func newMockConfigurationVersions(client *mockClient) *mockConfigurationVersions { - return &mockConfigurationVersions{ +func newMockConfigurationVersions(client *MockClient) *MockConfigurationVersions { + return &MockConfigurationVersions{ client: client, configVersions: make(map[string]*tfe.ConfigurationVersion), uploadPaths: make(map[string]string), @@ -168,7 +167,7 @@ func newMockConfigurationVersions(client *mockClient) *mockConfigurationVersions } } -func (m *mockConfigurationVersions) List(ctx context.Context, workspaceID string, options tfe.ConfigurationVersionListOptions) (*tfe.ConfigurationVersionList, error) { +func (m *MockConfigurationVersions) List(ctx context.Context, workspaceID string, options tfe.ConfigurationVersionListOptions) (*tfe.ConfigurationVersionList, error) { cvl := &tfe.ConfigurationVersionList{} for _, cv := range m.configVersions { cvl.Items = append(cvl.Items, cv) @@ -185,8 +184,8 @@ func (m *mockConfigurationVersions) List(ctx context.Context, workspaceID string return cvl, nil } -func (m *mockConfigurationVersions) Create(ctx context.Context, workspaceID string, options tfe.ConfigurationVersionCreateOptions) (*tfe.ConfigurationVersion, error) { - id := generateID("cv-") +func (m *MockConfigurationVersions) Create(ctx context.Context, workspaceID string, options tfe.ConfigurationVersionCreateOptions) (*tfe.ConfigurationVersion, error) { + id := GenerateID("cv-") url := fmt.Sprintf("https://app.terraform.io/_archivist/%s", id) cv := &tfe.ConfigurationVersion{ @@ -201,7 +200,7 @@ func (m *mockConfigurationVersions) Create(ctx context.Context, workspaceID stri return cv, nil } -func (m *mockConfigurationVersions) Read(ctx context.Context, cvID string) (*tfe.ConfigurationVersion, error) { +func (m *MockConfigurationVersions) Read(ctx context.Context, cvID string) (*tfe.ConfigurationVersion, error) { cv, ok := m.configVersions[cvID] if !ok { return nil, tfe.ErrResourceNotFound @@ -209,7 +208,15 @@ func (m *mockConfigurationVersions) Read(ctx context.Context, cvID string) (*tfe return cv, nil } -func (m *mockConfigurationVersions) Upload(ctx context.Context, url, path string) error { +func (m *MockConfigurationVersions) ReadWithOptions(ctx context.Context, cvID string, options *tfe.ConfigurationVersionReadOptions) (*tfe.ConfigurationVersion, error) { + cv, ok := m.configVersions[cvID] + if !ok { + return nil, tfe.ErrResourceNotFound + } + return cv, nil +} + +func (m *MockConfigurationVersions) Upload(ctx context.Context, url, path string) error { cv, ok := m.uploadURLs[url] if !ok { return errors.New("404 not found") @@ -219,24 +226,24 @@ func (m *mockConfigurationVersions) Upload(ctx context.Context, url, path string return nil } -type mockCostEstimates struct { - client *mockClient - estimations map[string]*tfe.CostEstimate +type MockCostEstimates struct { + client *MockClient + Estimations map[string]*tfe.CostEstimate logs map[string]string } -func newMockCostEstimates(client *mockClient) *mockCostEstimates { - return &mockCostEstimates{ +func newMockCostEstimates(client *MockClient) *MockCostEstimates { + return &MockCostEstimates{ client: client, - estimations: make(map[string]*tfe.CostEstimate), + Estimations: make(map[string]*tfe.CostEstimate), logs: make(map[string]string), } } // create is a helper function to create a mock cost estimation that uses the // configured working directory to find the logfile. -func (m *mockCostEstimates) create(cvID, workspaceID string) (*tfe.CostEstimate, error) { - id := generateID("ce-") +func (m *MockCostEstimates) create(cvID, workspaceID string) (*tfe.CostEstimate, error) { + id := GenerateID("ce-") ce := &tfe.CostEstimate{ ID: id, @@ -263,21 +270,21 @@ func (m *mockCostEstimates) create(cvID, workspaceID string) (*tfe.CostEstimate, } m.logs[ce.ID] = logfile - m.estimations[ce.ID] = ce + m.Estimations[ce.ID] = ce return ce, nil } -func (m *mockCostEstimates) Read(ctx context.Context, costEstimateID string) (*tfe.CostEstimate, error) { - ce, ok := m.estimations[costEstimateID] +func (m *MockCostEstimates) Read(ctx context.Context, costEstimateID string) (*tfe.CostEstimate, error) { + ce, ok := m.Estimations[costEstimateID] if !ok { return nil, tfe.ErrResourceNotFound } return ce, nil } -func (m *mockCostEstimates) Logs(ctx context.Context, costEstimateID string) (io.Reader, error) { - ce, ok := m.estimations[costEstimateID] +func (m *MockCostEstimates) Logs(ctx context.Context, costEstimateID string) (io.Reader, error) { + ce, ok := m.Estimations[costEstimateID] if !ok { return nil, tfe.ErrResourceNotFound } @@ -301,39 +308,19 @@ func (m *mockCostEstimates) Logs(ctx context.Context, costEstimateID string) (io return bytes.NewBuffer(logs), nil } -// mockInput is a mock implementation of terraform.UIInput. -type mockInput struct { - answers map[string]string -} - -func (m *mockInput) Input(ctx context.Context, opts *terraform.InputOpts) (string, error) { - v, ok := m.answers[opts.Id] - if !ok { - return "", fmt.Errorf("unexpected input request in test: %s", opts.Id) - } - if v == "wait-for-external-update" { - select { - case <-ctx.Done(): - case <-time.After(time.Minute): - } - } - delete(m.answers, opts.Id) - return v, nil -} - -type mockOrganizations struct { - client *mockClient +type MockOrganizations struct { + client *MockClient organizations map[string]*tfe.Organization } -func newMockOrganizations(client *mockClient) *mockOrganizations { - return &mockOrganizations{ +func newMockOrganizations(client *MockClient) *MockOrganizations { + return &MockOrganizations{ client: client, organizations: make(map[string]*tfe.Organization), } } -func (m *mockOrganizations) List(ctx context.Context, options tfe.OrganizationListOptions) (*tfe.OrganizationList, error) { +func (m *MockOrganizations) List(ctx context.Context, options tfe.OrganizationListOptions) (*tfe.OrganizationList, error) { orgl := &tfe.OrganizationList{} for _, org := range m.organizations { orgl.Items = append(orgl.Items, org) @@ -376,13 +363,13 @@ func (m *mockLogReader) read(l []byte) (int, error) { return m.logs.Read(l) } -func (m *mockOrganizations) Create(ctx context.Context, options tfe.OrganizationCreateOptions) (*tfe.Organization, error) { +func (m *MockOrganizations) Create(ctx context.Context, options tfe.OrganizationCreateOptions) (*tfe.Organization, error) { org := &tfe.Organization{Name: *options.Name} m.organizations[org.Name] = org return org, nil } -func (m *mockOrganizations) Read(ctx context.Context, name string) (*tfe.Organization, error) { +func (m *MockOrganizations) Read(ctx context.Context, name string) (*tfe.Organization, error) { org, ok := m.organizations[name] if !ok { return nil, tfe.ErrResourceNotFound @@ -390,7 +377,7 @@ func (m *mockOrganizations) Read(ctx context.Context, name string) (*tfe.Organiz return org, nil } -func (m *mockOrganizations) Update(ctx context.Context, name string, options tfe.OrganizationUpdateOptions) (*tfe.Organization, error) { +func (m *MockOrganizations) Update(ctx context.Context, name string, options tfe.OrganizationUpdateOptions) (*tfe.Organization, error) { org, ok := m.organizations[name] if !ok { return nil, tfe.ErrResourceNotFound @@ -400,14 +387,14 @@ func (m *mockOrganizations) Update(ctx context.Context, name string, options tfe } -func (m *mockOrganizations) Delete(ctx context.Context, name string) error { +func (m *MockOrganizations) Delete(ctx context.Context, name string) error { delete(m.organizations, name) return nil } -func (m *mockOrganizations) Capacity(ctx context.Context, name string) (*tfe.Capacity, error) { +func (m *MockOrganizations) Capacity(ctx context.Context, name string) (*tfe.Capacity, error) { var pending, running int - for _, r := range m.client.Runs.runs { + for _, r := range m.client.Runs.Runs { if r.Status == tfe.RunPending { pending++ continue @@ -417,7 +404,7 @@ func (m *mockOrganizations) Capacity(ctx context.Context, name string) (*tfe.Cap return &tfe.Capacity{Pending: pending, Running: running}, nil } -func (m *mockOrganizations) Entitlements(ctx context.Context, name string) (*tfe.Entitlements, error) { +func (m *MockOrganizations) Entitlements(ctx context.Context, name string) (*tfe.Entitlements, error) { return &tfe.Entitlements{ Operations: true, PrivateModuleRegistry: true, @@ -428,10 +415,10 @@ func (m *mockOrganizations) Entitlements(ctx context.Context, name string) (*tfe }, nil } -func (m *mockOrganizations) RunQueue(ctx context.Context, name string, options tfe.RunQueueOptions) (*tfe.RunQueue, error) { +func (m *MockOrganizations) RunQueue(ctx context.Context, name string, options tfe.RunQueueOptions) (*tfe.RunQueue, error) { rq := &tfe.RunQueue{} - for _, r := range m.client.Runs.runs { + for _, r := range m.client.Runs.Runs { rq.Items = append(rq.Items, r) } @@ -446,15 +433,15 @@ func (m *mockOrganizations) RunQueue(ctx context.Context, name string, options t return rq, nil } -type mockPlans struct { - client *mockClient +type MockPlans struct { + client *MockClient logs map[string]string planOutputs map[string]string plans map[string]*tfe.Plan } -func newMockPlans(client *mockClient) *mockPlans { - return &mockPlans{ +func newMockPlans(client *MockClient) *MockPlans { + return &MockPlans{ client: client, logs: make(map[string]string), planOutputs: make(map[string]string), @@ -464,8 +451,8 @@ func newMockPlans(client *mockClient) *mockPlans { // create is a helper function to create a mock plan that uses the configured // working directory to find the logfile. -func (m *mockPlans) create(cvID, workspaceID string) (*tfe.Plan, error) { - id := generateID("plan-") +func (m *MockPlans) create(cvID, workspaceID string) (*tfe.Plan, error) { + id := GenerateID("plan-") url := fmt.Sprintf("https://app.terraform.io/_archivist/%s", id) p := &tfe.Plan{ @@ -489,7 +476,7 @@ func (m *mockPlans) create(cvID, workspaceID string) (*tfe.Plan, error) { return p, nil } -func (m *mockPlans) Read(ctx context.Context, planID string) (*tfe.Plan, error) { +func (m *MockPlans) Read(ctx context.Context, planID string) (*tfe.Plan, error) { p, ok := m.plans[planID] if !ok { return nil, tfe.ErrResourceNotFound @@ -501,7 +488,7 @@ func (m *mockPlans) Read(ctx context.Context, planID string) (*tfe.Plan, error) return p, nil } -func (m *mockPlans) Logs(ctx context.Context, planID string) (io.Reader, error) { +func (m *MockPlans) Logs(ctx context.Context, planID string) (io.Reader, error) { p, err := m.Read(ctx, planID) if err != nil { return nil, err @@ -538,7 +525,7 @@ func (m *mockPlans) Logs(ctx context.Context, planID string) (io.Reader, error) }, nil } -func (m *mockPlans) JSONOutput(ctx context.Context, planID string) ([]byte, error) { +func (m *MockPlans) JSONOutput(ctx context.Context, planID string) ([]byte, error) { planOutput, ok := m.planOutputs[planID] if !ok { return nil, tfe.ErrResourceNotFound @@ -547,14 +534,14 @@ func (m *mockPlans) JSONOutput(ctx context.Context, planID string) ([]byte, erro return []byte(planOutput), nil } -type mockPolicyChecks struct { - client *mockClient +type MockPolicyChecks struct { + client *MockClient checks map[string]*tfe.PolicyCheck logs map[string]string } -func newMockPolicyChecks(client *mockClient) *mockPolicyChecks { - return &mockPolicyChecks{ +func newMockPolicyChecks(client *MockClient) *MockPolicyChecks { + return &MockPolicyChecks{ client: client, checks: make(map[string]*tfe.PolicyCheck), logs: make(map[string]string), @@ -563,8 +550,8 @@ func newMockPolicyChecks(client *mockClient) *mockPolicyChecks { // create is a helper function to create a mock policy check that uses the // configured working directory to find the logfile. -func (m *mockPolicyChecks) create(cvID, workspaceID string) (*tfe.PolicyCheck, error) { - id := generateID("pc-") +func (m *MockPolicyChecks) create(cvID, workspaceID string) (*tfe.PolicyCheck, error) { + id := GenerateID("pc-") pc := &tfe.PolicyCheck{ ID: id, @@ -595,8 +582,8 @@ func (m *mockPolicyChecks) create(cvID, workspaceID string) (*tfe.PolicyCheck, e return pc, nil } -func (m *mockPolicyChecks) List(ctx context.Context, runID string, options tfe.PolicyCheckListOptions) (*tfe.PolicyCheckList, error) { - _, ok := m.client.Runs.runs[runID] +func (m *MockPolicyChecks) List(ctx context.Context, runID string, options tfe.PolicyCheckListOptions) (*tfe.PolicyCheckList, error) { + _, ok := m.client.Runs.Runs[runID] if !ok { return nil, tfe.ErrResourceNotFound } @@ -617,7 +604,7 @@ func (m *mockPolicyChecks) List(ctx context.Context, runID string, options tfe.P return pcl, nil } -func (m *mockPolicyChecks) Read(ctx context.Context, policyCheckID string) (*tfe.PolicyCheck, error) { +func (m *MockPolicyChecks) Read(ctx context.Context, policyCheckID string) (*tfe.PolicyCheck, error) { pc, ok := m.checks[policyCheckID] if !ok { return nil, tfe.ErrResourceNotFound @@ -657,7 +644,7 @@ func (m *mockPolicyChecks) Read(ctx context.Context, policyCheckID string) (*tfe return pc, nil } -func (m *mockPolicyChecks) Override(ctx context.Context, policyCheckID string) (*tfe.PolicyCheck, error) { +func (m *MockPolicyChecks) Override(ctx context.Context, policyCheckID string) (*tfe.PolicyCheck, error) { pc, ok := m.checks[policyCheckID] if !ok { return nil, tfe.ErrResourceNotFound @@ -666,7 +653,7 @@ func (m *mockPolicyChecks) Override(ctx context.Context, policyCheckID string) ( return pc, nil } -func (m *mockPolicyChecks) Logs(ctx context.Context, policyCheckID string) (io.Reader, error) { +func (m *MockPolicyChecks) Logs(ctx context.Context, policyCheckID string) (io.Reader, error) { pc, ok := m.checks[policyCheckID] if !ok { return nil, tfe.ErrResourceNotFound @@ -706,28 +693,28 @@ func (m *mockPolicyChecks) Logs(ctx context.Context, policyCheckID string) (io.R return bytes.NewBuffer(logs), nil } -type mockRuns struct { +type MockRuns struct { sync.Mutex - client *mockClient - runs map[string]*tfe.Run + client *MockClient + Runs map[string]*tfe.Run workspaces map[string][]*tfe.Run - // If modifyNewRun is non-nil, the create method will call it just before + // If ModifyNewRun is non-nil, the create method will call it just before // saving a new run in the runs map, so that a calling test can mimic // side-effects that a real server might apply in certain situations. - modifyNewRun func(client *mockClient, options tfe.RunCreateOptions, run *tfe.Run) + ModifyNewRun func(client *MockClient, options tfe.RunCreateOptions, run *tfe.Run) } -func newMockRuns(client *mockClient) *mockRuns { - return &mockRuns{ +func newMockRuns(client *MockClient) *MockRuns { + return &MockRuns{ client: client, - runs: make(map[string]*tfe.Run), + Runs: make(map[string]*tfe.Run), workspaces: make(map[string][]*tfe.Run), } } -func (m *mockRuns) List(ctx context.Context, workspaceID string, options tfe.RunListOptions) (*tfe.RunList, error) { +func (m *MockRuns) List(ctx context.Context, workspaceID string, options tfe.RunListOptions) (*tfe.RunList, error) { m.Lock() defer m.Unlock() @@ -756,7 +743,7 @@ func (m *mockRuns) List(ctx context.Context, workspaceID string, options tfe.Run return rl, nil } -func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*tfe.Run, error) { +func (m *MockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*tfe.Run, error) { m.Lock() defer m.Unlock() @@ -781,7 +768,7 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t } r := &tfe.Run{ - ID: generateID("run-"), + ID: GenerateID("run-"), Actions: &tfe.RunActions{IsCancelable: true}, Apply: a, CostEstimate: ce, @@ -821,33 +808,33 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t w.CurrentRun = r } - if m.modifyNewRun != nil { + if m.ModifyNewRun != nil { // caller-provided callback may modify the run in-place to mimic // side-effects that a real server might take in some situations. - m.modifyNewRun(m.client, options, r) + m.ModifyNewRun(m.client, options, r) } - m.runs[r.ID] = r + m.Runs[r.ID] = r m.workspaces[options.Workspace.ID] = append(m.workspaces[options.Workspace.ID], r) return r, nil } -func (m *mockRuns) Read(ctx context.Context, runID string) (*tfe.Run, error) { +func (m *MockRuns) Read(ctx context.Context, runID string) (*tfe.Run, error) { return m.ReadWithOptions(ctx, runID, nil) } -func (m *mockRuns) ReadWithOptions(ctx context.Context, runID string, _ *tfe.RunReadOptions) (*tfe.Run, error) { +func (m *MockRuns) ReadWithOptions(ctx context.Context, runID string, _ *tfe.RunReadOptions) (*tfe.Run, error) { m.Lock() defer m.Unlock() - r, ok := m.runs[runID] + r, ok := m.Runs[runID] if !ok { return nil, tfe.ErrResourceNotFound } pending := false - for _, r := range m.runs { + for _, r := range m.Runs { if r.ID != runID && r.Status == tfe.RunPending { pending = true break @@ -885,11 +872,11 @@ func (m *mockRuns) ReadWithOptions(ctx context.Context, runID string, _ *tfe.Run return rc.(*tfe.Run), nil } -func (m *mockRuns) Apply(ctx context.Context, runID string, options tfe.RunApplyOptions) error { +func (m *MockRuns) Apply(ctx context.Context, runID string, options tfe.RunApplyOptions) error { m.Lock() defer m.Unlock() - r, ok := m.runs[runID] + r, ok := m.Runs[runID] if !ok { return tfe.ErrResourceNotFound } @@ -902,19 +889,19 @@ func (m *mockRuns) Apply(ctx context.Context, runID string, options tfe.RunApply return nil } -func (m *mockRuns) Cancel(ctx context.Context, runID string, options tfe.RunCancelOptions) error { +func (m *MockRuns) Cancel(ctx context.Context, runID string, options tfe.RunCancelOptions) error { panic("not implemented") } -func (m *mockRuns) ForceCancel(ctx context.Context, runID string, options tfe.RunForceCancelOptions) error { +func (m *MockRuns) ForceCancel(ctx context.Context, runID string, options tfe.RunForceCancelOptions) error { panic("not implemented") } -func (m *mockRuns) Discard(ctx context.Context, runID string, options tfe.RunDiscardOptions) error { +func (m *MockRuns) Discard(ctx context.Context, runID string, options tfe.RunDiscardOptions) error { m.Lock() defer m.Unlock() - r, ok := m.runs[runID] + r, ok := m.Runs[runID] if !ok { return tfe.ErrResourceNotFound } @@ -923,15 +910,15 @@ func (m *mockRuns) Discard(ctx context.Context, runID string, options tfe.RunDis return nil } -type mockStateVersions struct { - client *mockClient +type MockStateVersions struct { + client *MockClient states map[string][]byte stateVersions map[string]*tfe.StateVersion workspaces map[string][]string } -func newMockStateVersions(client *mockClient) *mockStateVersions { - return &mockStateVersions{ +func newMockStateVersions(client *MockClient) *MockStateVersions { + return &MockStateVersions{ client: client, states: make(map[string][]byte), stateVersions: make(map[string]*tfe.StateVersion), @@ -939,7 +926,7 @@ func newMockStateVersions(client *mockClient) *mockStateVersions { } } -func (m *mockStateVersions) List(ctx context.Context, options tfe.StateVersionListOptions) (*tfe.StateVersionList, error) { +func (m *MockStateVersions) List(ctx context.Context, options tfe.StateVersionListOptions) (*tfe.StateVersionList, error) { svl := &tfe.StateVersionList{} for _, sv := range m.stateVersions { svl.Items = append(svl.Items, sv) @@ -956,8 +943,8 @@ func (m *mockStateVersions) List(ctx context.Context, options tfe.StateVersionLi return svl, nil } -func (m *mockStateVersions) Create(ctx context.Context, workspaceID string, options tfe.StateVersionCreateOptions) (*tfe.StateVersion, error) { - id := generateID("sv-") +func (m *MockStateVersions) Create(ctx context.Context, workspaceID string, options tfe.StateVersionCreateOptions) (*tfe.StateVersion, error) { + id := GenerateID("sv-") runID := os.Getenv("TFE_RUN_ID") url := fmt.Sprintf("https://app.terraform.io/_archivist/%s", id) @@ -983,11 +970,11 @@ func (m *mockStateVersions) Create(ctx context.Context, workspaceID string, opti return sv, nil } -func (m *mockStateVersions) Read(ctx context.Context, svID string) (*tfe.StateVersion, error) { +func (m *MockStateVersions) Read(ctx context.Context, svID string) (*tfe.StateVersion, error) { return m.ReadWithOptions(ctx, svID, nil) } -func (m *mockStateVersions) ReadWithOptions(ctx context.Context, svID string, options *tfe.StateVersionReadOptions) (*tfe.StateVersion, error) { +func (m *MockStateVersions) ReadWithOptions(ctx context.Context, svID string, options *tfe.StateVersionReadOptions) (*tfe.StateVersion, error) { sv, ok := m.stateVersions[svID] if !ok { return nil, tfe.ErrResourceNotFound @@ -995,11 +982,11 @@ func (m *mockStateVersions) ReadWithOptions(ctx context.Context, svID string, op return sv, nil } -func (m *mockStateVersions) Current(ctx context.Context, workspaceID string) (*tfe.StateVersion, error) { +func (m *MockStateVersions) Current(ctx context.Context, workspaceID string) (*tfe.StateVersion, error) { return m.CurrentWithOptions(ctx, workspaceID, nil) } -func (m *mockStateVersions) CurrentWithOptions(ctx context.Context, workspaceID string, options *tfe.StateVersionCurrentOptions) (*tfe.StateVersion, error) { +func (m *MockStateVersions) CurrentWithOptions(ctx context.Context, workspaceID string, options *tfe.StateVersionCurrentOptions) (*tfe.StateVersion, error) { w, ok := m.client.Workspaces.workspaceIDs[workspaceID] if !ok { return nil, tfe.ErrResourceNotFound @@ -1018,7 +1005,7 @@ func (m *mockStateVersions) CurrentWithOptions(ctx context.Context, workspaceID return sv, nil } -func (m *mockStateVersions) Download(ctx context.Context, url string) ([]byte, error) { +func (m *MockStateVersions) Download(ctx context.Context, url string) ([]byte, error) { state, ok := m.states[url] if !ok { return nil, tfe.ErrResourceNotFound @@ -1026,28 +1013,32 @@ func (m *mockStateVersions) Download(ctx context.Context, url string) ([]byte, e return state, nil } -type mockVariables struct { - client *mockClient +func (m *MockStateVersions) Outputs(ctx context.Context, svID string, options tfe.StateVersionOutputsListOptions) ([]*tfe.StateVersionOutput, error) { + panic("not implemented") +} + +type MockVariables struct { + client *MockClient workspaces map[string]*tfe.VariableList } -var _ tfe.Variables = (*mockVariables)(nil) +var _ tfe.Variables = (*MockVariables)(nil) -func newMockVariables(client *mockClient) *mockVariables { - return &mockVariables{ +func newMockVariables(client *MockClient) *MockVariables { + return &MockVariables{ client: client, workspaces: make(map[string]*tfe.VariableList), } } -func (m *mockVariables) List(ctx context.Context, workspaceID string, options tfe.VariableListOptions) (*tfe.VariableList, error) { +func (m *MockVariables) List(ctx context.Context, workspaceID string, options tfe.VariableListOptions) (*tfe.VariableList, error) { vl := m.workspaces[workspaceID] return vl, nil } -func (m *mockVariables) Create(ctx context.Context, workspaceID string, options tfe.VariableCreateOptions) (*tfe.Variable, error) { +func (m *MockVariables) Create(ctx context.Context, workspaceID string, options tfe.VariableCreateOptions) (*tfe.Variable, error) { v := &tfe.Variable{ - ID: generateID("var-"), + ID: GenerateID("var-"), Key: *options.Key, Category: *options.Category, } @@ -1073,33 +1064,33 @@ func (m *mockVariables) Create(ctx context.Context, workspaceID string, options return v, nil } -func (m *mockVariables) Read(ctx context.Context, workspaceID string, variableID string) (*tfe.Variable, error) { +func (m *MockVariables) Read(ctx context.Context, workspaceID string, variableID string) (*tfe.Variable, error) { panic("not implemented") } -func (m *mockVariables) Update(ctx context.Context, workspaceID string, variableID string, options tfe.VariableUpdateOptions) (*tfe.Variable, error) { +func (m *MockVariables) Update(ctx context.Context, workspaceID string, variableID string, options tfe.VariableUpdateOptions) (*tfe.Variable, error) { panic("not implemented") } -func (m *mockVariables) Delete(ctx context.Context, workspaceID string, variableID string) error { +func (m *MockVariables) Delete(ctx context.Context, workspaceID string, variableID string) error { panic("not implemented") } -type mockWorkspaces struct { - client *mockClient +type MockWorkspaces struct { + client *MockClient workspaceIDs map[string]*tfe.Workspace workspaceNames map[string]*tfe.Workspace } -func newMockWorkspaces(client *mockClient) *mockWorkspaces { - return &mockWorkspaces{ +func newMockWorkspaces(client *MockClient) *MockWorkspaces { + return &MockWorkspaces{ client: client, workspaceIDs: make(map[string]*tfe.Workspace), workspaceNames: make(map[string]*tfe.Workspace), } } -func (m *mockWorkspaces) List(ctx context.Context, organization string, options tfe.WorkspaceListOptions) (*tfe.WorkspaceList, error) { +func (m *MockWorkspaces) List(ctx context.Context, organization string, options tfe.WorkspaceListOptions) (*tfe.WorkspaceList, error) { dummyWorkspaces := 10 wl := &tfe.WorkspaceList{} @@ -1129,7 +1120,7 @@ func (m *mockWorkspaces) List(ctx context.Context, organization string, options if options.PageNumber <= 1 { for i := 0; i < dummyWorkspaces; i++ { wl.Items = append(wl.Items, &tfe.Workspace{ - ID: generateID("ws-"), + ID: GenerateID("ws-"), Name: fmt.Sprintf("dummy-workspace-%d", i), }) } @@ -1156,14 +1147,14 @@ func (m *mockWorkspaces) List(ctx context.Context, organization string, options return wl, nil } -func (m *mockWorkspaces) Create(ctx context.Context, organization string, options tfe.WorkspaceCreateOptions) (*tfe.Workspace, error) { +func (m *MockWorkspaces) Create(ctx context.Context, organization string, options tfe.WorkspaceCreateOptions) (*tfe.Workspace, error) { if strings.HasSuffix(*options.Name, "no-operations") { options.Operations = tfe.Bool(false) } else if options.Operations == nil { options.Operations = tfe.Bool(true) } w := &tfe.Workspace{ - ID: generateID("ws-"), + ID: GenerateID("ws-"), Name: *options.Name, Operations: *options.Operations, Permissions: &tfe.WorkspacePermissions{ @@ -1187,7 +1178,7 @@ func (m *mockWorkspaces) Create(ctx context.Context, organization string, option return w, nil } -func (m *mockWorkspaces) Read(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) { +func (m *MockWorkspaces) Read(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) { // custom error for TestCloud_plan500 in backend_plan_test.go if workspace == "network-error" { return nil, errors.New("I'm a little teacup") @@ -1200,7 +1191,7 @@ func (m *mockWorkspaces) Read(ctx context.Context, organization, workspace strin return w, nil } -func (m *mockWorkspaces) ReadByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { +func (m *MockWorkspaces) ReadByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { w, ok := m.workspaceIDs[workspaceID] if !ok { return nil, tfe.ErrResourceNotFound @@ -1208,7 +1199,19 @@ func (m *mockWorkspaces) ReadByID(ctx context.Context, workspaceID string) (*tfe return w, nil } -func (m *mockWorkspaces) Update(ctx context.Context, organization, workspace string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) { +func (m *MockWorkspaces) ReadWithOptions(ctx context.Context, organization string, workspace string, options *tfe.WorkspaceReadOptions) (*tfe.Workspace, error) { + panic("not implemented") +} + +func (m *MockWorkspaces) ReadByIDWithOptions(ctx context.Context, workspaceID string, options *tfe.WorkspaceReadOptions) (*tfe.Workspace, error) { + w, ok := m.workspaceIDs[workspaceID] + if !ok { + return nil, tfe.ErrResourceNotFound + } + return w, nil +} + +func (m *MockWorkspaces) Update(ctx context.Context, organization, workspace string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) { w, ok := m.workspaceNames[workspace] if !ok { return nil, tfe.ErrResourceNotFound @@ -1233,7 +1236,7 @@ func (m *mockWorkspaces) Update(ctx context.Context, organization, workspace str return w, nil } -func (m *mockWorkspaces) UpdateByID(ctx context.Context, workspaceID string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) { +func (m *MockWorkspaces) UpdateByID(ctx context.Context, workspaceID string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) { w, ok := m.workspaceIDs[workspaceID] if !ok { return nil, tfe.ErrResourceNotFound @@ -1255,7 +1258,7 @@ func (m *mockWorkspaces) UpdateByID(ctx context.Context, workspaceID string, opt return w, nil } -func (m *mockWorkspaces) Delete(ctx context.Context, organization, workspace string) error { +func (m *MockWorkspaces) Delete(ctx context.Context, organization, workspace string) error { if w, ok := m.workspaceNames[workspace]; ok { delete(m.workspaceIDs, w.ID) } @@ -1263,7 +1266,7 @@ func (m *mockWorkspaces) Delete(ctx context.Context, organization, workspace str return nil } -func (m *mockWorkspaces) DeleteByID(ctx context.Context, workspaceID string) error { +func (m *MockWorkspaces) DeleteByID(ctx context.Context, workspaceID string) error { if w, ok := m.workspaceIDs[workspaceID]; ok { delete(m.workspaceIDs, w.Name) } @@ -1271,7 +1274,7 @@ func (m *mockWorkspaces) DeleteByID(ctx context.Context, workspaceID string) err return nil } -func (m *mockWorkspaces) RemoveVCSConnection(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) { +func (m *MockWorkspaces) RemoveVCSConnection(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) { w, ok := m.workspaceNames[workspace] if !ok { return nil, tfe.ErrResourceNotFound @@ -1280,7 +1283,7 @@ func (m *mockWorkspaces) RemoveVCSConnection(ctx context.Context, organization, return w, nil } -func (m *mockWorkspaces) RemoveVCSConnectionByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { +func (m *MockWorkspaces) RemoveVCSConnectionByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { w, ok := m.workspaceIDs[workspaceID] if !ok { return nil, tfe.ErrResourceNotFound @@ -1289,7 +1292,7 @@ func (m *mockWorkspaces) RemoveVCSConnectionByID(ctx context.Context, workspaceI return w, nil } -func (m *mockWorkspaces) Lock(ctx context.Context, workspaceID string, options tfe.WorkspaceLockOptions) (*tfe.Workspace, error) { +func (m *MockWorkspaces) Lock(ctx context.Context, workspaceID string, options tfe.WorkspaceLockOptions) (*tfe.Workspace, error) { w, ok := m.workspaceIDs[workspaceID] if !ok { return nil, tfe.ErrResourceNotFound @@ -1301,7 +1304,7 @@ func (m *mockWorkspaces) Lock(ctx context.Context, workspaceID string, options t return w, nil } -func (m *mockWorkspaces) Unlock(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { +func (m *MockWorkspaces) Unlock(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { w, ok := m.workspaceIDs[workspaceID] if !ok { return nil, tfe.ErrResourceNotFound @@ -1313,7 +1316,7 @@ func (m *mockWorkspaces) Unlock(ctx context.Context, workspaceID string) (*tfe.W return w, nil } -func (m *mockWorkspaces) ForceUnlock(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { +func (m *MockWorkspaces) ForceUnlock(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { w, ok := m.workspaceIDs[workspaceID] if !ok { return nil, tfe.ErrResourceNotFound @@ -1325,37 +1328,49 @@ func (m *mockWorkspaces) ForceUnlock(ctx context.Context, workspaceID string) (* return w, nil } -func (m *mockWorkspaces) AssignSSHKey(ctx context.Context, workspaceID string, options tfe.WorkspaceAssignSSHKeyOptions) (*tfe.Workspace, error) { +func (m *MockWorkspaces) AssignSSHKey(ctx context.Context, workspaceID string, options tfe.WorkspaceAssignSSHKeyOptions) (*tfe.Workspace, error) { panic("not implemented") } -func (m *mockWorkspaces) UnassignSSHKey(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { +func (m *MockWorkspaces) UnassignSSHKey(ctx context.Context, workspaceID string) (*tfe.Workspace, error) { panic("not implemented") } -func (m *mockWorkspaces) RemoteStateConsumers(ctx context.Context, workspaceID string) (*tfe.WorkspaceList, error) { +func (m *MockWorkspaces) RemoteStateConsumers(ctx context.Context, workspaceID string) (*tfe.WorkspaceList, error) { panic("not implemented") } -func (m *mockWorkspaces) AddRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceAddRemoteStateConsumersOptions) error { +func (m *MockWorkspaces) AddRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceAddRemoteStateConsumersOptions) error { panic("not implemented") } -func (m *mockWorkspaces) RemoveRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceRemoveRemoteStateConsumersOptions) error { +func (m *MockWorkspaces) RemoveRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceRemoveRemoteStateConsumersOptions) error { panic("not implemented") } -func (m *mockWorkspaces) UpdateRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceUpdateRemoteStateConsumersOptions) error { +func (m *MockWorkspaces) UpdateRemoteStateConsumers(ctx context.Context, workspaceID string, options tfe.WorkspaceUpdateRemoteStateConsumersOptions) error { panic("not implemented") } -func (m *mockWorkspaces) Readme(ctx context.Context, workspaceID string) (io.Reader, error) { +func (m *MockWorkspaces) Readme(ctx context.Context, workspaceID string) (io.Reader, error) { + panic("not implemented") +} + +func (m *MockWorkspaces) Tags(ctx context.Context, workspaceID string, options tfe.WorkspaceTagListOptions) (*tfe.TagList, error) { + panic("not implemented") +} + +func (m *MockWorkspaces) AddTags(ctx context.Context, workspaceID string, options tfe.WorkspaceAddTagsOptions) error { + panic("not implemented") +} + +func (m *MockWorkspaces) RemoveTags(ctx context.Context, workspaceID string, options tfe.WorkspaceRemoveTagsOptions) error { panic("not implemented") } const alphanumeric = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" -func generateID(s string) string { +func GenerateID(s string) string { b := make([]byte, 16) for i := range b { b[i] = alphanumeric[rand.Intn(len(alphanumeric))]