Merge pull request #1254 from p0tr3c-terraform/feat/tfc-human-friendly-workspace-names

feat: human readable tfc workspace names
main
Kamil Potrec 2021-12-09 08:52:36 +00:00 committed by GitHub
commit 817b68cbab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 645 additions and 86 deletions

1
go.mod
View File

@ -26,6 +26,7 @@ require (
github.com/hashicorp/go-getter v1.5.3
github.com/hashicorp/go-hclog v0.9.2
github.com/hashicorp/go-plugin v1.3.0
github.com/hashicorp/go-tfe v0.8.1
github.com/hashicorp/go-version v1.3.0
github.com/hashicorp/hcl/v2 v2.7.2
github.com/hashicorp/terraform v0.14.0

5
go.sum
View File

@ -342,6 +342,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
@ -422,10 +423,12 @@ github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
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-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
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.8.1 h1:J6ulpLaKPHrcnwudRjxvlMYIGzqQFlnPhg3SVFh5N4E=
github.com/hashicorp/go-tfe v0.8.1/go.mod h1:XAV72S4O1iP8BDaqiaPLmL2B4EE6almocnOn8E8stHc=
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=
@ -742,6 +745,7 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d h1:Z4EH+5EffvBEhh37F0C0DnpklTMh00JOkjW5zK3ofBI=
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.82+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20190808065407-f07404cefc8c/go.mod h1:wk2XFUg6egk4tSDNZtXeKfe2G6690UVyt163PuUxBZk=
@ -1032,6 +1036,7 @@ golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -52,7 +52,7 @@ func GetBackend(config config.SupplierConfig, opts *Options) (Backend, error) {
case BackendKeyHTTPS:
return NewHTTPReader(&http.Client{}, fmt.Sprintf("%s://%s", config.Backend, config.Path), opts)
case BackendKeyTFCloud:
return NewTFCloudReader(&http.Client{}, config.Path, opts)
return NewTFCloudReader(config.Path, opts), nil
default:
return nil, errors.Errorf("Unsupported backend '%s'", backend)
}

View File

@ -1,16 +1,16 @@
package backend
import (
"encoding/json"
"fmt"
"bytes"
"context"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"regexp"
"strings"
tfe "github.com/hashicorp/go-tfe"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
pkghttp "github.com/snyk/driftctl/pkg/http"
)
const BackendKeyTFCloud = "tfcloud"
@ -28,72 +28,103 @@ type TFCloudBody struct {
}
type TFCloudBackend struct {
request *http.Request
client pkghttp.HTTPClient
reader io.ReadCloser
opts *Options
client *tfe.Client
reader io.ReadCloser
opts *Options
workspacePath string
}
func NewTFCloudReader(client pkghttp.HTTPClient, workspaceId string, opts *Options) (*TFCloudBackend, error) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/workspaces/%s/current-state-version", opts.TFCloudEndpoint, workspaceId), nil)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", "application/vnd.api+json")
return &TFCloudBackend{req, client, nil, opts}, nil
func NewTFCloudReader(workspacePath string, opts *Options) *TFCloudBackend {
return &TFCloudBackend{opts: opts, workspacePath: workspacePath}
}
func (t *TFCloudBackend) authorize() error {
func (t *TFCloudBackend) getToken() (string, error) {
token := t.opts.TFCloudToken
if token == "" {
tfConfigFile, err := getTerraformConfigFile()
if err != nil {
return err
return "", err
}
file, err := os.Open(tfConfigFile)
if err != nil {
return err
return "", err
}
defer file.Close()
reader := NewTFCloudConfigReader(file)
token, err = reader.GetToken(t.request.URL.Host)
u, err := url.Parse(t.opts.TFCloudEndpoint)
if err != nil {
return err
return "", err
}
return reader.GetToken(u.Host)
}
t.request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
return token, nil
}
// A regular expression used to validate string workspace ID patterns.
var reStringID = regexp.MustCompile(`^ws-[a-zA-Z0-9\-\._]+$`)
// isValidWorkspaceID checks if the given input is present and non-empty.
func isValidWorkspaceID(v string) bool {
return v != "" && reStringID.MatchString(v)
}
func (t *TFCloudBackend) getWorkspaceId() (string, error) {
if isValidWorkspaceID(t.workspacePath) {
return t.workspacePath, nil
}
workspacePath := strings.Split(t.workspacePath, "/")
if len(workspacePath) != 2 {
return "", errors.New("unable to parse terraform cloud workspace, it should be either a workspace id (ws-xxxxx) or a {org}/{workspaceName}")
}
workspace, err := t.client.Workspaces.Read(context.Background(), workspacePath[0], workspacePath[1])
if err != nil {
return "", errors.Errorf("unable to read terraform workspace id: %s", err.Error())
}
return workspace.ID, nil
}
func (t *TFCloudBackend) initTFEClient() error {
token, err := t.getToken()
if err != nil {
return err
}
config := &tfe.Config{
Token: token,
Address: t.opts.TFCloudEndpoint,
}
tfcClient, err := tfe.NewClient(config)
if err != nil {
return err
}
t.client = tfcClient
return nil
}
func (t *TFCloudBackend) Read(p []byte) (n int, err error) {
if t.reader == nil {
if err := t.authorize(); err != nil {
return 0, err
if t.client == nil {
if err := t.initTFEClient(); err != nil {
return 0, err
}
}
res, err := t.client.Do(t.request)
workspaceId, err := t.getWorkspaceId()
if err != nil {
return 0, err
}
if res.StatusCode < 200 || res.StatusCode >= 400 {
return 0, errors.Errorf("error requesting terraform cloud backend state: status code: %d", res.StatusCode)
}
body := TFCloudBody{}
bodyBytes, _ := ioutil.ReadAll(res.Body)
err = json.Unmarshal(bodyBytes, &body)
stateVersion, err := t.client.StateVersions.Current(context.Background(), workspaceId)
if err != nil {
return 0, err
return 0, errors.Errorf("unable to read current state version: %s", err.Error())
}
rawURL := body.Data.Attributes.HostedStateDownloadUrl
logrus.WithFields(logrus.Fields{"hosted-state-download-url": rawURL}).Trace("Terraform Cloud backend response")
h, err := NewHTTPReader(t.client, rawURL, &Options{})
state, err := t.client.StateVersions.Download(context.Background(), stateVersion.DownloadURL)
if err != nil {
return 0, err
return 0, errors.Errorf("unable to download current state content: %s", err.Error())
}
t.reader = h
t.reader = io.NopCloser(bytes.NewReader(state))
}
return t.reader.Read(p)
}

View File

@ -1,17 +1,17 @@
package backend
import (
"net/http"
"testing"
"github.com/jarcoal/httpmock"
"github.com/snyk/driftctl/test/mocks"
tfe "github.com/hashicorp/go-tfe"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
mock "github.com/stretchr/testify/mock"
)
func TestTFCloudBackend_Read(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
type args struct {
workspaceId string
options *Options
@ -19,93 +19,130 @@ func TestTFCloudBackend_Read(t *testing.T) {
tests := []struct {
name string
args args
url string
wantErr error
expected string
mock func()
mock func(*mocks.Workspaces, *mocks.StateVersions)
}{
{
name: "Should fetch URL with auth header",
args: args{
workspaceId: "workspaceId",
workspaceId: "ws-ABCDEFG12345678",
options: &Options{
TFCloudToken: "TOKEN",
TFCloudEndpoint: "https://app.terraform.io/api/v2",
},
},
url: "https://app.terraform.io/api/v2/workspaces/workspaceId/current-state-version",
wantErr: nil,
expected: "{}",
mock: func() {
httpmock.Reset()
httpmock.RegisterResponder(
"GET",
"https://app.terraform.io/api/v2/workspaces/workspaceId/current-state-version",
httpmock.NewBytesResponder(http.StatusOK, []byte(`{"data":{"attributes":{"hosted-state-download-url":"https://archivist.terraform.io/v1/object/test"}}}`)),
)
httpmock.RegisterResponder(
"GET",
"https://archivist.terraform.io/v1/object/test",
httpmock.NewBytesResponder(http.StatusOK, []byte(`{}`)),
)
mock: func(Workspaces *mocks.Workspaces, StateVersions *mocks.StateVersions) {
retDownloadUrl := "https://archivist.terraform.io/v1/object/test"
StateVersions.On("Current", mock.Anything, "ws-ABCDEFG12345678").Return(&tfe.StateVersion{DownloadURL: retDownloadUrl}, nil)
StateVersions.On("Download", mock.Anything, retDownloadUrl).Return([]byte(`{}`), nil)
},
},
{
name: "Should resolve path and return state",
args: args{
workspaceId: "some-org/some-workspace",
options: &Options{
TFCloudToken: "TOKEN",
TFCloudEndpoint: "https://app.terraform.io/api/v2",
},
},
wantErr: nil,
expected: "{}",
mock: func(Workspaces *mocks.Workspaces, StateVersions *mocks.StateVersions) {
Workspaces.On("Read", mock.Anything, "some-org", "some-workspace").Return(&tfe.Workspace{ID: "ws-ABCDEFG12345678"}, nil)
retDownloadUrl := "https://archivist.terraform.io/v1/object/test"
StateVersions.On("Current", mock.Anything, "ws-ABCDEFG12345678").Return(&tfe.StateVersion{DownloadURL: retDownloadUrl}, nil)
StateVersions.On("Download", mock.Anything, retDownloadUrl).Return([]byte(`{}`), nil)
},
},
{
name: "Should fail with wrong workspaceId",
args: args{
workspaceId: "wrong_workspaceId",
workspaceId: "ws-ABCDEFG12345678",
options: &Options{
TFCloudToken: "TOKEN",
TFCloudEndpoint: "https://app.terraform.io/api/v2",
},
},
url: "https://app.terraform.io/api/v2/workspaces/wrong_workspaceId/current-state-version",
mock: func() {
httpmock.Reset()
httpmock.RegisterResponder(
"GET",
"https://app.terraform.io/api/v2/workspaces/wrong_workspaceId/current-state-version",
httpmock.NewBytesResponder(http.StatusNotFound, []byte{}),
)
mock: func(Workspaces *mocks.Workspaces, StateVersions *mocks.StateVersions) {
retDownloadUrl := "https://archivist.terraform.io/v1/object/test"
StateVersions.On("Current", mock.Anything, "ws-ABCDEFG12345678").Return(&tfe.StateVersion{DownloadURL: retDownloadUrl}, errors.New("resource not found"))
},
wantErr: errors.New("error requesting terraform cloud backend state: status code: 404"),
wantErr: errors.New("unable to read current state version: resource not found"),
},
{
name: "Should fail with bad authentication token",
name: "Should fail with download error",
args: args{
workspaceId: "workspaceId",
workspaceId: "ws-ABCDEFG12345678",
options: &Options{
TFCloudToken: "TOKEN",
TFCloudEndpoint: "https://app.terraform.io/api/v2",
},
},
url: "https://app.terraform.io/api/v2/workspaces/workspaceId/current-state-version",
mock: func() {
httpmock.Reset()
httpmock.RegisterResponder(
"GET",
"https://app.terraform.io/api/v2/workspaces/workspaceId/current-state-version",
httpmock.NewBytesResponder(http.StatusUnauthorized, []byte{}),
)
mock: func(Workspaces *mocks.Workspaces, StateVersions *mocks.StateVersions) {
retDownloadUrl := "https://archivist.terraform.io/v1/object/test"
StateVersions.On("Current", mock.Anything, "ws-ABCDEFG12345678").Return(&tfe.StateVersion{DownloadURL: retDownloadUrl}, nil)
StateVersions.On("Download", mock.Anything, retDownloadUrl).Return([]byte(`{}`), errors.New("connection terminated"))
},
wantErr: errors.New("error requesting terraform cloud backend state: status code: 401"),
wantErr: errors.New("unable to download current state content: connection terminated"),
},
{
name: "Should fail with bad authentication token - workspace id",
args: args{
workspaceId: "ws-ABCDEFG12345678",
options: &Options{
TFCloudToken: "TOKEN",
TFCloudEndpoint: "https://app.terraform.io/api/v2",
},
},
mock: func(Workspaces *mocks.Workspaces, StateVersions *mocks.StateVersions) {
retDownloadUrl := "https://archivist.terraform.io/v1/object/test"
StateVersions.On("Current", mock.Anything, "ws-ABCDEFG12345678").Return(&tfe.StateVersion{DownloadURL: retDownloadUrl}, errors.New("unauthorized"))
},
wantErr: errors.New("unable to read current state version: unauthorized"),
},
{
name: "Should fail with bad authentication token - full path",
args: args{
workspaceId: "some-org/some-workspace",
options: &Options{
TFCloudToken: "TOKEN",
TFCloudEndpoint: "https://app.terraform.io/api/v2",
},
},
mock: func(Workspaces *mocks.Workspaces, StateVersions *mocks.StateVersions) {
Workspaces.On("Read", mock.Anything, "some-org", "some-workspace").Return(&tfe.Workspace{ID: "ws-ABCDEFG12345678"}, errors.New("unauthorized"))
},
wantErr: errors.New("unable to read terraform workspace id: unauthorized"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mock()
reader := NewTFCloudReader(tt.args.workspaceId, tt.args.options)
reader, err := NewTFCloudReader(&http.Client{}, tt.args.workspaceId, tt.args.options)
assert.NoError(t, err)
fakeWorkspaces := &mocks.Workspaces{}
fakeStateVersions := &mocks.StateVersions{}
tt.mock(fakeWorkspaces, fakeStateVersions)
reader.client = &tfe.Client{
Workspaces: fakeWorkspaces,
StateVersions: fakeStateVersions,
}
got := make([]byte, len(tt.expected))
_, err = reader.Read(got)
_, err := reader.Read(got)
if tt.wantErr != nil {
assert.EqualError(t, err, tt.wantErr.Error())
return
} else {
assert.NoError(t, err)
}
fakeWorkspaces.AssertExpectations(t)
fakeStateVersions.AssertExpectations(t)
assert.NotNil(t, got)
assert.Equal(t, tt.expected, string(got))
})

131
test/mocks/StateVersions.go Normal file
View File

@ -0,0 +1,131 @@
// Code generated by mockery v2.9.4. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
tfe "github.com/hashicorp/go-tfe"
)
// StateVersions is an autogenerated mock type for the StateVersions type
type StateVersions struct {
mock.Mock
}
// Create provides a mock function with given fields: ctx, workspaceID, options
func (_m *StateVersions) Create(ctx context.Context, workspaceID string, options tfe.StateVersionCreateOptions) (*tfe.StateVersion, error) {
ret := _m.Called(ctx, workspaceID, options)
var r0 *tfe.StateVersion
if rf, ok := ret.Get(0).(func(context.Context, string, tfe.StateVersionCreateOptions) *tfe.StateVersion); ok {
r0 = rf(ctx, workspaceID, options)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.StateVersion)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, tfe.StateVersionCreateOptions) error); ok {
r1 = rf(ctx, workspaceID, options)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Current provides a mock function with given fields: ctx, workspaceID
func (_m *StateVersions) Current(ctx context.Context, workspaceID string) (*tfe.StateVersion, error) {
ret := _m.Called(ctx, workspaceID)
var r0 *tfe.StateVersion
if rf, ok := ret.Get(0).(func(context.Context, string) *tfe.StateVersion); ok {
r0 = rf(ctx, workspaceID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.StateVersion)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, workspaceID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Download provides a mock function with given fields: ctx, url
func (_m *StateVersions) Download(ctx context.Context, url string) ([]byte, error) {
ret := _m.Called(ctx, url)
var r0 []byte
if rf, ok := ret.Get(0).(func(context.Context, string) []byte); ok {
r0 = rf(ctx, url)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, url)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// List provides a mock function with given fields: ctx, options
func (_m *StateVersions) List(ctx context.Context, options tfe.StateVersionListOptions) (*tfe.StateVersionList, error) {
ret := _m.Called(ctx, options)
var r0 *tfe.StateVersionList
if rf, ok := ret.Get(0).(func(context.Context, tfe.StateVersionListOptions) *tfe.StateVersionList); ok {
r0 = rf(ctx, options)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.StateVersionList)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, tfe.StateVersionListOptions) error); ok {
r1 = rf(ctx, options)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Read provides a mock function with given fields: ctx, svID
func (_m *StateVersions) Read(ctx context.Context, svID string) (*tfe.StateVersion, error) {
ret := _m.Called(ctx, svID)
var r0 *tfe.StateVersion
if rf, ok := ret.Get(0).(func(context.Context, string) *tfe.StateVersion); ok {
r0 = rf(ctx, svID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.StateVersion)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, svID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

343
test/mocks/Workspaces.go Normal file
View File

@ -0,0 +1,343 @@
// Code generated by mockery v2.9.4. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
tfe "github.com/hashicorp/go-tfe"
)
// Workspaces is an autogenerated mock type for the Workspaces type
type Workspaces struct {
mock.Mock
}
// AssignSSHKey provides a mock function with given fields: ctx, workspaceID, options
func (_m *Workspaces) AssignSSHKey(ctx context.Context, workspaceID string, options tfe.WorkspaceAssignSSHKeyOptions) (*tfe.Workspace, error) {
ret := _m.Called(ctx, workspaceID, options)
var r0 *tfe.Workspace
if rf, ok := ret.Get(0).(func(context.Context, string, tfe.WorkspaceAssignSSHKeyOptions) *tfe.Workspace); ok {
r0 = rf(ctx, workspaceID, options)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.Workspace)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, tfe.WorkspaceAssignSSHKeyOptions) error); ok {
r1 = rf(ctx, workspaceID, options)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Create provides a mock function with given fields: ctx, organization, options
func (_m *Workspaces) Create(ctx context.Context, organization string, options tfe.WorkspaceCreateOptions) (*tfe.Workspace, error) {
ret := _m.Called(ctx, organization, options)
var r0 *tfe.Workspace
if rf, ok := ret.Get(0).(func(context.Context, string, tfe.WorkspaceCreateOptions) *tfe.Workspace); ok {
r0 = rf(ctx, organization, options)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.Workspace)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, tfe.WorkspaceCreateOptions) error); ok {
r1 = rf(ctx, organization, options)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Delete provides a mock function with given fields: ctx, organization, workspace
func (_m *Workspaces) Delete(ctx context.Context, organization string, workspace string) error {
ret := _m.Called(ctx, organization, workspace)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
r0 = rf(ctx, organization, workspace)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteByID provides a mock function with given fields: ctx, workspaceID
func (_m *Workspaces) DeleteByID(ctx context.Context, workspaceID string) error {
ret := _m.Called(ctx, workspaceID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, workspaceID)
} else {
r0 = ret.Error(0)
}
return r0
}
// ForceUnlock provides a mock function with given fields: ctx, workspaceID
func (_m *Workspaces) ForceUnlock(ctx context.Context, workspaceID string) (*tfe.Workspace, error) {
ret := _m.Called(ctx, workspaceID)
var r0 *tfe.Workspace
if rf, ok := ret.Get(0).(func(context.Context, string) *tfe.Workspace); ok {
r0 = rf(ctx, workspaceID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.Workspace)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, workspaceID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// List provides a mock function with given fields: ctx, organization, options
func (_m *Workspaces) List(ctx context.Context, organization string, options tfe.WorkspaceListOptions) (*tfe.WorkspaceList, error) {
ret := _m.Called(ctx, organization, options)
var r0 *tfe.WorkspaceList
if rf, ok := ret.Get(0).(func(context.Context, string, tfe.WorkspaceListOptions) *tfe.WorkspaceList); ok {
r0 = rf(ctx, organization, options)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.WorkspaceList)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, tfe.WorkspaceListOptions) error); ok {
r1 = rf(ctx, organization, options)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Lock provides a mock function with given fields: ctx, workspaceID, options
func (_m *Workspaces) Lock(ctx context.Context, workspaceID string, options tfe.WorkspaceLockOptions) (*tfe.Workspace, error) {
ret := _m.Called(ctx, workspaceID, options)
var r0 *tfe.Workspace
if rf, ok := ret.Get(0).(func(context.Context, string, tfe.WorkspaceLockOptions) *tfe.Workspace); ok {
r0 = rf(ctx, workspaceID, options)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.Workspace)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, tfe.WorkspaceLockOptions) error); ok {
r1 = rf(ctx, workspaceID, options)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Read provides a mock function with given fields: ctx, organization, workspace
func (_m *Workspaces) Read(ctx context.Context, organization string, workspace string) (*tfe.Workspace, error) {
ret := _m.Called(ctx, organization, workspace)
var r0 *tfe.Workspace
if rf, ok := ret.Get(0).(func(context.Context, string, string) *tfe.Workspace); ok {
r0 = rf(ctx, organization, workspace)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.Workspace)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, organization, workspace)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ReadByID provides a mock function with given fields: ctx, workspaceID
func (_m *Workspaces) ReadByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) {
ret := _m.Called(ctx, workspaceID)
var r0 *tfe.Workspace
if rf, ok := ret.Get(0).(func(context.Context, string) *tfe.Workspace); ok {
r0 = rf(ctx, workspaceID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.Workspace)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, workspaceID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoveVCSConnection provides a mock function with given fields: ctx, organization, workspace
func (_m *Workspaces) RemoveVCSConnection(ctx context.Context, organization string, workspace string) (*tfe.Workspace, error) {
ret := _m.Called(ctx, organization, workspace)
var r0 *tfe.Workspace
if rf, ok := ret.Get(0).(func(context.Context, string, string) *tfe.Workspace); ok {
r0 = rf(ctx, organization, workspace)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.Workspace)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, organization, workspace)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoveVCSConnectionByID provides a mock function with given fields: ctx, workspaceID
func (_m *Workspaces) RemoveVCSConnectionByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) {
ret := _m.Called(ctx, workspaceID)
var r0 *tfe.Workspace
if rf, ok := ret.Get(0).(func(context.Context, string) *tfe.Workspace); ok {
r0 = rf(ctx, workspaceID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.Workspace)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, workspaceID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UnassignSSHKey provides a mock function with given fields: ctx, workspaceID
func (_m *Workspaces) UnassignSSHKey(ctx context.Context, workspaceID string) (*tfe.Workspace, error) {
ret := _m.Called(ctx, workspaceID)
var r0 *tfe.Workspace
if rf, ok := ret.Get(0).(func(context.Context, string) *tfe.Workspace); ok {
r0 = rf(ctx, workspaceID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.Workspace)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, workspaceID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Unlock provides a mock function with given fields: ctx, workspaceID
func (_m *Workspaces) Unlock(ctx context.Context, workspaceID string) (*tfe.Workspace, error) {
ret := _m.Called(ctx, workspaceID)
var r0 *tfe.Workspace
if rf, ok := ret.Get(0).(func(context.Context, string) *tfe.Workspace); ok {
r0 = rf(ctx, workspaceID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.Workspace)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, workspaceID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Update provides a mock function with given fields: ctx, organization, workspace, options
func (_m *Workspaces) Update(ctx context.Context, organization string, workspace string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) {
ret := _m.Called(ctx, organization, workspace, options)
var r0 *tfe.Workspace
if rf, ok := ret.Get(0).(func(context.Context, string, string, tfe.WorkspaceUpdateOptions) *tfe.Workspace); ok {
r0 = rf(ctx, organization, workspace, options)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.Workspace)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, string, tfe.WorkspaceUpdateOptions) error); ok {
r1 = rf(ctx, organization, workspace, options)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateByID provides a mock function with given fields: ctx, workspaceID, options
func (_m *Workspaces) UpdateByID(ctx context.Context, workspaceID string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) {
ret := _m.Called(ctx, workspaceID, options)
var r0 *tfe.Workspace
if rf, ok := ret.Get(0).(func(context.Context, string, tfe.WorkspaceUpdateOptions) *tfe.Workspace); ok {
r0 = rf(ctx, workspaceID, options)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*tfe.Workspace)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, tfe.WorkspaceUpdateOptions) error); ok {
r1 = rf(ctx, workspaceID, options)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

11
test/tfe/tfe.go Normal file
View File

@ -0,0 +1,11 @@
package test_tfe
import "github.com/hashicorp/go-tfe"
type Workspaces interface {
tfe.Workspaces
}
type StateVersions interface {
tfe.StateVersions
}