diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index e85e40dd..5bfbc9b6 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated // Code generated by controller-gen. DO NOT EDIT. diff --git a/go.mod b/go.mod index e0c7dda4..e078ba99 100644 --- a/go.mod +++ b/go.mod @@ -11,14 +11,14 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gnostic v0.5.6 // indirect - github.com/hashicorp/go-tfe v0.21.0 + github.com/hashicorp/go-tfe v1.3.0 github.com/hashicorp/terraform v0.15.2 github.com/json-iterator/go v1.1.12 // indirect github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.15.0 github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.7.2 github.com/zclconf/go-cty v1.9.1 golang.org/x/net v0.0.0-20211020060615-d418f374d309 // indirect golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 // indirect diff --git a/go.sum b/go.sum index 175d14c4..d424d162 100644 --- a/go.sum +++ b/go.sum @@ -352,24 +352,24 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.4.1/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= -github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= +github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= 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-slug v0.8.1 h1:srN7ivgAjHfZddYY1DjBaihRCFy20+vCcOrlx1O2AfE= +github.com/hashicorp/go-slug v0.8.1/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4= 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.14.0/go.mod h1:B71izbwmCZdhEo/GzHopCXN3P74cYv2tsff1mxY4J6c= -github.com/hashicorp/go-tfe v0.21.0 h1:P1QoeLkigDi4BXGQ//42kyXwfcHUqbh5jJemml6iQJs= -github.com/hashicorp/go-tfe v0.21.0/go.mod h1:gyXLXbpBVxA2F/6opah8XBsOkZJxHYQmghl0OWi8keI= +github.com/hashicorp/go-tfe v1.3.0 h1:5sboIfj0Uz6YAfPeDAVRXBKf3EI3D054kTbmOoUUW3g= +github.com/hashicorp/go-tfe v1.3.0/go.mod h1:5PORBlPPMya01sElYhCLUMu07BHGTwP5CRedU26SjPM= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -639,8 +639,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV 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/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/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 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= @@ -1096,8 +1097,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/vendor/github.com/hashicorp/go-retryablehttp/README.md b/vendor/github.com/hashicorp/go-retryablehttp/README.md index 8943becf..09f5eaf2 100644 --- a/vendor/github.com/hashicorp/go-retryablehttp/README.md +++ b/vendor/github.com/hashicorp/go-retryablehttp/README.md @@ -45,6 +45,25 @@ The returned response object is an `*http.Response`, the same thing you would usually get from `net/http`. Had the request failed one or more times, the above call would block and retry with exponential backoff. +## Retrying cases that fail after a seeming success + +It's possible for a request to succeed in the sense that the expected response headers are received, but then to encounter network-level errors while reading the response body. In go-retryablehttp's most basic usage, this error would not be retryable, due to the out-of-band handling of the response body. In some cases it may be desirable to handle the response body as part of the retryable operation. + +A toy example (which will retry the full request and succeed on the second attempt) is shown below: + +```go +c := retryablehttp.NewClient() +r := retryablehttp.NewRequest("GET", "://foo", nil) +handlerShouldRetry := true +r.SetResponseHandler(func(*http.Response) error { + if !handlerShouldRetry { + return nil + } + handlerShouldRetry = false + return errors.New("retryable error") +}) +``` + ## Getting a stdlib `*http.Client` with retries It's possible to convert a `*retryablehttp.Client` directly to a `*http.Client`. diff --git a/vendor/github.com/hashicorp/go-retryablehttp/client.go b/vendor/github.com/hashicorp/go-retryablehttp/client.go index adbdd92e..57116e96 100644 --- a/vendor/github.com/hashicorp/go-retryablehttp/client.go +++ b/vendor/github.com/hashicorp/go-retryablehttp/client.go @@ -69,11 +69,21 @@ var ( // scheme specified in the URL is invalid. This error isn't typed // specifically so we resort to matching on the error string. schemeErrorRe = regexp.MustCompile(`unsupported protocol scheme`) + + // A regular expression to match the error returned by net/http when the + // TLS certificate is not trusted. This error isn't typed + // specifically so we resort to matching on the error string. + notTrustedErrorRe = regexp.MustCompile(`certificate is not trusted`) ) // ReaderFunc is the type of function that can be given natively to NewRequest type ReaderFunc func() (io.Reader, error) +// ResponseHandlerFunc is a type of function that takes in a Response, and does something with it. +// It only runs if the initial part of the request was successful. +// If an error is returned, the client's retry policy will be used to determine whether to retry the whole request. +type ResponseHandlerFunc func(*http.Response) error + // LenReader is an interface implemented by many in-memory io.Reader's. Used // for automatically sending the right Content-Length header when possible. type LenReader interface { @@ -86,6 +96,8 @@ type Request struct { // used to rewind the request data in between retries. body ReaderFunc + responseHandler ResponseHandlerFunc + // Embed an HTTP request directly. This makes a *Request act exactly // like an *http.Request so that all meta methods are supported. *http.Request @@ -94,8 +106,16 @@ type Request struct { // WithContext returns wrapped Request with a shallow copy of underlying *http.Request // with its context changed to ctx. The provided ctx must be non-nil. func (r *Request) WithContext(ctx context.Context) *Request { - r.Request = r.Request.WithContext(ctx) - return r + return &Request{ + body: r.body, + responseHandler: r.responseHandler, + Request: r.Request.WithContext(ctx), + } +} + +// SetResponseHandler allows setting the response handler. +func (r *Request) SetResponseHandler(fn ResponseHandlerFunc) { + r.responseHandler = fn } // BodyBytes allows accessing the request body. It is an analogue to @@ -252,23 +272,31 @@ func FromRequest(r *http.Request) (*Request, error) { return nil, err } // Could assert contentLength == r.ContentLength - return &Request{bodyReader, r}, nil + return &Request{body: bodyReader, Request: r}, nil } // NewRequest creates a new wrapped request. func NewRequest(method, url string, rawBody interface{}) (*Request, error) { + return NewRequestWithContext(context.Background(), method, url, rawBody) +} + +// NewRequestWithContext creates a new wrapped request with the provided context. +// +// The context controls the entire lifetime of a request and its response: +// obtaining a connection, sending the request, and reading the response headers and body. +func NewRequestWithContext(ctx context.Context, method, url string, rawBody interface{}) (*Request, error) { bodyReader, contentLength, err := getBodyReaderAndContentLength(rawBody) if err != nil { return nil, err } - httpReq, err := http.NewRequest(method, url, nil) + httpReq, err := http.NewRequestWithContext(ctx, method, url, nil) if err != nil { return nil, err } httpReq.ContentLength = contentLength - return &Request{bodyReader, httpReq}, nil + return &Request{body: bodyReader, Request: httpReq}, nil } // Logger interface allows to use other loggers than @@ -435,6 +463,9 @@ func baseRetryPolicy(resp *http.Response, err error) (bool, error) { } // Don't retry if the error was due to TLS cert verification failure. + if notTrustedErrorRe.MatchString(v.Error()) { + return false, v + } if _, ok := v.Err.(x509.UnknownAuthorityError); ok { return false, v } @@ -455,7 +486,7 @@ func baseRetryPolicy(resp *http.Response, err error) (bool, error) { // the server time to recover, as 500's are typically not permanent // errors and may relate to outages on the server side. This will catch // invalid response codes as well, like 0 and 999. - if resp.StatusCode == 0 || (resp.StatusCode >= 500 && resp.StatusCode != 501) { + if resp.StatusCode == 0 || (resp.StatusCode >= 500 && resp.StatusCode != http.StatusNotImplemented) { return true, fmt.Errorf("unexpected HTTP status %s", resp.Status) } @@ -555,13 +586,12 @@ func (c *Client) Do(req *Request) (*http.Response, error) { var resp *http.Response var attempt int var shouldRetry bool - var doErr, checkErr error + var doErr, respErr, checkErr error for i := 0; ; i++ { + doErr, respErr = nil, nil attempt++ - var code int // HTTP response code - // Always rewind the request body when non-nil. if req.body != nil { body, err := req.body() @@ -589,19 +619,24 @@ func (c *Client) Do(req *Request) (*http.Response, error) { // Attempt the request resp, doErr = c.HTTPClient.Do(req.Request) - if resp != nil { - code = resp.StatusCode - } // Check if we should continue with retries. shouldRetry, checkErr = c.CheckRetry(req.Context(), resp, doErr) + if !shouldRetry && doErr == nil && req.responseHandler != nil { + respErr = req.responseHandler(resp) + shouldRetry, checkErr = c.CheckRetry(req.Context(), resp, respErr) + } - if doErr != nil { + err := doErr + if respErr != nil { + err = respErr + } + if err != nil { switch v := logger.(type) { case LeveledLogger: - v.Error("request failed", "error", doErr, "method", req.Method, "url", req.URL) + v.Error("request failed", "error", err, "method", req.Method, "url", req.URL) case Logger: - v.Printf("[ERR] %s %s request failed: %v", req.Method, req.URL, doErr) + v.Printf("[ERR] %s %s request failed: %v", req.Method, req.URL, err) } } else { // Call this here to maintain the behavior of logging all requests, @@ -636,11 +671,11 @@ func (c *Client) Do(req *Request) (*http.Response, error) { } wait := c.Backoff(c.RetryWaitMin, c.RetryWaitMax, i, resp) - desc := fmt.Sprintf("%s %s", req.Method, req.URL) - if code > 0 { - desc = fmt.Sprintf("%s (status: %d)", desc, code) - } if logger != nil { + desc := fmt.Sprintf("%s %s", req.Method, req.URL) + if resp != nil { + desc = fmt.Sprintf("%s (status: %d)", desc, resp.StatusCode) + } switch v := logger.(type) { case LeveledLogger: v.Debug("retrying request", "request", desc, "timeout", wait, "remaining", remain) @@ -648,11 +683,13 @@ func (c *Client) Do(req *Request) (*http.Response, error) { v.Printf("[DEBUG] %s: retrying in %s (%d left)", desc, wait, remain) } } + timer := time.NewTimer(wait) select { case <-req.Context().Done(): + timer.Stop() c.HTTPClient.CloseIdleConnections() return nil, req.Context().Err() - case <-time.After(wait): + case <-timer.C: } // Make shallow copy of http Request so that we can modify its body @@ -662,15 +699,19 @@ func (c *Client) Do(req *Request) (*http.Response, error) { } // this is the closest we have to success criteria - if doErr == nil && checkErr == nil && !shouldRetry { + if doErr == nil && respErr == nil && checkErr == nil && !shouldRetry { return resp, nil } defer c.HTTPClient.CloseIdleConnections() - err := doErr + var err error if checkErr != nil { err = checkErr + } else if respErr != nil { + err = respErr + } else { + err = doErr } if c.ErrorHandler != nil { diff --git a/vendor/github.com/hashicorp/go-slug/slug.go b/vendor/github.com/hashicorp/go-slug/slug.go index 4f6ca940..a29f3208 100644 --- a/vendor/github.com/hashicorp/go-slug/slug.go +++ b/vendor/github.com/hashicorp/go-slug/slug.go @@ -386,7 +386,7 @@ func Unpack(r io.Reader, dst string) error { } // Only unpack regular files from this point on. - if header.Typeflag == tar.TypeDir { + if header.Typeflag == tar.TypeDir || header.Typeflag == tar.TypeXGlobalHeader || header.Typeflag == tar.TypeXHeader { continue } else if header.Typeflag != tar.TypeReg && header.Typeflag != tar.TypeRegA { return fmt.Errorf("failed creating %q: unsupported type %c", path, diff --git a/vendor/github.com/hashicorp/go-slug/terraformignore.go b/vendor/github.com/hashicorp/go-slug/terraformignore.go index 4f478be2..6803313c 100644 --- a/vendor/github.com/hashicorp/go-slug/terraformignore.go +++ b/vendor/github.com/hashicorp/go-slug/terraformignore.go @@ -192,6 +192,7 @@ func (r *rule) compile() error { .git/ .terraform/ !.terraform/modules/ + terraform.tfstate */ var defaultExclusions = []rule{ @@ -207,6 +208,10 @@ var defaultExclusions = []rule{ val: filepath.Join("**", ".terraform", "modules", "**"), excluded: true, }, + { + val: filepath.Join("**", "terraform.tfstate"), + excluded: false, + }, } func debug(printAll bool, path string, message ...interface{}) { diff --git a/vendor/github.com/hashicorp/go-tfe/.golangci.yml b/vendor/github.com/hashicorp/go-tfe/.golangci.yml index 039ccfb8..0fa0a95c 100644 --- a/vendor/github.com/hashicorp/go-tfe/.golangci.yml +++ b/vendor/github.com/hashicorp/go-tfe/.golangci.yml @@ -10,10 +10,14 @@ linters: - nestif #https://github.com/nakabonne/nestif - exportloopref #https://github.com/kyoh86/exportloopref - bodyclose #https://github.com/timakin/bodyclose + - goconst #https://github.com/jgautheron/goconst - errcheck #https://github.com/kisielk/errcheck - stylecheck #https://github.com/dominikh/go-tools/tree/master/stylecheck - revive #golint is deprecated and golangci-lint recommends to use revive instead https://github.com/mgechev/revive #other deprecated lint libraries: maligned, scopelint, interfacer + - gocritic #https://github.com/go-critic/go-critic + - unparam #https://github.com/mvdan/unparam + - misspell #https://github.com/client9/misspell issues: exclude-rules: - path: _test\.go @@ -25,6 +29,20 @@ linters-settings: # https://github.com/kisielk/errcheck#excluding-functions check-type-assertions: true check-blank: true + goconst: + min-len: 20 + min-occurrences: 5 + ignore-calls: false + gocritic: + enabled-tags: + - diagnostic + - opinionated + - performance + disabled-checks: + - unnamedResult + - hugeParam + - singleCaseSwitch + - ifElseChain revive: # see https://github.com/mgechev/revive#available-rules for details. ignore-generated-header: false #recommended in their configuration @@ -33,4 +51,4 @@ linters-settings: - name: indent-error-flow #Prevents redundant else statements severity: warning - name: useless-break - severity: warning \ No newline at end of file + severity: warning diff --git a/vendor/github.com/hashicorp/go-tfe/CHANGELOG.md b/vendor/github.com/hashicorp/go-tfe/CHANGELOG.md new file mode 100644 index 00000000..a66ed07f --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/CHANGELOG.md @@ -0,0 +1,55 @@ +# v1.3.0 + +## Enhancements +* Adds support for Microsoft Teams notification configuration by @JarrettSpiker [#398](https://github.com/hashicorp/go-tfe/pull/389) +* Add support for Audit Trail API by @sebasslash [#407](https://github.com/hashicorp/go-tfe/pull/407) +* Adds Private Registry Provider, Provider Version, and Provider Platform APIs support by @joekarl and @annawinkler [#313](https://github.com/hashicorp/go-tfe/pull/313) +* Adds List Registry Modules endpoint by @chroju [#385](https://github.com/hashicorp/go-tfe/pull/385) +* Adds `WebhookURL` field to `VCSRepo` struct by @kgns [#413](https://github.com/hashicorp/go-tfe/pull/413) +* Adds `Category` field to `VariableUpdateOptions` struct by @jtyr [#397](https://github.com/hashicorp/go-tfe/pull/397) +* Adds `TriggerPatterns` to `Workspace` by @matejrisek [#400](https://github.com/hashicorp/go-tfe/pull/400) +* [beta] Adds `ExtState` field to `StateVersionCreateOptions` by @brandonc [#416](https://github.com/hashicorp/go-tfe/pull/416) + +# v1.2.0 + +## Enhancements +* Adds support for reading current state version outputs to StateVersionOutputs, which can be useful for reading outputs when users don't have the necessary permissions to read the entire state by @brandonc [#370](https://github.com/hashicorp/go-tfe/pull/370) +* Adds Variable Set methods for `ApplyToWorkspaces` and `RemoveFromWorkspaces` by @byronwolfman [#375](https://github.com/hashicorp/go-tfe/pull/375) +* Adds `Names` query param field to `TeamListOptions` by @sebasslash [#393](https://github.com/hashicorp/go-tfe/pull/393) +* Adds `Emails` query param field to `OrganizationMembershipListOptions` by @sebasslash [#393](https://github.com/hashicorp/go-tfe/pull/393) +* Adds Run Tasks API support by @glennsarti [#381](https://github.com/hashicorp/go-tfe/pull/381), [#382](https://github.com/hashicorp/go-tfe/pull/382) and [#383](https://github.com/hashicorp/go-tfe/pull/383) + + +## Bug fixes +* Fixes ignored comment when performing apply, discard, cancel, and force-cancel run actions [#388](https://github.com/hashicorp/go-tfe/pull/388) + +# v1.1.0 + +## Enhancements + +* Add Variable Set API support by @rexredinger [#305](https://github.com/hashicorp/go-tfe/pull/305) +* Add Comments API support by @alex-ikse [#355](https://github.com/hashicorp/go-tfe/pull/355) +* Add beta support for SSOTeamID to `Team`, `TeamCreateOptions`, `TeamUpdateOptions` by @xlgmokha [#364](https://github.com/hashicorp/go-tfe/pull/364) + +# v1.0.0 + +## Breaking Changes +* Renamed methods named Generate to Create for `AgentTokens`, `OrganizationTokens`, `TeamTokens`, `UserTokens` by @sebasslash [#327](https://github.com/hashicorp/go-tfe/pull/327) +* Methods that express an action on a relationship have been prefixed with a verb, e.g `Current()` is now `ReadCurrent()` by @sebasslash [#327](https://github.com/hashicorp/go-tfe/pull/327) +* All list option structs are now pointers @uturunku1 [#309](https://github.com/hashicorp/go-tfe/pull/309) +* All errors have been refactored into constants in `errors.go` @uturunku1 [#310](https://github.com/hashicorp/go-tfe/pull/310) +* The `ID` field in Create/Update option structs has been renamed to `Type` in accordance with the JSON:API spec by @omarismail, @uturunku1 [#190](https://github.com/hashicorp/go-tfe/pull/190), [#323](https://github.com/hashicorp/go-tfe/pull/323), [#332](https://github.com/hashicorp/go-tfe/pull/332) +* Nested URL params (consisting of an organization, module and provider name) used to identify a `RegistryModule` have been refactored into a struct `RegistryModuleID` by @sebasslash [#337](https://github.com/hashicorp/go-tfe/pull/337) + + +## Enhancements +* Added missing include fields for `AdminRuns`, `AgentPools`, `ConfigurationVersions`, `OAuthClients`, `Organizations`, `PolicyChecks`, `PolicySets`, `Policies` and `RunTriggers` by @uturunku1 [#339](https://github.com/hashicorp/go-tfe/pull/339) +* Cleanup documentation and improve consistency by @uturunku1 [#331](https://github.com/hashicorp/go-tfe/pull/331) +* Add more linters to our CI pipeline by @sebasslash [#326](https://github.com/hashicorp/go-tfe/pull/326) +* Resolve `TFE_HOSTNAME` as fallback for `TFE_ADDRESS` by @sebasslash [#340](https://github.com/hashicorp/go-tfe/pull/326) +* Adds a `fetching` status to `RunStatus` and adds the `Archive` method to the ConfigurationVersions interface by @mpminardi [#338](https://github.com/hashicorp/go-tfe/pull/338) +* Added a `Download` method to the `ConfigurationVersions` interface by @tylerwolf [#358](https://github.com/hashicorp/go-tfe/pull/358) +* API Coverage documentation by @laurenolivia [#334](https://github.com/hashicorp/go-tfe/pull/334) + +## Bug Fixes +* Fixed invalid memory address error when `AdminSMTPSettingsUpdateOptions.Auth` field is empty and accessed by @uturunku1 [#335](https://github.com/hashicorp/go-tfe/pull/335) diff --git a/vendor/github.com/hashicorp/go-tfe/CODEOWNERS b/vendor/github.com/hashicorp/go-tfe/CODEOWNERS new file mode 100644 index 00000000..989a818a --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/CODEOWNERS @@ -0,0 +1,2 @@ +*.go @hashicorp/tf-cli +*.md @hashicorp/tf-cli diff --git a/vendor/github.com/hashicorp/go-tfe/README.md b/vendor/github.com/hashicorp/go-tfe/README.md index 0becaa83..1828af68 100644 --- a/vendor/github.com/hashicorp/go-tfe/README.md +++ b/vendor/github.com/hashicorp/go-tfe/README.md @@ -16,11 +16,9 @@ documentation and API, the platform will always be stated as 'Terraform Enterprise' - but a feature will be explicitly noted as only supported in one or the other, if applicable (rare). -Note this client is in beta and is subject to change (though it is generally -quite stable). We will indicate any breaking changes by releasing new versions. -Until the release of v1.0, any minor version changes will indicate possible -breaking changes. Patch version changes will be used for both bugfixes and -non-breaking changes. +## Version Information + +Almost always, minor version changes will indicate backwards-compatible features and enhancements. Occasionally, function signature changes that reflect a bug fix may appear as a minor version change. Patch version changes will be used for bug fixes, performance improvements, and otherwise unimpactful changes. ## Installation @@ -50,7 +48,7 @@ if err != nil { log.Fatal(err) } -orgs, err := client.Organizations.List(context.Background(), tfe.OrganizationListOptions{}) +orgs, err := client.Organizations.List(context.Background(), nil) if err != nil { log.Fatal(err) } @@ -58,7 +56,72 @@ if err != nil { ## Documentation -For complete usage of the API client, see the full [package docs](https://pkg.go.dev/github.com/hashicorp/go-tfe). +For complete usage of the API client, see the [full package docs](https://pkg.go.dev/github.com/hashicorp/go-tfe). + +## API Coverage + +This API client covers most of the existing Terraform Cloud API calls and is updated regularly to add new or missing endpoints. + +- [x] Account +- [x] Agent Pools +- [x] Agent Tokens +- [x] Applies +- [x] Audit Trails +- [x] Changelog +- [x] Comments +- [x] Configuration Versions +- [x] Cost Estimation +- [ ] Feature Sets +- [ ] Invoices +- [x] IP Ranges +- [x] Notification Configurations +- [x] OAuth Clients +- [x] OAuth Tokens +- [x] Organizations +- [x] Organization Memberships +- [x] Organization Tags +- [x] Organization Tokens +- [x] Plan Exports +- [x] Plans +- [x] Policies +- [x] Policy Checks +- [x] Policy Sets +- [x] Policy Set Parameters +- [ ] Private Registry + - [x] Modules + - [ ] Providers + - [ ] Provider Provider Versions and Platforms + - [ ] GPG Keys +- [x] Runs +- [x] Run Tasks +- [ ] Run Tasks Integration +- [x] Run Triggers +- [x] SSH Keys +- [x] Stability Policy +- [x] State Versions +- [x] State Version Outputs +- [ ] Subscriptions +- [x] Team Access +- [x] Team Membership +- [x] Team Tokens +- [x] Teams +- [x] User Tokens +- [x] Users +- [x] Variable Sets +- [x] Variables +- [ ] VCS Events +- [x] Workspaces +- [x] Workspace-Specific Variables +- [x] Workspace Resources +- [x] Admin + - [x] Module Sharing + - [x] Organizations + - [x] Runs + - [x] Settings + - [x] Terraform Versions + - [x] Users + - [x] Workspaces + ## Examples @@ -66,41 +129,12 @@ See the [examples directory](https://github.com/hashicorp/go-tfe/tree/main/examp ## Running tests -See [TESTS.md](https://github.com/hashicorp/go-tfe/tree/main/TESTS.md). +See [TESTS.md](docs/TESTS.md). ## Issues and Contributing -If you find an issue with this package, please report an issue. If you'd like, -we welcome any contributions. Fork this repository and submit a pull request. +See [CONTRIBUTING.md](docs/CONTRIBUTING.md) ## Releases -Documentation updates and test fixes that only touch test files don't require a release or tag. You can just merge these changes into `main` once they have been approved. - -### Creating a release - -1. [Create a new release in GitHub](https://help.github.com/en/github/administering-a-repository/creating-releases) by clicking on "Releases" and then "Draft a new release" -1. Set the `Tag version` to a new tag, using [Semantic Versioning](https://semver.org/) as a guideline. -1. Set the `Target` as `main`. -1. Set the `Release title` to the tag you created, `vX.Y.Z` -1. Use the description section to describe why you're releasing and what changes you've made. You should include links to merged PRs. Use the following headers in the description of your release: - - BREAKING CHANGES: Use this for any changes that aren't backwards compatible. Include details on how to handle these changes. - - FEATURES: Use this for any large new features added, - - ENHANCEMENTS: Use this for smaller new features added - - BUG FIXES: Use this for any bugs that were fixed. - - NOTES: Use this section if you need to include any additional notes on things like upgrading, upcoming deprecations, or any other information you might want to highlight. - - Markdown example: - - ```markdown - ENHANCEMENTS - * Add description of new small feature (#3)[link-to-pull-request] - - BUG FIXES - * Fix description of a bug (#2)[link-to-pull-request] - * Fix description of another bug (#1)[link-to-pull-request] - ``` - -1. Don't attach any binaries. The zip and tar.gz assets are automatically created and attached after you publish your release. -1. Click "Publish release" to save and publish your release. - +See [RELEASES.md](docs/RELEASES.md) diff --git a/vendor/github.com/hashicorp/go-tfe/TESTS.md b/vendor/github.com/hashicorp/go-tfe/TESTS.md deleted file mode 100644 index 8f171d6d..00000000 --- a/vendor/github.com/hashicorp/go-tfe/TESTS.md +++ /dev/null @@ -1,79 +0,0 @@ -## Running tests - -### 1. (Optional) Create repositories for policy sets and registry modules - -If you are planning to run the full suite of tests or work on policy sets or registry modules, you'll need to set up repositories for them in GitHub. - -Your policy set repository will need the following: -1. A policy set stored in a subdirectory `policy-sets/foo` -1. A branch other than `main` named `policies` - -Your registry module repository will need to be a [valid module](https://www.terraform.io/docs/cloud/registry/publish.html#preparing-a-module-repository). -It will need the following: -1. To be named `terraform--` -1. At least one valid SemVer tag in the format `x.y.z` -[terraform-random-module](ttps://github.com/caseylang/terraform-random-module) is a good example repo. - -### 2. Set up environment variables - -##### Required: -Tests are run against an actual backend so they require a valid backend address and token. -1. `TFE_ADDRESS` - URL of a Terraform Cloud or Terraform Enterprise instance to be used for testing, including scheme. Example: `https://tfe.local` -1. `TFE_TOKEN` - A [user API token](https://www.terraform.io/docs/cloud/users-teams-organizations/users.html#api-tokens) for the Terraform Cloud or Terraform Enterprise instance being used for testing. - -##### Optional: -1. `GITHUB_TOKEN` - [GitHub personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). Required for running any tests that use VCS (OAuth clients, policy sets, etc). -1. `GITHUB_POLICY_SET_IDENTIFIER` - GitHub policy set repository identifier in the format `username/repository`. Required for running policy set tests. -1. `GITHUB_REGISTRY_MODULE_IDENTIFIER` - GitHub registry module repository identifier in the format `username/repository`. Required for running registry module tests. -1. `ENABLE_TFE` - Some tests are only applicable to Terraform Enterprise or Terraform Cloud. By setting `ENABLE_TFE=1` you will enable enterprise only tests and disable cloud only tests. In CI `ENABLE_TFE` is not set so if you are writing enterprise only features you should manually test with `ENABLE_TFE=1` against a Terraform Enterprise instance. -1. `SKIP_PAID` - Some tests depend on paid only features. By setting `SKIP_PAID=1`, you will skip tests that access paid features. - -You can set your environment variables up however you prefer. The following are instructions for setting up environment variables using [envchain](https://github.com/sorah/envchain). - 1. Make sure you have envchain installed. [Instructions for this can be found in the envchain README](https://github.com/sorah/envchain#installation). - 1. Pick a namespace for storing your environment variables. I suggest `go-tfe` or something similar. - 1. For each environment variable you need to set, run the following command: - ```sh - envchain --set YOUR_NAMESPACE_HERE ENVIRONMENT_VARIABLE_HERE - ``` - **OR** - - Set all of the environment variables at once with the following command: - ```sh - envchain --set YOUR_NAMESPACE_HERE TFE_ADDRESS TFE_TOKEN GITHUB_TOKEN GITHUB_POLICY_SET_IDENTIFIER - ``` - -### 3. Make sure run queue settings are correct - -In order for the tests relating to queuing and capacity to pass, FRQ (fair run queuing) should be -enabled with a limit of 2 concurrent runs per organization on the Terraform Cloud or Terraform Enterprise instance you are using for testing. - -### 4. Run the tests - -#### Running all the tests -As running the all of the tests takes about ~20 minutes, make sure to add a timeout to your -command (as the default timeout is 10m). - -##### With envchain: -```sh -$ envchain YOUR_NAMESPACE_HERE go test ./... -timeout=30m -tags=integration -``` - -##### Without envchain: -```sh -$ TFE_TOKEN=xyz TFE_ADDRESS=xyz ENABLE_TFE=1 go test ./... -timeout=30m -tags=integration -``` - -#### Running specific tests - -The commands below use notification configurations as an example. - -##### With envchain: -```sh -$ envchain YOUR_NAMESPACE_HERE go test -run TestNotificationConfiguration -v ./... -tags=integration -``` - -##### Without envchain: -```sh -$ TFE_TOKEN=xyz TFE_ADDRESS=xyz ENABLE_TFE=1 go test -run TestNotificationConfiguration -v ./... -tags=integration -``` - diff --git a/vendor/github.com/hashicorp/go-tfe/admin_organization.go b/vendor/github.com/hashicorp/go-tfe/admin_organization.go index 3f83556c..c4301374 100644 --- a/vendor/github.com/hashicorp/go-tfe/admin_organization.go +++ b/vendor/github.com/hashicorp/go-tfe/admin_organization.go @@ -15,7 +15,7 @@ var _ AdminOrganizations = (*adminOrganizations)(nil) // TFE API docs: https://www.terraform.io/docs/cloud/api/admin/organizations.html type AdminOrganizations interface { // List all the organizations visible to the current user. - List(ctx context.Context, options AdminOrganizationListOptions) (*AdminOrganizationList, error) + List(ctx context.Context, options *AdminOrganizationListOptions) (*AdminOrganizationList, error) // Read attributes of an existing organization via admin API. Read(ctx context.Context, organization string) (*AdminOrganization, error) @@ -25,6 +25,12 @@ type AdminOrganizations interface { // Delete an organization by its name via admin API Delete(ctx context.Context, organization string) error + + // ListModuleConsumers lists specific organizations in the Terraform Enterprise installation that have permission to use an organization's modules. + ListModuleConsumers(ctx context.Context, organization string, options *AdminOrganizationListModuleConsumersOptions) (*AdminOrganizationList, error) + + // UpdateModuleConsumers specifies a list of organizations that can use modules from the sharing organization's private registry. Setting a list of module consumers will turn off global module sharing for an organization. + UpdateModuleConsumers(ctx context.Context, organization string, consumerOrganizations []string) error } // adminOrganizations implements AdminOrganizations. @@ -37,6 +43,7 @@ type AdminOrganization struct { Name string `jsonapi:"primary,organizations"` AccessBetaTools bool `jsonapi:"attr,access-beta-tools"` ExternalID string `jsonapi:"attr,external-id"` + GlobalModuleSharing *bool `jsonapi:"attr,global-module-sharing"` IsDisabled bool `jsonapi:"attr,is-disabled"` NotificationEmail string `jsonapi:"attr,notification-email"` SsoEnabled bool `jsonapi:"attr,sso-enabled"` @@ -52,6 +59,7 @@ type AdminOrganization struct { // https://www.terraform.io/docs/cloud/api/admin/organizations.html#request-body type AdminOrganizationUpdateOptions struct { AccessBetaTools *bool `jsonapi:"attr,access-beta-tools,omitempty"` + GlobalModuleSharing *bool `jsonapi:"attr,global-module-sharing,omitempty"` IsDisabled *bool `jsonapi:"attr,is-disabled,omitempty"` TerraformBuildWorkerApplyTimeout *string `jsonapi:"attr,terraform-build-worker-apply-timeout,omitempty"` TerraformBuildWorkerPlanTimeout *string `jsonapi:"attr,terraform-build-worker-plan-timeout,omitempty"` @@ -64,23 +72,40 @@ type AdminOrganizationList struct { Items []*AdminOrganization } +// AdminOrgIncludeOpt represents the available options for include query params. +// https://www.terraform.io/docs/cloud/api/admin/organizations.html#available-related-resources +type AdminOrgIncludeOpt string + +const AdminOrgOwners AdminOrgIncludeOpt = "owners" + // AdminOrganizationListOptions represents the options for listing organizations via Admin API. type AdminOrganizationListOptions struct { ListOptions - // A query string used to filter organizations. + // Optional: A query string used to filter organizations. // Any organizations with a name or notification email partially matching this value will be returned. - Query *string `url:"q,omitempty"` - - // A list of relations to include. See available resources + Query string `url:"q,omitempty"` + // Optional: A list of relations to include. See available resources // https://www.terraform.io/docs/cloud/api/admin/organizations.html#available-related-resources - Include *string `url:"include"` + Include []AdminOrgIncludeOpt `url:"include,omitempty"` +} + +// AdminOrganizationListModuleConsumersOptions represents the options for listing organization module consumers through the Admin API +type AdminOrganizationListModuleConsumersOptions struct { + ListOptions +} + +type AdminOrganizationID struct { + ID string `jsonapi:"primary,organizations"` } // List all the organizations visible to the current user. -func (s *adminOrganizations) List(ctx context.Context, options AdminOrganizationListOptions) (*AdminOrganizationList, error) { - url := "admin/organizations" - req, err := s.client.newRequest("GET", url, &options) +func (s *adminOrganizations) List(ctx context.Context, options *AdminOrganizationListOptions) (*AdminOrganizationList, error) { + if err := options.valid(); err != nil { + return nil, err + } + u := "admin/organizations" + req, err := s.client.newRequest("GET", u, options) if err != nil { return nil, err } @@ -94,6 +119,29 @@ func (s *adminOrganizations) List(ctx context.Context, options AdminOrganization return orgl, nil } +// ListModuleConsumers lists specific organizations in the Terraform Enterprise installation that have permission to use an organization's modules. +func (s *adminOrganizations) ListModuleConsumers(ctx context.Context, organization string, options *AdminOrganizationListModuleConsumersOptions) (*AdminOrganizationList, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg + } + + u := fmt.Sprintf("admin/organizations/%s/relationships/module-consumers", url.QueryEscape(organization)) + + req, err := s.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + orgl := &AdminOrganizationList{} + err = s.client.do(ctx, req, orgl) + if err != nil { + return nil, err + } + + return orgl, nil +} + +// Read an organization by its name. func (s *adminOrganizations) Read(ctx context.Context, organization string) (*AdminOrganization, error) { if !validStringID(&organization) { return nil, ErrInvalidOrg @@ -114,6 +162,7 @@ func (s *adminOrganizations) Read(ctx context.Context, organization string) (*Ad return org, nil } +// Update an organization by its name. func (s *adminOrganizations) Update(ctx context.Context, organization string, options AdminOrganizationUpdateOptions) (*AdminOrganization, error) { if !validStringID(&organization) { return nil, ErrInvalidOrg @@ -134,6 +183,35 @@ func (s *adminOrganizations) Update(ctx context.Context, organization string, op return org, nil } +// UpdateModuleConsumers updates an organization to specify a list of organizations that can use modules from the sharing organization's private registry. +func (s *adminOrganizations) UpdateModuleConsumers(ctx context.Context, organization string, consumerOrganizationIDs []string) error { + if !validStringID(&organization) { + return ErrInvalidOrg + } + + u := fmt.Sprintf("admin/organizations/%s/relationships/module-consumers", url.QueryEscape(organization)) + + var organizations []*AdminOrganizationID + for _, id := range consumerOrganizationIDs { + if !validStringID(&id) { + return ErrInvalidOrg + } + organizations = append(organizations, &AdminOrganizationID{ID: id}) + } + + req, err := s.client.newRequest("PATCH", u, organizations) + if err != nil { + return err + } + + err = s.client.do(ctx, req, nil) + if err != nil { + return err + } + + return nil +} + // Delete an organization by its name. func (s *adminOrganizations) Delete(ctx context.Context, organization string) error { if !validStringID(&organization) { @@ -148,3 +226,28 @@ func (s *adminOrganizations) Delete(ctx context.Context, organization string) er return s.client.do(ctx, req, nil) } + +func (o *AdminOrganizationListOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateAdminOrgIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func validateAdminOrgIncludeParams(params []AdminOrgIncludeOpt) error { + for _, p := range params { + switch p { + case AdminOrgOwners: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/admin_run.go b/vendor/github.com/hashicorp/go-tfe/admin_run.go index 4dd4576c..b0467aa6 100644 --- a/vendor/github.com/hashicorp/go-tfe/admin_run.go +++ b/vendor/github.com/hashicorp/go-tfe/admin_run.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" "strings" @@ -19,17 +18,13 @@ var _ AdminRuns = (*adminRuns)(nil) // TFE API docs: https://www.terraform.io/docs/cloud/api/admin/runs.html type AdminRuns interface { // List all the runs of the given installation. - List(ctx context.Context, options AdminRunsListOptions) (*AdminRunsList, error) + List(ctx context.Context, options *AdminRunsListOptions) (*AdminRunsList, error) // Force-cancel a run by its ID. ForceCancel(ctx context.Context, runID string, options AdminRunForceCancelOptions) error } -// adminRuns implements the AdminRuns interface. -type adminRuns struct { - client *Client -} - +// AdminRun represents AdminRuns interface. type AdminRun struct { ID string `jsonapi:"primary,runs"` CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` @@ -48,25 +43,42 @@ type AdminRunsList struct { Items []*AdminRun } +// AdminRunIncludeOpt represents the available options for include query params. +// https://www.terraform.io/cloud-docs/api-docs/admin/runs#available-related-resources +type AdminRunIncludeOpt string + +const ( + AdminRunWorkspace AdminRunIncludeOpt = "workspace" + AdminRunWorkspaceOrg AdminRunIncludeOpt = "workspace.organization" + AdminRunWorkspaceOrgOwners AdminRunIncludeOpt = "workspace.organization.owners" +) + // AdminRunsListOptions represents the options for listing runs. // https://www.terraform.io/docs/cloud/api/admin/runs.html#query-parameters type AdminRunsListOptions struct { ListOptions - RunStatus *string `url:"filter[status],omitempty"` - Query *string `url:"q,omitempty"` - Include *string `url:"include,omitempty"` + RunStatus string `url:"filter[status],omitempty"` + Query string `url:"q,omitempty"` + // Optional: A list of relations to include. See available resources + // https://www.terraform.io/cloud-docs/api-docs/admin/runs#available-related-resources + Include []AdminRunIncludeOpt `url:"include,omitempty"` +} + +// adminRuns implements the AdminRuns interface. +type adminRuns struct { + client *Client } // List all the runs of the terraform enterprise installation. // https://www.terraform.io/docs/cloud/api/admin/runs.html#list-all-runs -func (s *adminRuns) List(ctx context.Context, options AdminRunsListOptions) (*AdminRunsList, error) { +func (s *adminRuns) List(ctx context.Context, options *AdminRunsListOptions) (*AdminRunsList, error) { if err := options.valid(); err != nil { return nil, err } u := "admin/runs" - req, err := s.client.newRequest("GET", u, &options) + req, err := s.client.newRequest("GET", u, options) if err != nil { return nil, err } @@ -91,7 +103,7 @@ type AdminRunForceCancelOptions struct { // https://www.terraform.io/docs/cloud/api/admin/runs.html#force-a-run-into-the-quot-cancelled-quot-state func (s *adminRuns) ForceCancel(ctx context.Context, runID string, options AdminRunForceCancelOptions) error { if !validStringID(&runID) { - return errors.New("invalid value for run ID") + return ErrInvalidRunID } u := fmt.Sprintf("admin/runs/%s/actions/force-cancel", url.QueryEscape(runID)) @@ -103,36 +115,68 @@ func (s *adminRuns) ForceCancel(ctx context.Context, runID string, options Admin return s.client.do(ctx, req, nil) } -func (o AdminRunsListOptions) valid() error { - if validString(o.RunStatus) { - validRunStatus := map[string]int{ - string(RunApplied): 1, - string(RunApplyQueued): 1, - string(RunApplying): 1, - string(RunCanceled): 1, - string(RunConfirmed): 1, - string(RunCostEstimated): 1, - string(RunCostEstimating): 1, - string(RunDiscarded): 1, - string(RunErrored): 1, - string(RunPending): 1, - string(RunPlanQueued): 1, - string(RunPlanned): 1, - string(RunPlannedAndFinished): 1, - string(RunPlanning): 1, - string(RunPolicyChecked): 1, - string(RunPolicyChecking): 1, - string(RunPolicyOverride): 1, - string(RunPolicySoftFailed): 1, - } - runStatus := strings.Split(*o.RunStatus, ",") +func (o *AdminRunsListOptions) valid() error { + if o == nil { // nothing to validate + return nil + } + if err := validateAdminRunFilterParams(o.RunStatus); err != nil { + return err + } + + if err := validateAdminRunIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func validateAdminRunFilterParams(runStatus string) error { + // For the platform, an invalid filter value is a semantically understood query that returns an empty set, no error, no warning. But for go-tfe, an invalid value is good enough reason to error prior to a network call to the platform: + if validString(&runStatus) { + sanitizedRunstatus := strings.TrimSpace(runStatus) + runStatuses := strings.Split(sanitizedRunstatus, ",") // iterate over our statuses, and ensure it is valid. - for _, status := range runStatus { - if _, present := validRunStatus[status]; !present { - return fmt.Errorf("invalid value %s for run status", status) + for _, status := range runStatuses { + switch status { + case string(RunApplied), + string(RunApplyQueued), + string(RunApplying), + string(RunCanceled), + string(RunConfirmed), + string(RunCostEstimate), + string(RunCostEstimating), + string(RunDiscarded), + string(RunErrored), + string(RunPending), + string(RunPlanQueued), + string(RunPlanned), + string(RunPlannedAndFinished), + string(RunPlanning), + string(RunPolicyChecked), + string(RunPolicyChecking), + string(RunPolicyOverride), + string(RunPolicySoftFailed), + "": + // do nothing + default: + return fmt.Errorf(`invalid value "%s" for run status`, status) } } } + + return nil +} + +func validateAdminRunIncludeParams(params []AdminRunIncludeOpt) error { + for _, p := range params { + switch p { + case AdminRunWorkspace, AdminRunWorkspaceOrg, AdminRunWorkspaceOrgOwners: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + return nil } diff --git a/vendor/github.com/hashicorp/go-tfe/admin_setting.go b/vendor/github.com/hashicorp/go-tfe/admin_setting.go index ccf0551f..a3dfafb1 100644 --- a/vendor/github.com/hashicorp/go-tfe/admin_setting.go +++ b/vendor/github.com/hashicorp/go-tfe/admin_setting.go @@ -4,7 +4,6 @@ package tfe // Note that admin settings are only available in Terraform Enterprise. // // TFE API docs: https://www.terraform.io/docs/cloud/api/admin/settings.html -// AdminSettings todo type AdminSettings struct { General GeneralSettings SAML SAMLSettings diff --git a/vendor/github.com/hashicorp/go-tfe/admin_setting_cost_estimation.go b/vendor/github.com/hashicorp/go-tfe/admin_setting_cost_estimation.go index 75f27c27..e291a25f 100644 --- a/vendor/github.com/hashicorp/go-tfe/admin_setting_cost_estimation.go +++ b/vendor/github.com/hashicorp/go-tfe/admin_setting_cost_estimation.go @@ -7,7 +7,8 @@ import ( // Compile-time proof of interface implementation. var _ CostEstimationSettings = (*adminCostEstimationSettings)(nil) -// CostEstimationSettings describes all the cost estimation admin settings. +// CostEstimationSettings describes all the cost estimation admin settings for the Admin Setting API. +// https://www.terraform.io/cloud-docs/api-docs/admin/settings type CostEstimationSettings interface { // Read returns the cost estimation settings. Read(ctx context.Context) (*AdminCostEstimationSetting, error) @@ -37,6 +38,20 @@ type AdminCostEstimationSetting struct { AzureTenantID string `jsonapi:"attr,azure-tenant-id"` } +// AdminCostEstimationSettingOptions represents the admin options for updating +// the cost estimation settings. +// https://www.terraform.io/docs/cloud/api/admin/settings.html#request-body-1 +type AdminCostEstimationSettingOptions struct { + Enabled *bool `jsonapi:"attr,enabled,omitempty"` + AWSAccessKeyID *string `jsonapi:"attr,aws-access-key-id,omitempty"` + AWSAccessKey *string `jsonapi:"attr,aws-secret-key,omitempty"` + GCPCredentials *string `jsonapi:"attr,gcp-credentials,omitempty"` + AzureClientID *string `jsonapi:"attr,azure-client-id,omitempty"` + AzureClientSecret *string `jsonapi:"attr,azure-client-secret,omitempty"` + AzureSubscriptionID *string `jsonapi:"attr,azure-subscription-id,omitempty"` + AzureTenantID *string `jsonapi:"attr,azure-tenant-id,omitempty"` +} + // Read returns the cost estimation settings. func (a *adminCostEstimationSettings) Read(ctx context.Context) (*AdminCostEstimationSetting, error) { req, err := a.client.newRequest("GET", "admin/cost-estimation-settings", nil) @@ -53,20 +68,6 @@ func (a *adminCostEstimationSettings) Read(ctx context.Context) (*AdminCostEstim return ace, nil } -// AdminCostEstimationSettingOptions represents the admin options for updating -// the cost estimation settings. -// https://www.terraform.io/docs/cloud/api/admin/settings.html#request-body-1 -type AdminCostEstimationSettingOptions struct { - Enabled *bool `jsonapi:"attr,enabled,omitempty"` - AWSAccessKeyID *string `jsonapi:"attr,aws-access-key-id,omitempty"` - AWSAccessKey *string `jsonapi:"attr,aws-secret-key,omitempty"` - GCPCredentials *string `jsonapi:"attr,gcp-credentials,omitempty"` - AzureClientID *string `jsonapi:"attr,azure-client-id,omitempty"` - AzureClientSecret *string `jsonapi:"attr,azure-client-secret,omitempty"` - AzureSubscriptionID *string `jsonapi:"attr,azure-subscription-id,omitempty"` - AzureTenantID *string `jsonapi:"attr,azure-tenant-id,omitempty"` -} - // Update updates the cost-estimation settings. func (a *adminCostEstimationSettings) Update(ctx context.Context, options AdminCostEstimationSettingOptions) (*AdminCostEstimationSetting, error) { req, err := a.client.newRequest("PATCH", "admin/cost-estimation-settings", &options) diff --git a/vendor/github.com/hashicorp/go-tfe/admin_setting_customization.go b/vendor/github.com/hashicorp/go-tfe/admin_setting_customization.go index d467faaa..d429a5e9 100644 --- a/vendor/github.com/hashicorp/go-tfe/admin_setting_customization.go +++ b/vendor/github.com/hashicorp/go-tfe/admin_setting_customization.go @@ -20,7 +20,8 @@ type adminCustomizationSettings struct { client *Client } -// AdminCustomizationSetting represents the Customization settings in Terraform Enterprise. +// AdminCustomizationSetting represents the Customization settings in Terraform Enterprise for the Admin Settings API. +// https://www.terraform.io/cloud-docs/api-docs/admin/settings type AdminCustomizationSetting struct { ID string `jsonapi:"primary,customization-settings"` SupportEmail string `jsonapi:"attr,support-email-address"` diff --git a/vendor/github.com/hashicorp/go-tfe/admin_setting_general.go b/vendor/github.com/hashicorp/go-tfe/admin_setting_general.go index b0c5b85f..31124fd6 100644 --- a/vendor/github.com/hashicorp/go-tfe/admin_setting_general.go +++ b/vendor/github.com/hashicorp/go-tfe/admin_setting_general.go @@ -7,7 +7,8 @@ import ( // Compile-time proof of interface implementation. var _ GeneralSettings = (*adminGeneralSettings)(nil) -// GeneralSettings describes the general admin settings. +// GeneralSettings describes the general admin settings for the Admin Setting API. +// https://www.terraform.io/cloud-docs/api-docs/admin/settings type GeneralSettings interface { // Read returns the general settings Read(ctx context.Context) (*AdminGeneralSetting, error) @@ -39,6 +40,18 @@ type AdminGeneralSetting struct { DefaultRemoteStateAccess bool `jsonapi:"attr,default-remote-state-access"` } +// AdminGeneralSettingsUpdateOptions represents the admin options for updating +// general settings. +// https://www.terraform.io/docs/cloud/api/admin/settings.html#request-body +type AdminGeneralSettingsUpdateOptions struct { + LimitUserOrgCreation *bool `jsonapi:"attr,limit-user-organization-creation,omitempty"` + APIRateLimitingEnabled *bool `jsonapi:"attr,api-rate-limiting-enabled,omitempty"` + APIRateLimit *int `jsonapi:"attr,api-rate-limit,omitempty"` + SendPassingStatusUntriggeredPlans *bool `jsonapi:"attr,send-passing-statuses-for-untriggered-speculative-plans,omitempty"` + AllowSpeculativePlansOnPR *bool `jsonapi:"attr,allow-speculative-plans-on-pull-requests-from-forks,omitempty"` + DefaultRemoteStateAccess *bool `jsonapi:"attr,default-remote-state-access,omitempty"` +} + // Read returns the general settings. func (a *adminGeneralSettings) Read(ctx context.Context) (*AdminGeneralSetting, error) { req, err := a.client.newRequest("GET", "admin/general-settings", nil) @@ -55,18 +68,6 @@ func (a *adminGeneralSettings) Read(ctx context.Context) (*AdminGeneralSetting, return ags, nil } -// AdminGeneralSettingsUpdateOptions represents the admin options for updating -// general settings. -// https://www.terraform.io/docs/cloud/api/admin/settings.html#request-body -type AdminGeneralSettingsUpdateOptions struct { - LimitUserOrgCreation *bool `jsonapi:"attr,limit-user-organization-creation,omitempty"` - APIRateLimitingEnabled *bool `jsonapi:"attr,api-rate-limiting-enabled,omitempty"` - APIRateLimit *int `jsonapi:"attr,api-rate-limit,omitempty"` - SendPassingStatusUntriggeredPlans *bool `jsonapi:"attr,send-passing-statuses-for-untriggered-speculative-plans,omitempty"` - AllowSpeculativePlansOnPR *bool `jsonapi:"attr,allow-speculative-plans-on-pull-requests-from-forks,omitempty"` - DefaultRemoteStateAccess *bool `jsonapi:"attr,default-remote-state-access,omitempty"` -} - // Update updates the general settings. func (a *adminGeneralSettings) Update(ctx context.Context, options AdminGeneralSettingsUpdateOptions) (*AdminGeneralSetting, error) { req, err := a.client.newRequest("PATCH", "admin/general-settings", &options) diff --git a/vendor/github.com/hashicorp/go-tfe/admin_setting_saml.go b/vendor/github.com/hashicorp/go-tfe/admin_setting_saml.go index 3de700fa..8ea59aed 100644 --- a/vendor/github.com/hashicorp/go-tfe/admin_setting_saml.go +++ b/vendor/github.com/hashicorp/go-tfe/admin_setting_saml.go @@ -7,7 +7,8 @@ import ( // Compile-time proof of interface implementation. var _ SAMLSettings = (*adminSAMLSettings)(nil) -// SAMLSettings describes all the SAML admin settings. +// SAMLSettings describes all the SAML admin settings for the Admin Setting API. +// https://www.terraform.io/cloud-docs/api-docs/admin/settings type SAMLSettings interface { // Read returns the SAML settings. Read(ctx context.Context) (*AdminSAMLSetting, error) diff --git a/vendor/github.com/hashicorp/go-tfe/admin_setting_smtp.go b/vendor/github.com/hashicorp/go-tfe/admin_setting_smtp.go index 4cfc5be5..4cb6046e 100644 --- a/vendor/github.com/hashicorp/go-tfe/admin_setting_smtp.go +++ b/vendor/github.com/hashicorp/go-tfe/admin_setting_smtp.go @@ -7,7 +7,7 @@ import ( // Compile-time proof of interface implementation. var _ SMTPSettings = (*adminSMTPSettings)(nil) -// SMTPSettings describes all the SMTP admin settings. +// SMTPSettings describes all the SMTP admin settings for the Admin Setting API https://www.terraform.io/cloud-docs/api-docs/admin/settings. type SMTPSettings interface { // Read returns the SMTP settings. Read(ctx context.Context) (*AdminSMTPSetting, error) @@ -30,12 +30,6 @@ const ( SMTPAuthLogin SMTPAuthType = "login" ) -var validSMTPAuthType = map[SMTPAuthType]struct{}{ - SMTPAuthNone: struct{}{}, - SMTPAuthPlain: struct{}{}, - SMTPAuthLogin: struct{}{}, -} - // AdminSMTPSetting represents a the SMTP settings in Terraform Enterprise. type AdminSMTPSetting struct { ID string `jsonapi:"primary,smtp-settings"` @@ -77,11 +71,12 @@ type AdminSMTPSettingsUpdateOptions struct { TestEmailAddress *string `jsonapi:"attr,test-email-address,omitempty"` } -// Updat updates the SMTP settings. +// Update updates the SMTP settings. func (a *adminSMTPSettings) Update(ctx context.Context, options AdminSMTPSettingsUpdateOptions) (*AdminSMTPSetting, error) { - if !options.valid() { - return nil, ErrInvalidSMTPAuth + if err := options.valid(); err != nil { + return nil, err } + req, err := a.client.newRequest("PATCH", "admin/smtp-settings", &options) if err != nil { return nil, err @@ -96,7 +91,23 @@ func (a *adminSMTPSettings) Update(ctx context.Context, options AdminSMTPSetting return smtp, nil } -func (o AdminSMTPSettingsUpdateOptions) valid() bool { - _, isValidType := validSMTPAuthType[*o.Auth] - return isValidType +func (o AdminSMTPSettingsUpdateOptions) valid() error { + if validString((*string)(o.Auth)) { + if err := validateAdminSettingSMTPAuth(*o.Auth); err != nil { + return err + } + } + + return nil +} + +func validateAdminSettingSMTPAuth(authVal SMTPAuthType) error { + switch authVal { + case SMTPAuthNone, SMTPAuthPlain, SMTPAuthLogin: + // do nothing + default: + return ErrInvalidSMTPAuth + } + + return nil } diff --git a/vendor/github.com/hashicorp/go-tfe/admin_setting_twilio.go b/vendor/github.com/hashicorp/go-tfe/admin_setting_twilio.go index bb962cfe..6913f204 100644 --- a/vendor/github.com/hashicorp/go-tfe/admin_setting_twilio.go +++ b/vendor/github.com/hashicorp/go-tfe/admin_setting_twilio.go @@ -7,7 +7,8 @@ import ( // Compile-time proof of interface implementation. var _ TwilioSettings = (*adminTwilioSettings)(nil) -// TwilioSettings describes all the Twilio admin settings. +// TwilioSettings describes all the Twilio admin settings for the Admin Setting API. +// https://www.terraform.io/cloud-docs/api-docs/admin/settings. type TwilioSettings interface { // Read returns the Twilio settings. Read(ctx context.Context) (*AdminTwilioSetting, error) @@ -57,6 +58,12 @@ type AdminTwilioSettingsUpdateOptions struct { FromNumber *string `jsonapi:"attr,from-number,omitempty"` } +// AdminTwilioSettingsVerifyOptions represents the test number to verify Twilio. +// https://www.terraform.io/docs/cloud/api/admin/settings.html#verify-twilio-settings +type AdminTwilioSettingsVerifyOptions struct { + TestNumber *string `jsonapi:"attr,test-number"` // Required +} + // Update updates the Twilio settings. func (a *adminTwilioSettings) Update(ctx context.Context, options AdminTwilioSettingsUpdateOptions) (*AdminTwilioSetting, error) { req, err := a.client.newRequest("PATCH", "admin/twilio-settings", &options) @@ -73,14 +80,11 @@ func (a *adminTwilioSettings) Update(ctx context.Context, options AdminTwilioSet return twilio, nil } -// AdminTwilioSettingsVerifyOptions represents the test number to verify Twilio. -// https://www.terraform.io/docs/cloud/api/admin/settings.html#verify-twilio-settings -type AdminTwilioSettingsVerifyOptions struct { - TestNumber *string `jsonapi:"attr,test-number"` -} - // Verify verifies Twilio settings. func (a *adminTwilioSettings) Verify(ctx context.Context, options AdminTwilioSettingsVerifyOptions) error { + if err := options.valid(); err != nil { + return err + } req, err := a.client.newRequest("PATCH", "admin/twilio-settings/verify", &options) if err != nil { return err @@ -88,3 +92,11 @@ func (a *adminTwilioSettings) Verify(ctx context.Context, options AdminTwilioSet return a.client.do(ctx, req, nil) } + +func (o AdminTwilioSettingsVerifyOptions) valid() error { + if !validString(o.TestNumber) { + return ErrRequiredTestNumber + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/admin_terraform_version.go b/vendor/github.com/hashicorp/go-tfe/admin_terraform_version.go index 6c0be515..10eb9e12 100644 --- a/vendor/github.com/hashicorp/go-tfe/admin_terraform_version.go +++ b/vendor/github.com/hashicorp/go-tfe/admin_terraform_version.go @@ -17,7 +17,7 @@ var _ AdminTerraformVersions = (*adminTerraformVersions)(nil) // TFE API docs: https://www.terraform.io/docs/cloud/api/admin/terraform-versions.html type AdminTerraformVersions interface { // List all the terraform versions. - List(ctx context.Context, options AdminTerraformVersionsListOptions) (*AdminTerraformVersionsList, error) + List(ctx context.Context, options *AdminTerraformVersionsListOptions) (*AdminTerraformVersionsList, error) // Read a terraform version by its ID. Read(ctx context.Context, id string) (*AdminTerraformVersion, error) @@ -39,21 +39,57 @@ type adminTerraformVersions struct { // AdminTerraformVersion represents a Terraform Version type AdminTerraformVersion struct { - ID string `jsonapi:"primary,terraform-versions"` - Version string `jsonapi:"attr,version"` - URL string `jsonapi:"attr,url"` - Sha string `jsonapi:"attr,sha"` - Official bool `jsonapi:"attr,official"` - Enabled bool `jsonapi:"attr,enabled"` - Beta bool `jsonapi:"attr,beta"` - Usage int `jsonapi:"attr,usage"` - CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` + ID string `jsonapi:"primary,terraform-versions"` + Version string `jsonapi:"attr,version"` + URL string `jsonapi:"attr,url"` + Sha string `jsonapi:"attr,sha"` + Deprecated bool `jsonapi:"attr,deprecated"` + DeprecatedReason *string `jsonapi:"attr,deprecated-reason,omitempty"` + Official bool `jsonapi:"attr,official"` + Enabled bool `jsonapi:"attr,enabled"` + Beta bool `jsonapi:"attr,beta"` + Usage int `jsonapi:"attr,usage"` + CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` } // AdminTerraformVersionsListOptions represents the options for listing // terraform versions. type AdminTerraformVersionsListOptions struct { ListOptions + + // Optional: A query string to find an exact version + Filter string `url:"filter[version],omitempty"` + + // Optional: A search query string to find all versions that match version substring + Search string `url:"search[version],omitempty"` +} + +// AdminTerraformVersionCreateOptions for creating a terraform version. +// https://www.terraform.io/docs/cloud/api/admin/terraform-versions.html#request-body +type AdminTerraformVersionCreateOptions struct { + Type string `jsonapi:"primary,terraform-versions"` + Version *string `jsonapi:"attr,version"` // Required + URL *string `jsonapi:"attr,url"` // Required + Sha *string `jsonapi:"attr,sha"` // Required + Official *bool `jsonapi:"attr,official,omitempty"` + Deprecated *bool `jsonapi:"attr,deprecated,omitempty"` + DeprecatedReason *string `jsonapi:"attr,deprecated-reason,omitempty"` + Enabled *bool `jsonapi:"attr,enabled,omitempty"` + Beta *bool `jsonapi:"attr,beta,omitempty"` +} + +// AdminTerraformVersionUpdateOptions for updating terraform version. +// https://www.terraform.io/docs/cloud/api/admin/terraform-versions.html#request-body +type AdminTerraformVersionUpdateOptions struct { + Type string `jsonapi:"primary,terraform-versions"` + Version *string `jsonapi:"attr,version,omitempty"` + URL *string `jsonapi:"attr,url,omitempty"` + Sha *string `jsonapi:"attr,sha,omitempty"` + Official *bool `jsonapi:"attr,official,omitempty"` + Deprecated *bool `jsonapi:"attr,deprecated,omitempty"` + DeprecatedReason *string `jsonapi:"attr,deprecated-reason,omitempty"` + Enabled *bool `jsonapi:"attr,enabled,omitempty"` + Beta *bool `jsonapi:"attr,beta,omitempty"` } // AdminTerraformVersionsList represents a list of terraform versions. @@ -63,8 +99,8 @@ type AdminTerraformVersionsList struct { } // List all the terraform versions. -func (a *adminTerraformVersions) List(ctx context.Context, options AdminTerraformVersionsListOptions) (*AdminTerraformVersionsList, error) { - req, err := a.client.newRequest("GET", "admin/terraform-versions", &options) +func (a *adminTerraformVersions) List(ctx context.Context, options *AdminTerraformVersionsListOptions) (*AdminTerraformVersionsList, error) { + req, err := a.client.newRequest("GET", "admin/terraform-versions", options) if err != nil { return nil, err } @@ -99,20 +135,11 @@ func (a *adminTerraformVersions) Read(ctx context.Context, id string) (*AdminTer return tfv, nil } -// AdminTerraformVersionCreateOptions for creating a terraform version. -// https://www.terraform.io/docs/cloud/api/admin/terraform-versions.html#request-body -type AdminTerraformVersionCreateOptions struct { - Type string `jsonapi:"primary,terraform-versions"` - Version *string `jsonapi:"attr,version"` - URL *string `jsonapi:"attr,url"` - Sha *string `jsonapi:"attr,sha"` - Official *bool `jsonapi:"attr,official,omitempty"` - Enabled *bool `jsonapi:"attr,enabled,omitempty"` - Beta *bool `jsonapi:"attr,beta,omitempty"` -} - // Create a new terraform version. func (a *adminTerraformVersions) Create(ctx context.Context, options AdminTerraformVersionCreateOptions) (*AdminTerraformVersion, error) { + if err := options.valid(); err != nil { + return nil, err + } req, err := a.client.newRequest("POST", "admin/terraform-versions", &options) if err != nil { return nil, err @@ -127,18 +154,6 @@ func (a *adminTerraformVersions) Create(ctx context.Context, options AdminTerraf return tfv, nil } -// AdminTerraformVersionUpdateOptions for updating terraform version. -// https://www.terraform.io/docs/cloud/api/admin/terraform-versions.html#request-body -type AdminTerraformVersionUpdateOptions struct { - Type string `jsonapi:"primary,terraform-versions"` - Version *string `jsonapi:"attr,version,omitempty"` - URL *string `jsonapi:"attr,url,omitempty"` - Sha *string `jsonapi:"attr,sha,omitempty"` - Official *bool `jsonapi:"attr,official,omitempty"` - Enabled *bool `jsonapi:"attr,enabled,omitempty"` - Beta *bool `jsonapi:"attr,beta,omitempty"` -} - // Update an existing terraform version. func (a *adminTerraformVersions) Update(ctx context.Context, id string, options AdminTerraformVersionUpdateOptions) (*AdminTerraformVersion, error) { if !validStringID(&id) { @@ -174,3 +189,20 @@ func (a *adminTerraformVersions) Delete(ctx context.Context, id string) error { return a.client.do(ctx, req, nil) } + +func (o AdminTerraformVersionCreateOptions) valid() error { + if (o == AdminTerraformVersionCreateOptions{}) { + return ErrRequiredTFVerCreateOps + } + if !validString(o.Version) { + return ErrRequiredVersion + } + if !validString(o.URL) { + return ErrRequiredURL + } + if !validString(o.Sha) { + return ErrRequiredSha + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/admin_user.go b/vendor/github.com/hashicorp/go-tfe/admin_user.go index 75009ce6..80fc6ab7 100644 --- a/vendor/github.com/hashicorp/go-tfe/admin_user.go +++ b/vendor/github.com/hashicorp/go-tfe/admin_user.go @@ -16,7 +16,7 @@ var _ AdminUsers = (*adminUsers)(nil) // TFE API docs: https://www.terraform.io/docs/cloud/api/admin/users.html type AdminUsers interface { // List all the users of the given installation. - List(ctx context.Context, options AdminUserListOptions) (*AdminUserList, error) + List(ctx context.Context, options *AdminUserListOptions) (*AdminUserList, error) // Delete a user by its ID. Delete(ctx context.Context, userID string) error @@ -27,10 +27,10 @@ type AdminUsers interface { // Unsuspend a user by its ID. Unsuspend(ctx context.Context, userID string) (*AdminUser, error) - // GrantAdmin grants admin privilages to a user by its ID. + // GrantAdmin grants admin privileges to a user by its ID. GrantAdmin(ctx context.Context, userID string) (*AdminUser, error) - // RevokeAdmin revokees admin privilages to a user by its ID. + // RevokeAdmin revokees admin privileges to a user by its ID. RevokeAdmin(ctx context.Context, userID string) (*AdminUser, error) // Disable2FA disables a user's two-factor authentication in the situation @@ -64,29 +64,39 @@ type AdminUserList struct { Items []*AdminUser } +// AdminUserIncludeOpt represents the available options for include query params. +// https://www.terraform.io/docs/cloud/api/admin/users.html#available-related-resources +type AdminUserIncludeOpt string + +const AdminUserOrgs AdminUserIncludeOpt = "organizations" + // AdminUserListOptions represents the options for listing users. // https://www.terraform.io/docs/cloud/api/admin/users.html#query-parameters type AdminUserListOptions struct { ListOptions - // A search query string. Users are searchable by username and email address. - Query *string `url:"q,omitempty"` + // Optional: A search query string. Users are searchable by username and email address. + Query string `url:"q,omitempty"` - // Can be "true" or "false" to show only administrators or non-administrators. - Administrators *string `url:"filter[admin]"` + // Optional: Can be "true" or "false" to show only administrators or non-administrators. + Administrators string `url:"filter[admin],omitempty"` - // Can be "true" or "false" to show only suspended users or users who are not suspended. - SuspendedUsers *string `url:"filter[suspended]"` + // Optional: Can be "true" or "false" to show only suspended users or users who are not suspended. + SuspendedUsers string `url:"filter[suspended],omitempty"` - // A list of relations to include. See available resources + // Optional: A list of relations to include. See available resources // https://www.terraform.io/docs/cloud/api/admin/users.html#available-related-resources - Include *string `url:"include"` + Include []AdminUserIncludeOpt `url:"include,omitempty"` } // List all user accounts in the Terraform Enterprise installation -func (a *adminUsers) List(ctx context.Context, options AdminUserListOptions) (*AdminUserList, error) { +func (a *adminUsers) List(ctx context.Context, options *AdminUserListOptions) (*AdminUserList, error) { + if err := options.valid(); err != nil { + return nil, err + } + u := "admin/users" - req, err := a.client.newRequest("GET", u, &options) + req, err := a.client.newRequest("GET", u, options) if err != nil { return nil, err } @@ -157,7 +167,7 @@ func (a *adminUsers) Unsuspend(ctx context.Context, userID string) (*AdminUser, return au, nil } -// GrantAdmin grants admin privilages to a user by its ID. +// GrantAdmin grants admin privileges to a user by its ID. func (a *adminUsers) GrantAdmin(ctx context.Context, userID string) (*AdminUser, error) { if !validStringID(&userID) { return nil, ErrInvalidUserValue @@ -178,7 +188,7 @@ func (a *adminUsers) GrantAdmin(ctx context.Context, userID string) (*AdminUser, return au, nil } -// RevokeAdmin revokes admin privilages to a user by its ID. +// RevokeAdmin revokes admin privileges to a user by its ID. func (a *adminUsers) RevokeAdmin(ctx context.Context, userID string) (*AdminUser, error) { if !validStringID(&userID) { return nil, ErrInvalidUserValue @@ -220,3 +230,27 @@ func (a *adminUsers) Disable2FA(ctx context.Context, userID string) (*AdminUser, return au, nil } + +func (o *AdminUserListOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateAdminUserIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func validateAdminUserIncludeParams(params []AdminUserIncludeOpt) error { + for _, p := range params { + switch p { + case AdminUserOrgs: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/admin_workspace.go b/vendor/github.com/hashicorp/go-tfe/admin_workspace.go index 1edfaf1d..1fcfc90d 100644 --- a/vendor/github.com/hashicorp/go-tfe/admin_workspace.go +++ b/vendor/github.com/hashicorp/go-tfe/admin_workspace.go @@ -15,7 +15,7 @@ var _ AdminWorkspaces = (*adminWorkspaces)(nil) // TFE API docs: https://www.terraform.io/docs/cloud/api/admin/workspaces.html type AdminWorkspaces interface { // List all the workspaces within a workspace. - List(ctx context.Context, options AdminWorkspaceListOptions) (*AdminWorkspaceList, error) + List(ctx context.Context, options *AdminWorkspaceListOptions) (*AdminWorkspaceList, error) // Read a workspace by its ID. Read(ctx context.Context, workspaceID string) (*AdminWorkspace, error) @@ -24,11 +24,12 @@ type AdminWorkspaces interface { Delete(ctx context.Context, workspaceID string) error } -// adminWorkspaces implements AdminWorkspaces. +// adminWorkspaces implements AdminWorkspaces interface. type adminWorkspaces struct { client *Client } +// AdminVCSRepo represents a VCS repository type AdminVCSRepo struct { Identifier string `jsonapi:"attr,identifier"` } @@ -45,17 +46,26 @@ type AdminWorkspace struct { CurrentRun *Run `jsonapi:"relation,current-run"` } +// AdminWorkspaceIncludeOpt represents the available options for include query params. +// https://www.terraform.io/docs/cloud/api/admin/workspaces.html#available-related-resources +type AdminWorkspaceIncludeOpt string + +const ( + AdminWorkspaceOrg AdminWorkspaceIncludeOpt = "organization" + AdminWorkspaceCurrentRun AdminWorkspaceIncludeOpt = "current_run" + AdminWorkspaceOrgOwners AdminWorkspaceIncludeOpt = "organization.owners" +) + // AdminWorkspaceListOptions represents the options for listing workspaces. type AdminWorkspaceListOptions struct { ListOptions // A query string (partial workspace name) used to filter the results. // https://www.terraform.io/docs/cloud/api/admin/workspaces.html#query-parameters - Query *string `url:"q,omitempty"` - - // A list of relations to include. See available resources + Query string `url:"q,omitempty"` + // Optional: A list of relations to include. See available resources // https://www.terraform.io/docs/cloud/api/admin/workspaces.html#available-related-resources - Include *string `url:"include"` + Include []AdminWorkspaceIncludeOpt `url:"include,omitempty"` } // AdminWorkspaceList represents a list of workspaces. @@ -64,10 +74,14 @@ type AdminWorkspaceList struct { Items []*AdminWorkspace } -// List all the workspaces within a worksapce. -func (s *adminWorkspaces) List(ctx context.Context, options AdminWorkspaceListOptions) (*AdminWorkspaceList, error) { +// List all the workspaces within a workspace. +func (s *adminWorkspaces) List(ctx context.Context, options *AdminWorkspaceListOptions) (*AdminWorkspaceList, error) { + if err := options.valid(); err != nil { + return nil, err + } + u := "admin/workspaces" - req, err := s.client.newRequest("GET", u, &options) + req, err := s.client.newRequest("GET", u, options) if err != nil { return nil, err } @@ -116,3 +130,28 @@ func (s *adminWorkspaces) Delete(ctx context.Context, workspaceID string) error return s.client.do(ctx, req, nil) } + +func (o *AdminWorkspaceListOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateAdminWSIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func validateAdminWSIncludeParams(params []AdminWorkspaceIncludeOpt) error { + for _, p := range params { + switch p { + case AdminWorkspaceOrg, AdminWorkspaceCurrentRun, AdminWorkspaceOrgOwners: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/agent_pool.go b/vendor/github.com/hashicorp/go-tfe/agent_pool.go index f1021f0b..f2913efb 100644 --- a/vendor/github.com/hashicorp/go-tfe/agent_pool.go +++ b/vendor/github.com/hashicorp/go-tfe/agent_pool.go @@ -15,7 +15,7 @@ var _ AgentPools = (*agentPools)(nil) // TFE API docs: https://www.terraform.io/docs/cloud/api/agents.html type AgentPools interface { // List all the agent pools of the given organization. - List(ctx context.Context, organization string, options AgentPoolListOptions) (*AgentPoolList, error) + List(ctx context.Context, organization string, options *AgentPoolListOptions) (*AgentPoolList, error) // Create a new agent pool with the given options. Create(ctx context.Context, organization string, options AgentPoolCreateOptions) (*AgentPool, error) @@ -23,6 +23,9 @@ type AgentPools interface { // Read a agent pool by its ID. Read(ctx context.Context, agentPoolID string) (*AgentPool, error) + // Read a agent pool by its ID with the given options. + ReadWithOptions(ctx context.Context, agentPoolID string, options *AgentPoolReadOptions) (*AgentPool, error) + // Update an agent pool by its ID. Update(ctx context.Context, agentPool string, options AgentPoolUpdateOptions) (*AgentPool, error) @@ -48,21 +51,50 @@ type AgentPool struct { // Relations Organization *Organization `jsonapi:"relation,organization"` + Workspaces []*Workspace `jsonapi:"relation,workspaces"` +} + +// A list of relations to include +// https://www.terraform.io/cloud-docs/api-docs/agents#available-related-resources +type AgentPoolIncludeOpt string + +const AgentPoolWorkspaces AgentPoolIncludeOpt = "workspaces" + +type AgentPoolReadOptions struct { + Include []AgentPoolIncludeOpt `url:"include,omitempty"` } // AgentPoolListOptions represents the options for listing agent pools. type AgentPoolListOptions struct { ListOptions + // Optional: A list of relations to include. See available resources + // https://www.terraform.io/cloud-docs/api-docs/agents#available-related-resources + Include []AgentPoolIncludeOpt `url:"include,omitempty"` +} + +// AgentPoolCreateOptions represents the options for creating an agent pool. +type AgentPoolCreateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,agent-pools"` + + // Required: A name to identify the agent pool. + Name *string `jsonapi:"attr,name"` } // List all the agent pools of the given organization. -func (s *agentPools) List(ctx context.Context, organization string, options AgentPoolListOptions) (*AgentPoolList, error) { +func (s *agentPools) List(ctx context.Context, organization string, options *AgentPoolListOptions) (*AgentPoolList, error) { if !validStringID(&organization) { return nil, ErrInvalidOrg } + if err := options.valid(); err != nil { + return nil, err + } u := fmt.Sprintf("organizations/%s/agent-pools", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) + req, err := s.client.newRequest("GET", u, options) if err != nil { return nil, err } @@ -76,28 +108,6 @@ func (s *agentPools) List(ctx context.Context, organization string, options Agen return poolList, nil } -// AgentPoolCreateOptions represents the options for creating an agent pool. -type AgentPoolCreateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,agent-pools"` - - // A name to identify the agent pool. - Name *string `jsonapi:"attr,name"` -} - -func (o AgentPoolCreateOptions) valid() error { - if !validString(o.Name) { - return ErrRequiredName - } - if !validStringID(o.Name) { - return ErrInvalidName - } - return nil -} - // Create a new agent pool with the given options. func (s *agentPools) Create(ctx context.Context, organization string, options AgentPoolCreateOptions) (*AgentPool, error) { if !validStringID(&organization) { @@ -123,11 +133,19 @@ func (s *agentPools) Create(ctx context.Context, organization string, options Ag return pool, nil } -// Read a single agent pool by its ID. +// Read a single agent pool by its ID func (s *agentPools) Read(ctx context.Context, agentpoolID string) (*AgentPool, error) { + return s.ReadWithOptions(ctx, agentpoolID, nil) +} + +// Read a single agent pool by its ID with options. +func (s *agentPools) ReadWithOptions(ctx context.Context, agentpoolID string, options *AgentPoolReadOptions) (*AgentPool, error) { if !validStringID(&agentpoolID) { return nil, ErrInvalidAgentPoolID } + if err := options.valid(); err != nil { + return nil, err + } u := fmt.Sprintf("agent-pools/%s", url.QueryEscape(agentpoolID)) req, err := s.client.newRequest("GET", u, nil) @@ -156,13 +174,6 @@ type AgentPoolUpdateOptions struct { Name *string `jsonapi:"attr,name"` } -func (o AgentPoolUpdateOptions) valid() error { - if o.Name != nil && !validStringID(o.Name) { - return ErrInvalidName - } - return nil -} - // Update an agent pool by its ID. func (s *agentPools) Update(ctx context.Context, agentPoolID string, options AgentPoolUpdateOptions) (*AgentPool, error) { if !validStringID(&agentPoolID) { @@ -202,3 +213,55 @@ func (s *agentPools) Delete(ctx context.Context, agentPoolID string) error { return s.client.do(ctx, req, nil) } + +func (o AgentPoolCreateOptions) valid() error { + if !validString(o.Name) { + return ErrRequiredName + } + if !validStringID(o.Name) { + return ErrInvalidName + } + return nil +} + +func (o AgentPoolUpdateOptions) valid() error { + if o.Name != nil && !validStringID(o.Name) { + return ErrInvalidName + } + return nil +} + +func (o *AgentPoolReadOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + if err := validateAgentPoolIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func (o *AgentPoolListOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + if err := validateAgentPoolIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func validateAgentPoolIncludeParams(params []AgentPoolIncludeOpt) error { + for _, p := range params { + switch p { + case AgentPoolWorkspaces: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/agent_token.go b/vendor/github.com/hashicorp/go-tfe/agent_token.go index 0c9c655c..aa74ae3b 100644 --- a/vendor/github.com/hashicorp/go-tfe/agent_token.go +++ b/vendor/github.com/hashicorp/go-tfe/agent_token.go @@ -19,8 +19,8 @@ type AgentTokens interface { // List all the agent tokens of the given agent pool. List(ctx context.Context, agentPoolID string) (*AgentTokenList, error) - // Generate a new agent token with the given options. - Generate(ctx context.Context, agentPoolID string, options AgentTokenGenerateOptions) (*AgentToken, error) + // Create a new agent token with the given options. + Create(ctx context.Context, agentPoolID string, options AgentTokenCreateOptions) (*AgentToken, error) // Read an agent token by its ID. Read(ctx context.Context, agentTokenID string) (*AgentToken, error) @@ -34,12 +34,6 @@ type agentTokens struct { client *Client } -// AgentTokenList represents a list of agent tokens. -type AgentTokenList struct { - *Pagination - Items []*AgentToken -} - // AgentToken represents a Terraform Cloud agent token. type AgentToken struct { ID string `jsonapi:"primary,authentication-tokens"` @@ -49,6 +43,24 @@ type AgentToken struct { Token string `jsonapi:"attr,token"` } +// AgentTokenList represents a list of agent tokens. +type AgentTokenList struct { + *Pagination + Items []*AgentToken +} + +// AgentTokenCreateOptions represents the options for creating an agent token. +type AgentTokenCreateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,agent-tokens"` + + // Description of the token + Description *string `jsonapi:"attr,description"` +} + // List all the agent tokens of the given agent pool. func (s *agentTokens) List(ctx context.Context, agentPoolID string) (*AgentTokenList, error) { if !validStringID(&agentPoolID) { @@ -70,20 +82,8 @@ func (s *agentTokens) List(ctx context.Context, agentPoolID string) (*AgentToken return tokenList, nil } -// AgentTokenGenerateOptions represents the options for creating an agent token. -type AgentTokenGenerateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,agent-tokens"` - - // Description of the token - Description *string `jsonapi:"attr,description"` -} - -// Generate a new agent token with the given options. -func (s *agentTokens) Generate(ctx context.Context, agentPoolID string, options AgentTokenGenerateOptions) (*AgentToken, error) { +// Create a new agent token with the given options. +func (s *agentTokens) Create(ctx context.Context, agentPoolID string, options AgentTokenCreateOptions) (*AgentToken, error) { if !validStringID(&agentPoolID) { return nil, ErrInvalidAgentPoolID } diff --git a/vendor/github.com/hashicorp/go-tfe/apply.go b/vendor/github.com/hashicorp/go-tfe/apply.go index 751a9ed3..b18680f0 100644 --- a/vendor/github.com/hashicorp/go-tfe/apply.go +++ b/vendor/github.com/hashicorp/go-tfe/apply.go @@ -23,7 +23,7 @@ type Applies interface { Logs(ctx context.Context, applyID string) (io.Reader, error) } -// applies implements Applys. +// applies implements Applies interface. type applies struct { client *Client } @@ -31,7 +31,7 @@ type applies struct { // ApplyStatus represents an apply state. type ApplyStatus string -//List all available apply statuses. +// List all available apply statuses. const ( ApplyCanceled ApplyStatus = "canceled" ApplyCreated ApplyStatus = "created" @@ -105,7 +105,7 @@ func (s *applies) Logs(ctx context.Context, applyID string) (io.Reader, error) { u, err := url.Parse(a.LogReadURL) if err != nil { - return nil, fmt.Errorf("invalid log URL: %v", err) + return nil, fmt.Errorf("invalid log URL: %w", err) } done := func() (bool, error) { diff --git a/vendor/github.com/hashicorp/go-tfe/audit_trail.go b/vendor/github.com/hashicorp/go-tfe/audit_trail.go new file mode 100644 index 00000000..72e498f4 --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/audit_trail.go @@ -0,0 +1,142 @@ +package tfe + +import ( + "context" + "encoding/json" + "io/ioutil" + "net/http" + "time" + + "github.com/google/go-querystring/query" + retryablehttp "github.com/hashicorp/go-retryablehttp" +) + +// Compile-time proof of interface implementation +var _ AuditTrails = (*auditTrails)(nil) + +// AuditTrails describes all the audit event related methods that the Terraform +// Cloud API supports. +// **Note:** These methods require the client to be configured with an organization token for +// an organization in the Business tier. Furthermore, these methods are only available in Terraform Cloud. +// +// TFC API Docs: https://www.terraform.io/cloud-docs/api-docs/audit-trails +type AuditTrails interface { + // Read all the audit events in an organization. + List(ctx context.Context, options *AuditTrailListOptions) (*AuditTrailList, error) +} + +// auditTrails implements AuditTrails +type auditTrails struct { + client *Client +} + +// AuditTrailRequest represents the request details of the audit event. +type AuditTrailRequest struct { + ID string `json:"id"` +} + +// AuditTrailAuth represents the details of the actor that invoked the audit event. +type AuditTrailAuth struct { + AccessorID string `json:"accessor_id"` + Description string `json:"description"` + Type string `json:"type"` + ImpersonatorID *string `json:"impersonator_id"` + OrganizationID string `json:"organization_id"` +} + +// AuditTrailResource represents the details of the API resource in the audit event. +type AuditTrailResource struct { + ID string `json:"id"` + Type string `json:"type"` + Action string `json:"action"` + Meta map[string]interface{} `json:"meta"` +} + +// AuditTrail represents an event in the TFC audit log. +type AuditTrail struct { + ID string `json:"id"` + Version string `json:"version"` + Type string `json:"type"` + Timestamp time.Time `json:"timestamp"` + + Auth AuditTrailAuth `json:"auth"` + Request AuditTrailRequest `json:"request"` + Resource AuditTrailResource `json:"resource"` +} + +// AuditTrailList represents a list of audit trails. +type AuditTrailList struct { + *Pagination + + Items []*AuditTrail `json:"data"` +} + +// AuditTrailListOptions represents the options for listing audit trails. +type AuditTrailListOptions struct { + // Optional: Returns only audit trails created after this date + Since time.Time `url:"since,omitempty"` + *ListOptions +} + +// List all the audit events in an organization. +func (s *auditTrails) List(ctx context.Context, options *AuditTrailListOptions) (*AuditTrailList, error) { + u, err := s.client.baseURL.Parse("/api/v2/organization/audit-trail") + if err != nil { + return nil, err + } + + headers := make(http.Header) + headers.Set("User-Agent", _userAgent) + headers.Set("Authorization", "Bearer "+s.client.token) + headers.Set("Content-Type", "application/json") + + if options != nil { + q, err := query.Values(options) + if err != nil { + return nil, err + } + + u.RawQuery = encodeQueryParams(q) + } + + req, err := retryablehttp.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, err + } + + // Attach the headers to the request + for k, v := range headers { + req.Header[k] = v + } + + if err := s.client.limiter.Wait(ctx); err != nil { + return nil, err + } + + resp, err := s.client.http.Do(req.WithContext(ctx)) + if err != nil { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + return nil, err + } + } + defer resp.Body.Close() + + if err := checkResponseCode(resp); err != nil { + return nil, err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + atl := &AuditTrailList{} + if err := json.Unmarshal(body, atl); err != nil { + return nil, err + } + + return atl, nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/comment.go b/vendor/github.com/hashicorp/go-tfe/comment.go new file mode 100644 index 00000000..0f1a069a --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/comment.go @@ -0,0 +1,129 @@ +package tfe + +import ( + "context" + "fmt" + "net/url" +) + +// Compile-time proof of interface implementation. +var _ Comments = (*comments)(nil) + +// Comments describes all the comment related methods that the +// Terraform Enterprise API supports. +// +// TFE API docs: +// https://www.terraform.io/docs/cloud/api/comments.html +type Comments interface { + // List all comments of the given run. + List(ctx context.Context, runID string) (*CommentList, error) + + // Read a comment by its ID. + Read(ctx context.Context, commentID string) (*Comment, error) + + // Create a new comment with the given options. + Create(ctx context.Context, runID string, options CommentCreateOptions) (*Comment, error) +} + +// Comments implements Comments. +type comments struct { + client *Client +} + +// CommentList represents a list of comments. +type CommentList struct { + *Pagination + Items []*Comment +} + +// Comment represents a Terraform Enterprise comment. +type Comment struct { + ID string `jsonapi:"primary,comments"` + Body string `jsonapi:"attr,body"` +} + +type CommentCreateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,comments"` + + // Required: Body of the comment. + Body string `jsonapi:"attr,body"` +} + +// List all comments of the given run. +func (s *comments) List(ctx context.Context, runID string) (*CommentList, error) { + if !validStringID(&runID) { + return nil, ErrInvalidRunID + } + + u := fmt.Sprintf("runs/%s/comments", url.QueryEscape(runID)) + req, err := s.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + cl := &CommentList{} + err = s.client.do(ctx, req, cl) + if err != nil { + return nil, err + } + + return cl, nil +} + +// Create a new comment with the given options. +func (s *comments) Create(ctx context.Context, runID string, options CommentCreateOptions) (*Comment, error) { + if err := options.valid(); err != nil { + return nil, err + } + + if !validStringID(&runID) { + return nil, ErrInvalidRunID + } + + u := fmt.Sprintf("runs/%s/comments", url.QueryEscape(runID)) + req, err := s.client.newRequest("POST", u, &options) + if err != nil { + return nil, err + } + + comm := &Comment{} + err = s.client.do(ctx, req, comm) + if err != nil { + return nil, err + } + + return comm, err +} + +// Read a comment by its ID. +func (s *comments) Read(ctx context.Context, commentID string) (*Comment, error) { + if !validStringID(&commentID) { + return nil, ErrInvalidCommentID + } + + u := fmt.Sprintf("comments/%s", url.QueryEscape(commentID)) + req, err := s.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + comm := &Comment{} + err = s.client.do(ctx, req, comm) + if err != nil { + return nil, err + } + + return comm, nil +} + +func (o CommentCreateOptions) valid() error { + if !validString(&o.Body) { + return ErrInvalidCommentBody + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/configuration_version.go b/vendor/github.com/hashicorp/go-tfe/configuration_version.go index ceb9ef63..ad4a8a37 100644 --- a/vendor/github.com/hashicorp/go-tfe/configuration_version.go +++ b/vendor/github.com/hashicorp/go-tfe/configuration_version.go @@ -3,7 +3,9 @@ package tfe import ( "bytes" "context" + "errors" "fmt" + "io/fs" "net/url" "os" "time" @@ -21,7 +23,7 @@ var _ ConfigurationVersions = (*configurationVersions)(nil) // https://www.terraform.io/docs/enterprise/api/configuration-versions.html type ConfigurationVersions interface { // List returns all configuration versions of a workspace. - List(ctx context.Context, workspaceID string, options ConfigurationVersionListOptions) (*ConfigurationVersionList, error) + List(ctx context.Context, workspaceID string, options *ConfigurationVersionListOptions) (*ConfigurationVersionList, error) // Create is used to create a new configuration version. The created // configuration version will be usable once data is uploaded to it. @@ -37,6 +39,13 @@ type ConfigurationVersions interface { // the upload URL from a configuration version and the full path to the // configuration files on disk. Upload(ctx context.Context, url string, path string) error + + // Archive a configuration version. This can only be done on configuration versions that + // were created with the API or CLI, are in an uploaded state, and have no runs in progress. + Archive(ctx context.Context, cvID string) error + + // Download a configuration version. Only configuration versions in the uploaded state may be downloaded. + Download(ctx context.Context, cvID string) ([]byte, error) } // configurationVersions implements ConfigurationVersions. @@ -47,9 +56,11 @@ type configurationVersions struct { // ConfigurationStatus represents a configuration version status. type ConfigurationStatus string -//List all available configuration version statuses. +// List all available configuration version statuses. const ( + ConfigurationArchived ConfigurationStatus = "archived" ConfigurationErrored ConfigurationStatus = "errored" + ConfigurationFetching ConfigurationStatus = "fetching" ConfigurationPending ConfigurationStatus = "pending" ConfigurationUploaded ConfigurationStatus = "uploaded" ) @@ -93,24 +104,53 @@ type ConfigurationVersion struct { // CVStatusTimestamps holds the timestamps for individual configuration version // statuses. type CVStatusTimestamps struct { + ArchivedAt time.Time `jsonapi:"attr,archived-at,rfc3339"` + FetchingAt time.Time `jsonapi:"attr,fetching-at,rfc3339"` FinishedAt time.Time `jsonapi:"attr,finished-at,rfc3339"` QueuedAt time.Time `jsonapi:"attr,queued-at,rfc3339"` StartedAt time.Time `jsonapi:"attr,started-at,rfc3339"` } +// ConfigVerIncludeOpt represents the available options for include query params. +// https://www.terraform.io/docs/cloud/api/configuration-versions.html#available-related-resources +type ConfigVerIncludeOpt string + +const ( + ConfigVerIngressAttributes ConfigVerIncludeOpt = "ingress_attributes" + ConfigVerRun ConfigVerIncludeOpt = "run" +) + // ConfigurationVersionReadOptions represents the options for reading a configuration version. type ConfigurationVersionReadOptions struct { - Include string `url:"include"` + // Optional: A list of relations to include. See available resources: + // https://www.terraform.io/docs/cloud/api/configuration-versions.html#available-related-resources + Include []ConfigVerIncludeOpt `url:"include,omitempty"` } // ConfigurationVersionListOptions represents the options for listing // configuration versions. type ConfigurationVersionListOptions struct { ListOptions - - // A list of relations to include. See available resources: + // Optional: A list of relations to include. See available resources: // https://www.terraform.io/docs/cloud/api/configuration-versions.html#available-related-resources - Include *string `url:"include"` + Include []ConfigVerIncludeOpt `url:"include,omitempty"` +} + +// ConfigurationVersionCreateOptions represents the options for creating a +// configuration version. +type ConfigurationVersionCreateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,configuration-versions"` + + // Optional: When true, runs are queued automatically when the configuration version + // is uploaded. + AutoQueueRuns *bool `jsonapi:"attr,auto-queue-runs,omitempty"` + + // Optional: When true, this configuration version can only be used for planning. + Speculative *bool `jsonapi:"attr,speculative,omitempty"` } // IngressAttributes include commit information associated with configuration versions sourced from VCS. @@ -139,13 +179,16 @@ type IngressAttributes struct { } // List returns all configuration versions of a workspace. -func (s *configurationVersions) List(ctx context.Context, workspaceID string, options ConfigurationVersionListOptions) (*ConfigurationVersionList, error) { +func (s *configurationVersions) List(ctx context.Context, workspaceID string, options *ConfigurationVersionListOptions) (*ConfigurationVersionList, error) { if !validStringID(&workspaceID) { return nil, ErrInvalidWorkspaceID } + if err := options.valid(); err != nil { + return nil, err + } u := fmt.Sprintf("workspaces/%s/configuration-versions", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("GET", u, &options) + req, err := s.client.newRequest("GET", u, options) if err != nil { return nil, err } @@ -159,23 +202,6 @@ func (s *configurationVersions) List(ctx context.Context, workspaceID string, op return cvl, nil } -// ConfigurationVersionCreateOptions represents the options for creating a -// configuration version. -type ConfigurationVersionCreateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,configuration-versions"` - - // When true, runs are queued automatically when the configuration version - // is uploaded. - AutoQueueRuns *bool `jsonapi:"attr,auto-queue-runs,omitempty"` - - // When true, this configuration version can only be used for planning. - Speculative *bool `jsonapi:"attr,speculative,omitempty"` -} - // Create is used to create a new configuration version. The created // configuration version will be usable once data is uploaded to it. func (s *configurationVersions) Create(ctx context.Context, workspaceID string, options ConfigurationVersionCreateOptions) (*ConfigurationVersion, error) { @@ -208,6 +234,9 @@ func (s *configurationVersions) ReadWithOptions(ctx context.Context, cvID string if !validStringID(&cvID) { return nil, ErrInvalidConfigVersionID } + if err := options.valid(); err != nil { + return nil, err + } u := fmt.Sprintf("configuration-versions/%s", url.QueryEscape(cvID)) req, err := s.client.newRequest("GET", u, options) @@ -227,11 +256,16 @@ func (s *configurationVersions) ReadWithOptions(ctx context.Context, cvID string // Upload packages and uploads Terraform configuration files. It requires the // upload URL from a configuration version and the path to the configuration // files on disk. -func (s *configurationVersions) Upload(ctx context.Context, url, path string) error { +func (s *configurationVersions) Upload(ctx context.Context, u, path string) error { file, err := os.Stat(path) + if err != nil { - return err + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf(`failed to find terraform configuration files under the path "%v": %w`, path, err) + } + return fmt.Errorf(`unable to upload terraform configuration files from the path "%v": %w`, path, err) } + if !file.Mode().IsDir() { return ErrMissingDirectory } @@ -243,10 +277,86 @@ func (s *configurationVersions) Upload(ctx context.Context, url, path string) er return err } - req, err := s.client.newRequest("PUT", url, body) + req, err := s.client.newRequest("PUT", u, body) if err != nil { return err } return s.client.do(ctx, req, nil) } + +// Archive a configuration version. This can only be done on configuration versions that +// were created with the API or CLI, are in an uploaded state, and have no runs in progress. +func (s *configurationVersions) Archive(ctx context.Context, cvID string) error { + if !validStringID(&cvID) { + return ErrInvalidConfigVersionID + } + + body := bytes.NewBuffer(nil) + + u := fmt.Sprintf("configuration-versions/%s/actions/archive", url.QueryEscape(cvID)) + req, err := s.client.newRequest("POST", u, body) + if err != nil { + return err + } + + return s.client.do(ctx, req, nil) +} + +func (o *ConfigurationVersionReadOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateConfigVerIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func (o *ConfigurationVersionListOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateConfigVerIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func validateConfigVerIncludeParams(params []ConfigVerIncludeOpt) error { + for _, p := range params { + switch p { + case ConfigVerIngressAttributes, ConfigVerRun: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil +} + +// Download a configuration version. Only configuration versions in the uploaded state may be downloaded. +func (s *configurationVersions) Download(ctx context.Context, cvID string) ([]byte, error) { + if !validStringID(&cvID) { + return nil, ErrInvalidConfigVersionID + } + + u := fmt.Sprintf("configuration-versions/%s/download", url.QueryEscape(cvID)) + req, err := s.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + err = s.client.do(ctx, req, &buf) + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/errors.go b/vendor/github.com/hashicorp/go-tfe/errors.go index 91f4b92f..226d256e 100644 --- a/vendor/github.com/hashicorp/go-tfe/errors.go +++ b/vendor/github.com/hashicorp/go-tfe/errors.go @@ -12,93 +12,280 @@ var ( // ErrResourceNotFound is returned when receiving a 404. ErrResourceNotFound = errors.New("resource not found") - // ErrRequiredName is returned when a name option is not present. - ErrRequiredName = errors.New("name is required") - - // ErrInvalidName is returned when the name option has invalid value. - ErrInvalidName = errors.New("invalid value for name") - // ErrMissingDirectory is returned when the path does not have an existing directory. ErrMissingDirectory = errors.New("path needs to be an existing directory") ) +// Options/fields that cannot be defined +var ( + ErrUnsupportedOperations = errors.New("operations is deprecated and cannot be specified when execution mode is used") + + ErrUnsupportedPrivateKey = errors.New("private Key can only be present with Azure DevOps Server service provider") + + ErrUnsupportedRunTriggerType = errors.New(`"RunTriggerType" must be "inbound" when requesting "include" query params`) + + ErrUnsupportedBothTriggerPatternsAndPrefixes = errors.New(`"TriggerPatterns" and "TriggerPrefixes" cannot be populated at the same time`) +) + +// Library errors that usually indicate a bug in the implementation of go-tfe +var ( + ErrItemsMustBeSlice = errors.New(`model field "Items" must be a slice`) // ErrItemsMustBeSlice is returned when an API response attribute called Items is not a slice + + ErrInvalidRequestBody = errors.New("go-tfe bug: DELETE/PATCH/POST body must be nil, ptr, or ptr slice") // ErrInvalidRequestBody is returned when a request body for DELETE/PATCH/POST is not a reference type + + ErrInvalidStructFormat = errors.New("go-tfe bug: struct can't use both json and jsonapi attributes") // ErrInvalidStructFormat is returned when a mix of json and jsonapi tagged fields are used in the same struct +) + // Resource Errors var ( - // ErrWorkspaceLocked is returned when trying to lock a + ErrWorkspaceLocked = errors.New("workspace already locked") // ErrWorkspaceLocked is returned when trying to lock a // locked workspace. - ErrWorkspaceLocked = errors.New("workspace already locked") - // ErrWorkspaceNotLocked is returned when trying to unlock + ErrWorkspaceNotLocked = errors.New("workspace already unlocked") // ErrWorkspaceNotLocked is returned when trying to unlock // a unlocked workspace. - ErrWorkspaceNotLocked = errors.New("workspace already unlocked") - // ErrInvalidWorkspaceID is returned when the workspace ID is invalid. + ErrWorkspaceLockedByRun = errors.New("unable to unlock workspace locked by run") // ErrWorkspaceLockedByRun is returned when trying to unlock a + // workspace locked by a run +) + +// Invalid values for resources/struct fields +var ( ErrInvalidWorkspaceID = errors.New("invalid value for workspace ID") - // ErrInvalidWorkspaceValue is returned when workspace value is invalid. ErrInvalidWorkspaceValue = errors.New("invalid value for workspace") - // ErrWorkspacesRequired is returned when the Workspaces are not present. - ErrWorkspacesRequired = errors.New("workspaces is required") + ErrInvalidTerraformVersionID = errors.New("invalid value for terraform version ID") - // ErrWorkspaceMinLimit is returned when the length of Workspaces is 0. - ErrWorkspaceMinLimit = errors.New("must provide at least one workspace") + ErrInvalidTerraformVersionType = errors.New("invalid type for terraform version. Please use 'terraform-version'") - // ErrMissingTagIdentifier is returned when tag resource identifiers are invalid - ErrMissingTagIdentifier = errors.New("must specify at least one tag by ID or name") + ErrInvalidConfigVersionID = errors.New("invalid value for configuration version ID") + + ErrInvalidCostEstimateID = errors.New("invalid value for cost estimate ID") - // Run/Apply errors + ErrInvalidSMTPAuth = errors.New("invalid smtp auth type") + + ErrInvalidAgentPoolID = errors.New("invalid value for agent pool ID") + + ErrInvalidAgentTokenID = errors.New("invalid value for agent token ID") - // ErrInvalidRunID is returned when the run ID is invalid. ErrInvalidRunID = errors.New("invalid value for run ID") - // ErrInvalidApplyID is returned when the apply ID is invalid. - ErrInvalidApplyID = errors.New("invalid value for apply ID") + ErrInvalidRunTaskCategory = errors.New(`category must be "task"`) + + ErrInvalidRunTaskID = errors.New("invalid value for run task ID") + + ErrInvalidRunTaskURL = errors.New("invalid url for run task URL") + + ErrInvalidWorkspaceRunTaskID = errors.New("invalid value for workspace run task ID") - // Organzation errors + ErrInvalidWorkspaceRunTaskType = errors.New(`invalid value for type, please use "workspace-tasks"`) + + ErrInvalidTaskResultID = errors.New("invalid value for task result ID") + + ErrInvalidTaskStageID = errors.New("invalid value for task stage ID") + + ErrInvalidApplyID = errors.New("invalid value for apply ID") - // ErrInvalidOrg is returned when the organization option has an invalid value. ErrInvalidOrg = errors.New("invalid value for organization") - // Agent errors + ErrInvalidName = errors.New("invalid value for name") - // ErrInvalidAgentPoolID is returned when the agent pool ID is invalid. - ErrInvalidAgentPoolID = errors.New("invalid value for agent pool ID") + ErrInvalidNotificationConfigID = errors.New("invalid value for notification configuration ID") - // ErrInvalidAgentTokenID is returned when the agent toek ID is invalid. - ErrInvalidAgentTokenID = errors.New("invalid value for agent token ID") + ErrInvalidMembership = errors.New("invalid value for membership") - // Token errors + ErrInvalidMembershipIDs = errors.New("invalid value for organization membership ids") - // ErrAgentTokenDescription is returned when the description is blank. - ErrAgentTokenDescription = errors.New("agent token description can't be blank") + ErrInvalidOauthClientID = errors.New("invalid value for OAuth client ID") - // Config errors + ErrInvalidOauthTokenID = errors.New("invalid value for OAuth token ID") - // ErrInvalidConfigVersionID is returned when the configuration version ID is invalid. - ErrInvalidConfigVersionID = errors.New("invalid value for configuration version ID") + ErrInvalidPolicySetID = errors.New("invalid value for policy set ID") - // Cost Esimation Errors + ErrInvalidPolicyCheckID = errors.New("invalid value for policy check ID") - // ErrInvalidCostEstimateID is returned when the cost estimate ID is invalid. - ErrInvalidCostEstimateID = errors.New("invalid value for cost estimate ID") + ErrInvalidTag = errors.New("invalid tag id") + + ErrInvalidPlanExportID = errors.New("invalid value for plan export ID") + + ErrInvalidPlanID = errors.New("invalid value for plan ID") + + ErrInvalidParamID = errors.New("invalid value for parameter ID") + + ErrInvalidPolicyID = errors.New("invalid value for policy ID") + + ErrInvalidProvider = errors.New("invalid value for provider") + + ErrInvalidVersion = errors.New("invalid value for version") + + ErrInvalidRunTriggerID = errors.New("invalid value for run trigger ID") - // User + ErrInvalidRunTriggerType = errors.New(`invalid value or no value for RunTriggerType. It must be either "inbound" or "outbound"`) + + ErrInvalidIncludeValue = errors.New(`invalid value for "include" field`) + + ErrInvalidSHHKeyID = errors.New("invalid value for SSH key ID") + + ErrInvalidStateVerID = errors.New("invalid value for state version ID") + + ErrInvalidOutputID = errors.New("invalid value for state version output ID") + + ErrInvalidAccessTeamID = errors.New("invalid value for team access ID") + + ErrInvalidTeamID = errors.New("invalid value for team ID") + + ErrInvalidUsernames = errors.New("invalid value for usernames") + + ErrInvalidUserID = errors.New("invalid value for user ID") - // ErrInvalidUservalue is invalid. ErrInvalidUserValue = errors.New("invalid value for user") - // Settings + ErrInvalidTokenID = errors.New("invalid value for token ID") - // ErrInvalidSMTPAuth is returned when the smtp auth type is not valid. - ErrInvalidSMTPAuth = errors.New("invalid smtp auth type") + ErrInvalidCategory = errors.New("category must be policy-set") - // Terraform Versions + ErrInvalidPolicies = errors.New("must provide at least one policy") - // ErrInvalidTerraformVersionID is returned when the ID for a terraform - // version is invalid. - ErrInvalidTerraformVersionID = errors.New("invalid value for terraform version ID") + ErrInvalidVariableID = errors.New("invalid value for variable ID") - // ErrInvalidTerraformVersionType is returned when the type is not valid. - ErrInvalidTerraformVersionType = errors.New("invalid type for terraform version. Please use 'terraform-version'") + ErrInvalidNotificationTrigger = errors.New("invalid value for notification trigger") + + ErrInvalidVariableSetID = errors.New("invalid variable set ID") + + ErrInvalidCommentID = errors.New("invalid value for comment ID") + + ErrInvalidCommentBody = errors.New("invalid value for comment body") + + ErrInvalidNamespace = errors.New("invalid value for namespace") + + ErrInvalidKeyID = errors.New("invalid value for key-id") + + ErrInvalidOS = errors.New("invalid value for OS") + + ErrInvalidArch = errors.New("invalid value for arch") + + ErrInvalidRegistryName = errors.New("invalid value for registry-name") +) + +// Missing values for required field/option +var ( + ErrRequiredAccess = errors.New("access is required") + + ErrRequiredAgentPoolID = errors.New("'agent' execution mode requires an agent pool ID to be specified") + + ErrRequiredAgentMode = errors.New("specifying an agent pool ID requires 'agent' execution mode") + + ErrRequiredCategory = errors.New("category is required") + + ErrRequiredDestinationType = errors.New("destination type is required") + + ErrRequiredDataType = errors.New("data type is required") + + ErrRequiredKey = errors.New("key is required") + + ErrRequiredName = errors.New("name is required") + + ErrRequiredEnabled = errors.New("enabled is required") + + ErrRequiredEnforce = errors.New("enforce is required") + + ErrRequiredEnforcementPath = errors.New("enforcement path is required") + + ErrRequiredEnforcementMode = errors.New("enforcement mode is required") + + ErrRequiredEmail = errors.New("email is required") + + ErrRequiredM5 = errors.New("MD5 is required") + + ErrRequiredURL = errors.New("url is required") + + ErrRequiredAPIURL = errors.New("API URL is required") + + ErrRequiredHTTPURL = errors.New("HTTP URL is required") + + ErrRequiredServiceProvider = errors.New("service provider is required") + + ErrRequiredProvider = errors.New("provider is required") + + ErrRequiredOauthToken = errors.New("OAuth token is required") + + ErrRequiredOauthTokenID = errors.New("oauth token ID is required") + + ErrRequiredTestNumber = errors.New("TestNumber is required") + + ErrMissingTagIdentifier = errors.New("must specify at least one tag by ID or name") + + ErrAgentTokenDescription = errors.New("agent token description can't be blank") + + ErrRequiredTagID = errors.New("you must specify at least one tag id to remove") + + ErrRequiredTagWorkspaceID = errors.New("you must specify at least one workspace to add tag to") + + ErrRequiredWorkspace = errors.New("workspace is required") + + ErrRequiredWorkspaceID = errors.New("workspace ID is required") + + ErrWorkspacesRequired = errors.New("workspaces is required") + + ErrWorkspaceMinLimit = errors.New("must provide at least one workspace") + + ErrRequiredPlan = errors.New("plan is required") + + ErrRequiredPolicies = errors.New("policies is required") + + ErrRequiredVersion = errors.New("version is required") + + ErrRequiredVCSRepo = errors.New("vcs repo is required") + + ErrRequiredIdentifier = errors.New("identifier is required") + + ErrRequiredDisplayIdentifier = errors.New("display identifier is required") + + ErrRequiredSha = errors.New("sha is required") + + ErrRequiredSourceable = errors.New("sourceable is required") + + ErrRequiredValue = errors.New("value is required") + + ErrRequiredOrg = errors.New("organization is required") + + ErrRequiredTeam = errors.New("team is required") + + ErrRequiredStateVerListOps = errors.New("StateVersionListOptions is required") + + ErrRequiredTeamAccessListOps = errors.New("TeamAccessListOptions is required") + + ErrRequiredRunTriggerListOps = errors.New("RunTriggerListOptions is required") + + ErrRequiredTFVerCreateOps = errors.New("version, URL and sha is required for AdminTerraformVersionCreateOptions") + + ErrRequiredSerial = errors.New("serial is required") + + ErrRequiredState = errors.New("state is required") + + ErrRequiredSHHKeyID = errors.New("SSH key ID is required") + + ErrRequiredOnlyOneField = errors.New("only one of usernames or organization membership ids can be provided") + + ErrRequiredUsernameOrMembershipIds = errors.New("usernames or organization membership ids are required") + + ErrRequiredGlobalFlag = errors.New("global flag is required") + + ErrRequiredWorkspacesList = errors.New("no workspaces list provided") + + ErrCommentBody = errors.New("comment body is required") + + ErrEmptyTeamName = errors.New("team name can not be empty") + + ErrInvalidEmail = errors.New("email is invalid") + + ErrRequiredPrivateRegistry = errors.New("only private registry is allowed") + + ErrRequiredOS = errors.New("OS is required") + + ErrRequiredArch = errors.New("arch is required") + + ErrRequiredShasum = errors.New("shasum is required") + + ErrRequiredFilename = errors.New("filename is required") ) diff --git a/vendor/github.com/hashicorp/go-tfe/generate_mocks.sh b/vendor/github.com/hashicorp/go-tfe/generate_mocks.sh index 78f69dd0..0e933ab5 100644 --- a/vendor/github.com/hashicorp/go-tfe/generate_mocks.sh +++ b/vendor/github.com/hashicorp/go-tfe/generate_mocks.sh @@ -17,6 +17,7 @@ mockgen -source=admin_workspace.go -destination=mocks/admin_workspace_mocks.go - mockgen -source=agent_pool.go -destination=mocks/agent_pool_mocks.go -package=mocks mockgen -source=agent_token.go -destination=mocks/agent_token_mocks.go -package=mocks mockgen -source=apply.go -destination=mocks/apply_mocks.go -package=mocks +mockgen -source=audit_trail.go -destination=mocks/audit_trail.go -package=mocks mockgen -source=configuration_version.go -destination=mocks/configuration_version_mocks.go -package=mocks mockgen -source=cost_estimate.go -destination=mocks/cost_estimate_mocks.go -package=mocks mockgen -source=ip_ranges.go -destination=mocks/ip_ranges_mocks.go -package=mocks @@ -35,7 +36,11 @@ mockgen -source=policy_set.go -destination=mocks/policy_set_mocks.go -package=mo mockgen -source=policy_set_parameter.go -destination=mocks/policy_set_parameter_mocks.go -package=mocks mockgen -source=policy_set_version.go -destination=mocks/policy_set_version_mocks.go -package=mocks mockgen -source=registry_module.go -destination=mocks/registry_module_mocks.go -package=mocks +mockgen -source=registry_provider.go -destination=mocks/registry_provider_mocks.go -package=mocks +mockgen -source=registry_provider_platform.go -destination=mocks/registry_provider_platform_mocks.go -package=mocks +mockgen -source=registry_provider_version.go -destination=mocks/registry_provider_version_mocks.go -package=mocks mockgen -source=run.go -destination=mocks/run_mocks.go -package=mocks +mockgen -source=run_task.go -destination=mocks/run_tasks.go -package=mocks mockgen -source=run_trigger.go -destination=mocks/run_trigger_mocks.go -package=mocks mockgen -source=ssh_key.go -destination=mocks/ssh_key_mocks.go -package=mocks mockgen -source=state_version.go -destination=mocks/state_version_mocks.go -package=mocks @@ -48,4 +53,6 @@ mockgen -source=team_token.go -destination=mocks/team_token_mocks.go -package=mo mockgen -source=user.go -destination=mocks/user_mocks.go -package=mocks mockgen -source=user_token.go -destination=mocks/user_token_mocks.go -package=mocks mockgen -source=variable.go -destination=mocks/variable_mocks.go -package=mocks +mockgen -source=variable_set.go -destination=mocks/variable_set_mocks.go -package=mocks mockgen -source=workspace.go -destination=mocks/workspace_mocks.go -package=mocks +mockgen -source=workspace_run_task.go -destination=mocks/workspace_run_tasks.go -package=mocks diff --git a/vendor/github.com/hashicorp/go-tfe/go.mod b/vendor/github.com/hashicorp/go-tfe/go.mod index 0ab3f213..72c62c86 100644 --- a/vendor/github.com/hashicorp/go-tfe/go.mod +++ b/vendor/github.com/hashicorp/go-tfe/go.mod @@ -6,16 +6,16 @@ require ( github.com/golang/mock v1.6.0 github.com/google/go-querystring v1.1.0 github.com/hashicorp/go-cleanhttp v0.5.2 - github.com/hashicorp/go-retryablehttp v0.7.0 - github.com/hashicorp/go-slug v0.7.0 - github.com/hashicorp/go-uuid v1.0.2 + github.com/hashicorp/go-retryablehttp v0.7.1 + github.com/hashicorp/go-slug v0.8.1 + github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.7.2 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/vendor/github.com/hashicorp/go-tfe/go.sum b/vendor/github.com/hashicorp/go-tfe/go.sum index 7cd07676..6886fd21 100644 --- a/vendor/github.com/hashicorp/go-tfe/go.sum +++ b/vendor/github.com/hashicorp/go-tfe/go.sum @@ -12,20 +12,20 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= -github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -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-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= +github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-slug v0.8.1 h1:srN7ivgAjHfZddYY1DjBaihRCFy20+vCcOrlx1O2AfE= +github.com/hashicorp/go-slug v0.8.1/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -54,5 +54,5 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/hashicorp/go-tfe/ip_ranges.go b/vendor/github.com/hashicorp/go-tfe/ip_ranges.go index 162a797f..c03e3fd1 100644 --- a/vendor/github.com/hashicorp/go-tfe/ip_ranges.go +++ b/vendor/github.com/hashicorp/go-tfe/ip_ranges.go @@ -2,10 +2,6 @@ package tfe import ( "context" - "encoding/json" - "fmt" - - "github.com/hashicorp/go-retryablehttp" ) // Compile-time proof of interface implementation. @@ -22,10 +18,12 @@ type IPRanges interface { Read(ctx context.Context, modifiedSince string) (*IPRange, error) } +// ipRanges implements IPRanges interface. type ipRanges struct { client *Client } +// IPRange represents a list of Terraform Cloud's IP ranges type IPRange struct { // List of IP ranges in CIDR notation used for connections from user site to Terraform Cloud APIs API []string `json:"api"` @@ -37,6 +35,7 @@ type IPRange struct { VCS []string `json:"vcs"` } +// Read an IPRange that was not modified since the specified date. func (i *ipRanges) Read(ctx context.Context, modifiedSince string) (*IPRange, error) { req, err := i.client.newRequest("GET", "/api/meta/ip-ranges", nil) if err != nil { @@ -55,42 +54,3 @@ func (i *ipRanges) Read(ctx context.Context, modifiedSince string) (*IPRange, er return ir, nil } - -// The IP ranges API is not returning jsonapi like every other endpoint -// which means we need to handle it differently. -func (i *ipRanges) customDo(ctx context.Context, req *retryablehttp.Request, ir *IPRange) error { - // Wait will block until the limiter can obtain a new token - // or returns an error if the given context is canceled. - if err := i.client.limiter.Wait(ctx); err != nil { - return err - } - - // Add the context to the request. - req = req.WithContext(ctx) - - // Execute the request and check the response. - resp, err := i.client.http.Do(req) - if err != nil { - // If we got an error, and the context has been canceled, - // the context's error is probably more useful. - select { - case <-ctx.Done(): - return ctx.Err() - default: - return err - } - } - defer resp.Body.Close() - - if resp.StatusCode < 200 && resp.StatusCode >= 400 { - return fmt.Errorf("error HTTP response while retrieving IP ranges: %d", resp.StatusCode) - } else if resp.StatusCode == 304 { - return nil - } - - err = json.NewDecoder(resp.Body).Decode(ir) - if err != nil { - return err - } - return nil -} diff --git a/vendor/github.com/hashicorp/go-tfe/logreader.go b/vendor/github.com/hashicorp/go-tfe/logreader.go index 96ffe840..48c47465 100644 --- a/vendor/github.com/hashicorp/go-tfe/logreader.go +++ b/vendor/github.com/hashicorp/go-tfe/logreader.go @@ -2,6 +2,7 @@ package tfe import ( "context" + "errors" "fmt" "io" "math" @@ -22,30 +23,20 @@ type LogReader struct { endOfText bool } -// backoff will perform exponential backoff based on the iteration and -// limited by the provided min and max (in milliseconds) durations. -func backoff(min, max float64, iter int) time.Duration { - backoff := math.Pow(2, float64(iter)/5) * min - if backoff > max { - backoff = max - } - return time.Duration(backoff) * time.Millisecond -} - func (r *LogReader) Read(l []byte) (int, error) { - if written, err := r.read(l); err != io.ErrNoProgress { + if written, err := r.read(l); !errors.Is(err, io.ErrNoProgress) { return written, err } // Loop until we can any data, the context is canceled or the // run is finsished. If we would return right away without any - // data, we could and up causing a io.ErrNoProgress error. + // data, we could end up causing a io.ErrNoProgress error. for r.reads = 1; ; r.reads++ { select { case <-r.ctx.Done(): return 0, r.ctx.Err() case <-time.After(backoff(500, 2000, r.reads)): - if written, err := r.read(l); err != io.ErrNoProgress { + if written, err := r.read(l); !errors.Is(err, io.ErrNoProgress) { return written, err } } @@ -82,7 +73,7 @@ func (r *LogReader) read(l []byte) (int, error) { // Read the retrieved chunk. written, err := resp.Body.Read(l) - if err != nil && err != io.EOF { + if err != nil && !errors.Is(err, io.EOF) { // Ignore io.EOF errors returned when reading from the response // body as this indicates the end of the chunk and not the end // of the logfile. @@ -140,3 +131,13 @@ func (r *LogReader) read(l []byte) (int, error) { } return 0, io.ErrNoProgress } + +// backoff will perform exponential backoff based on the iteration and +// limited by the provided min and max (in milliseconds) durations. +func backoff(min, max float64, iter int) time.Duration { + backoff := math.Pow(2, float64(iter)/5) * min + if backoff > max { + backoff = max + } + return time.Duration(backoff) * time.Millisecond +} diff --git a/vendor/github.com/hashicorp/go-tfe/notification_configuration.go b/vendor/github.com/hashicorp/go-tfe/notification_configuration.go index d5406875..97f8da19 100644 --- a/vendor/github.com/hashicorp/go-tfe/notification_configuration.go +++ b/vendor/github.com/hashicorp/go-tfe/notification_configuration.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" "time" @@ -18,7 +17,7 @@ var _ NotificationConfigurations = (*notificationConfigurations)(nil) // https://www.terraform.io/docs/cloud/api/notification-configurations.html type NotificationConfigurations interface { // List all the notification configurations within a workspace. - List(ctx context.Context, workspaceID string, options NotificationConfigurationListOptions) (*NotificationConfigurationList, error) + List(ctx context.Context, workspaceID string, options *NotificationConfigurationListOptions) (*NotificationConfigurationList, error) // Create a new notification configuration with the given options. Create(ctx context.Context, workspaceID string, options NotificationConfigurationCreateOptions) (*NotificationConfiguration, error) @@ -41,14 +40,17 @@ type notificationConfigurations struct { client *Client } -// List of available notification triggers. +// NotificationTriggerType represents the different TFE notifications that can be sent +// as a run's progress transitions between different states +type NotificationTriggerType string + const ( - NotificationTriggerCreated string = "run:created" - NotificationTriggerPlanning string = "run:planning" - NotificationTriggerNeedsAttention string = "run:needs_attention" - NotificationTriggerApplying string = "run:applying" - NotificationTriggerCompleted string = "run:completed" - NotificationTriggerErrored string = "run:errored" + NotificationTriggerCreated NotificationTriggerType = "run:created" + NotificationTriggerPlanning NotificationTriggerType = "run:planning" + NotificationTriggerNeedsAttention NotificationTriggerType = "run:needs_attention" + NotificationTriggerApplying NotificationTriggerType = "run:applying" + NotificationTriggerCompleted NotificationTriggerType = "run:completed" + NotificationTriggerErrored NotificationTriggerType = "run:errored" ) // NotificationDestinationType represents the destination type of the @@ -57,9 +59,10 @@ type NotificationDestinationType string // List of available notification destination types. const ( - NotificationDestinationTypeEmail NotificationDestinationType = "email" - NotificationDestinationTypeGeneric NotificationDestinationType = "generic" - NotificationDestinationTypeSlack NotificationDestinationType = "slack" + NotificationDestinationTypeEmail NotificationDestinationType = "email" + NotificationDestinationTypeGeneric NotificationDestinationType = "generic" + NotificationDestinationTypeSlack NotificationDestinationType = "slack" + NotificationDestinationTypeMicrosoftTeams NotificationDestinationType = "microsoft-teams" ) // NotificationConfigurationList represents a list of Notification @@ -106,27 +109,6 @@ type NotificationConfigurationListOptions struct { ListOptions } -// List all the notification configurations associated with a workspace. -func (s *notificationConfigurations) List(ctx context.Context, workspaceID string, options NotificationConfigurationListOptions) (*NotificationConfigurationList, error) { - if !validStringID(&workspaceID) { - return nil, ErrInvalidWorkspaceID - } - - u := fmt.Sprintf("workspaces/%s/notification-configurations", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("GET", u, options) - if err != nil { - return nil, err - } - - ncl := &NotificationConfigurationList{} - err = s.client.do(ctx, req, ncl) - if err != nil { - return nil, err - } - - return ncl, nil -} - // NotificationConfigurationCreateOptions represents the options for // creating a new notification configuration. type NotificationConfigurationCreateOptions struct { @@ -136,52 +118,86 @@ type NotificationConfigurationCreateOptions struct { // https://jsonapi.org/format/#crud-creating Type string `jsonapi:"primary,notification-configurations"` - // The destination type of the notification configuration + // Required: The destination type of the notification configuration DestinationType *NotificationDestinationType `jsonapi:"attr,destination-type"` - // Whether the notification configuration should be enabled or not + // Required: Whether the notification configuration should be enabled or not Enabled *bool `jsonapi:"attr,enabled"` - // The name of the notification configuration + // Required: The name of the notification configuration Name *string `jsonapi:"attr,name"` - // The token of the notification configuration + // Optional: The token of the notification configuration Token *string `jsonapi:"attr,token,omitempty"` - // The list of run events that will trigger notifications. - Triggers []string `jsonapi:"attr,triggers,omitempty"` + // Optional: The list of run events that will trigger notifications. + Triggers []NotificationTriggerType `jsonapi:"attr,triggers,omitempty"` - // The url of the notification configuration + // Optional: The url of the notification configuration URL *string `jsonapi:"attr,url,omitempty"` - // The list of email addresses that will receive notification emails. + // Optional: The list of email addresses that will receive notification emails. // EmailAddresses is only available for TFE users. It is not available in TFC. EmailAddresses []string `jsonapi:"attr,email-addresses,omitempty"` - // The list of users belonging to the organization that will receive notification emails. + // Optional: The list of users belonging to the organization that will receive notification emails. EmailUsers []*User `jsonapi:"relation,users,omitempty"` } -func (o NotificationConfigurationCreateOptions) valid() error { - if o.DestinationType == nil { - return errors.New("destination type is required") - } - if o.Enabled == nil { - return errors.New("enabled is required") +// NotificationConfigurationUpdateOptions represents the options for +// updating a existing notification configuration. +type NotificationConfigurationUpdateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,notification-configurations"` + + // Optional: Whether the notification configuration should be enabled or not + Enabled *bool `jsonapi:"attr,enabled,omitempty"` + + // Optional: The name of the notification configuration + Name *string `jsonapi:"attr,name,omitempty"` + + // Optional: The token of the notification configuration + Token *string `jsonapi:"attr,token,omitempty"` + + // Optional: The list of run events that will trigger notifications. + Triggers []NotificationTriggerType `jsonapi:"attr,triggers,omitempty"` + + // Optional: The url of the notification configuration + URL *string `jsonapi:"attr,url,omitempty"` + + // Optional: The list of email addresses that will receive notification emails. + // EmailAddresses is only available for TFE users. It is not available in TFC. + EmailAddresses []string `jsonapi:"attr,email-addresses,omitempty"` + + // Optional: The list of users belonging to the organization that will receive notification emails. + EmailUsers []*User `jsonapi:"relation,users,omitempty"` +} + +// List all the notification configurations associated with a workspace. +func (s *notificationConfigurations) List(ctx context.Context, workspaceID string, options *NotificationConfigurationListOptions) (*NotificationConfigurationList, error) { + if !validStringID(&workspaceID) { + return nil, ErrInvalidWorkspaceID } - if !validString(o.Name) { - return ErrRequiredName + + u := fmt.Sprintf("workspaces/%s/notification-configurations", url.QueryEscape(workspaceID)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err } - if *o.DestinationType == NotificationDestinationTypeGeneric || *o.DestinationType == NotificationDestinationTypeSlack { - if o.URL == nil { - return errors.New("url is required") - } + ncl := &NotificationConfigurationList{} + err = s.client.do(ctx, req, ncl) + if err != nil { + return nil, err } - return nil + + return ncl, nil } -// Creates a notification configuration with the given options. +// Create a notification configuration with the given options. func (s *notificationConfigurations) Create(ctx context.Context, workspaceID string, options NotificationConfigurationCreateOptions) (*NotificationConfiguration, error) { if !validStringID(&workspaceID) { return nil, ErrInvalidWorkspaceID @@ -208,7 +224,7 @@ func (s *notificationConfigurations) Create(ctx context.Context, workspaceID str // Read a notification configuration by its ID. func (s *notificationConfigurations) Read(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error) { if !validStringID(¬ificationConfigurationID) { - return nil, errors.New("invalid value for notification configuration ID") + return nil, ErrInvalidNotificationConfigID } u := fmt.Sprintf("notification-configurations/%s", url.QueryEscape(notificationConfigurationID)) @@ -226,42 +242,14 @@ func (s *notificationConfigurations) Read(ctx context.Context, notificationConfi return nc, nil } -// NotificationConfigurationUpdateOptions represents the options for -// updating a existing notification configuration. -type NotificationConfigurationUpdateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,notification-configurations"` - - // Whether the notification configuration should be enabled or not - Enabled *bool `jsonapi:"attr,enabled,omitempty"` - - // The name of the notification configuration - Name *string `jsonapi:"attr,name,omitempty"` - - // The token of the notification configuration - Token *string `jsonapi:"attr,token,omitempty"` - - // The list of run events that will trigger notifications. - Triggers []string `jsonapi:"attr,triggers,omitempty"` - - // The url of the notification configuration - URL *string `jsonapi:"attr,url,omitempty"` - - // The list of email addresses that will receive notification emails. - // EmailAddresses is only available for TFE users. It is not available in TFC. - EmailAddresses []string `jsonapi:"attr,email-addresses,omitempty"` - - // The list of users belonging to the organization that will receive notification emails. - EmailUsers []*User `jsonapi:"relation,users,omitempty"` -} - // Updates a notification configuration with the given options. func (s *notificationConfigurations) Update(ctx context.Context, notificationConfigurationID string, options NotificationConfigurationUpdateOptions) (*NotificationConfiguration, error) { if !validStringID(¬ificationConfigurationID) { - return nil, errors.New("invalid value for notification configuration ID") + return nil, ErrInvalidNotificationConfigID + } + + if err := options.valid(); err != nil { + return nil, err } u := fmt.Sprintf("notification-configurations/%s", url.QueryEscape(notificationConfigurationID)) @@ -282,7 +270,7 @@ func (s *notificationConfigurations) Update(ctx context.Context, notificationCon // Delete a notifications configuration by its ID. func (s *notificationConfigurations) Delete(ctx context.Context, notificationConfigurationID string) error { if !validStringID(¬ificationConfigurationID) { - return errors.New("invalid value for notification configuration ID") + return ErrInvalidNotificationConfigID } u := fmt.Sprintf("notification-configurations/%s", url.QueryEscape(notificationConfigurationID)) @@ -294,11 +282,11 @@ func (s *notificationConfigurations) Delete(ctx context.Context, notificationCon return s.client.do(ctx, req, nil) } -// Verifies a notification configuration by delivering a verification +// Verify a notification configuration by delivering a verification // payload to the configured url. func (s *notificationConfigurations) Verify(ctx context.Context, notificationConfigurationID string) (*NotificationConfiguration, error) { if !validStringID(¬ificationConfigurationID) { - return nil, errors.New("invalid value for notification configuration ID") + return nil, ErrInvalidNotificationConfigID } u := fmt.Sprintf( @@ -316,3 +304,59 @@ func (s *notificationConfigurations) Verify(ctx context.Context, notificationCon return nc, nil } + +func (o NotificationConfigurationCreateOptions) valid() error { + if o.DestinationType == nil { + return ErrRequiredDestinationType + } + if o.Enabled == nil { + return ErrRequiredEnabled + } + if !validString(o.Name) { + return ErrRequiredName + } + + if !validNotificationTriggerType(o.Triggers) { + return ErrInvalidNotificationTrigger + } + + if *o.DestinationType == NotificationDestinationTypeGeneric || + *o.DestinationType == NotificationDestinationTypeSlack || + *o.DestinationType == NotificationDestinationTypeMicrosoftTeams { + + if o.URL == nil { + return ErrRequiredURL + } + } + return nil +} + +func (o NotificationConfigurationUpdateOptions) valid() error { + if o.Name != nil && !validString(o.Name) { + return ErrRequiredName + } + + if !validNotificationTriggerType(o.Triggers) { + return ErrInvalidNotificationTrigger + } + + return nil +} + +func validNotificationTriggerType(triggers []NotificationTriggerType) bool { + for _, t := range triggers { + switch t { + case NotificationTriggerApplying, + NotificationTriggerNeedsAttention, + NotificationTriggerCompleted, + NotificationTriggerCreated, + NotificationTriggerErrored, + NotificationTriggerPlanning: + continue + default: + return false + } + } + + return true +} diff --git a/vendor/github.com/hashicorp/go-tfe/oauth_client.go b/vendor/github.com/hashicorp/go-tfe/oauth_client.go index 571ee643..4be2321a 100644 --- a/vendor/github.com/hashicorp/go-tfe/oauth_client.go +++ b/vendor/github.com/hashicorp/go-tfe/oauth_client.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" "time" @@ -18,7 +17,7 @@ var _ OAuthClients = (*oAuthClients)(nil) // https://www.terraform.io/docs/enterprise/api/oauth-clients.html type OAuthClients interface { // List all the OAuth clients for a given organization. - List(ctx context.Context, organization string, options OAuthClientListOptions) (*OAuthClientList, error) + List(ctx context.Context, organization string, options *OAuthClientListOptions) (*OAuthClientList, error) // Create an OAuth client to connect an organization and a VCS provider. Create(ctx context.Context, organization string, options OAuthClientCreateOptions) (*OAuthClient, error) @@ -83,31 +82,17 @@ type OAuthClient struct { OAuthTokens []*OAuthToken `jsonapi:"relation,oauth-tokens"` } +// A list of relations to include +type OAuthClientIncludeOpt string + +const OauthClientOauthTokens OAuthClientIncludeOpt = "oauth_tokens" + // OAuthClientListOptions represents the options for listing // OAuth clients. type OAuthClientListOptions struct { ListOptions -} - -// List all the OAuth clients for a given organization. -func (s *oAuthClients) List(ctx context.Context, organization string, options OAuthClientListOptions) (*OAuthClientList, error) { - if !validStringID(&organization) { - return nil, ErrInvalidOrg - } - - u := fmt.Sprintf("organizations/%s/oauth-clients", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - ocl := &OAuthClientList{} - err = s.client.do(ctx, req, ocl) - if err != nil { - return nil, err - } - return ocl, nil + Include []OAuthClientIncludeOpt `url:"include,omitempty"` } // OAuthClientCreateOptions represents the options for creating an OAuth client. @@ -121,49 +106,79 @@ type OAuthClientCreateOptions struct { // A display name for the OAuth Client. Name *string `jsonapi:"attr,name"` - // The base URL of your VCS provider's API. + // Required: The base URL of your VCS provider's API. APIURL *string `jsonapi:"attr,api-url"` - // The homepage of your VCS provider. + // Required: The homepage of your VCS provider. HTTPURL *string `jsonapi:"attr,http-url"` - // The OAuth Client key. + // Optional: The OAuth Client key. Key *string `jsonapi:"attr,key,omitempty"` - // The token string you were given by your VCS provider. + // Optional: The token string you were given by your VCS provider. OAuthToken *string `jsonapi:"attr,oauth-token-string,omitempty"` - // Private key associated with this vcs provider - only available for ado_server + // Optional: Private key associated with this vcs provider - only available for ado_server PrivateKey *string `jsonapi:"attr,private-key,omitempty"` - // Secret key associated with this vcs provider - only available for ado_server + // Optional: Secret key associated with this vcs provider - only available for ado_server Secret *string `jsonapi:"attr,secret,omitempty"` - // RSAPublicKey the text of the SSH public key associated with your BitBucket + // Optional: RSAPublicKey the text of the SSH public key associated with your BitBucket // Server Application Link. RSAPublicKey *string `jsonapi:"attr,rsa-public-key,omitempty"` - // The VCS provider being connected with. + // Required: The VCS provider being connected with. ServiceProvider *ServiceProviderType `jsonapi:"attr,service-provider"` } -func (o OAuthClientCreateOptions) valid() error { - if !validString(o.APIURL) { - return errors.New("API URL is required") - } - if !validString(o.HTTPURL) { - return errors.New("HTTP URL is required") +// OAuthClientUpdateOptions represents the options for updating an OAuth client. +type OAuthClientUpdateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,oauth-clients"` + + // Optional: A display name for the OAuth Client. + Name *string `jsonapi:"attr,name,omitempty"` + + // Optional: The OAuth Client key. + Key *string `jsonapi:"attr,key,omitempty"` + + // Optional: Secret key associated with this vcs provider - only available for ado_server + Secret *string `jsonapi:"attr,secret,omitempty"` + + // Optional: RSAPublicKey the text of the SSH public key associated with your BitBucket + // Server Application Link. + RSAPublicKey *string `jsonapi:"attr,rsa-public-key,omitempty"` + + // Optional: The token string you were given by your VCS provider. + OAuthToken *string `jsonapi:"attr,oauth-token-string,omitempty"` +} + +// List all the OAuth clients for a given organization. +func (s *oAuthClients) List(ctx context.Context, organization string, options *OAuthClientListOptions) (*OAuthClientList, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg } - if o.ServiceProvider == nil { - return errors.New("service provider is required") + if err := options.valid(); err != nil { + return nil, err } - if !validString(o.OAuthToken) && *o.ServiceProvider != *ServiceProvider(ServiceProviderBitbucketServer) { - return errors.New("OAuth token is required") + + u := fmt.Sprintf("organizations/%s/oauth-clients", url.QueryEscape(organization)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err } - if validString(o.PrivateKey) && *o.ServiceProvider != *ServiceProvider(ServiceProviderAzureDevOpsServer) { - return errors.New("private Key can only be present with Azure DevOps Server service provider") + + ocl := &OAuthClientList{} + err = s.client.do(ctx, req, ocl) + if err != nil { + return nil, err } - return nil + + return ocl, nil } // Create an OAuth client to connect an organization and a VCS provider. @@ -193,7 +208,7 @@ func (s *oAuthClients) Create(ctx context.Context, organization string, options // Read an OAuth client by its ID. func (s *oAuthClients) Read(ctx context.Context, oAuthClientID string) (*OAuthClient, error) { if !validStringID(&oAuthClientID) { - return nil, errors.New("invalid value for OAuth client ID") + return nil, ErrInvalidOauthClientID } u := fmt.Sprintf("oauth-clients/%s", url.QueryEscape(oAuthClientID)) @@ -211,35 +226,10 @@ func (s *oAuthClients) Read(ctx context.Context, oAuthClientID string) (*OAuthCl return oc, err } -// OAuthClientUpdateOptions represents the options for updating an OAuth client. -type OAuthClientUpdateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,oauth-clients"` - - // A display name for the OAuth Client. - Name *string `jsonapi:"attr,name,omitempty"` - - // The OAuth Client key. - Key *string `jsonapi:"attr,key,omitempty"` - - // Secret key associated with this vcs provider - only available for ado_server - Secret *string `jsonapi:"attr,secret,omitempty"` - - // RSAPublicKey the text of the SSH public key associated with your BitBucket - // Server Application Link. - RSAPublicKey *string `jsonapi:"attr,rsa-public-key,omitempty"` - - // The token string you were given by your VCS provider. - OAuthToken *string `jsonapi:"attr,oauth-token-string,omitempty"` -} - // Update an OAuth client by its ID. func (s *oAuthClients) Update(ctx context.Context, oAuthClientID string, options OAuthClientUpdateOptions) (*OAuthClient, error) { if !validStringID(&oAuthClientID) { - return nil, errors.New("invalid value for OAuth client ID") + return nil, ErrInvalidOauthClientID } u := fmt.Sprintf("oauth-clients/%s", url.QueryEscape(oAuthClientID)) @@ -260,7 +250,7 @@ func (s *oAuthClients) Update(ctx context.Context, oAuthClientID string, options // Delete an OAuth client by its ID. func (s *oAuthClients) Delete(ctx context.Context, oAuthClientID string) error { if !validStringID(&oAuthClientID) { - return errors.New("invalid value for OAuth client ID") + return ErrInvalidOauthClientID } u := fmt.Sprintf("oauth-clients/%s", url.QueryEscape(oAuthClientID)) @@ -271,3 +261,47 @@ func (s *oAuthClients) Delete(ctx context.Context, oAuthClientID string) error { return s.client.do(ctx, req, nil) } + +func (o OAuthClientCreateOptions) valid() error { + if !validString(o.APIURL) { + return ErrRequiredAPIURL + } + if !validString(o.HTTPURL) { + return ErrRequiredHTTPURL + } + if o.ServiceProvider == nil { + return ErrRequiredServiceProvider + } + if !validString(o.OAuthToken) && *o.ServiceProvider != *ServiceProvider(ServiceProviderBitbucketServer) { + return ErrRequiredOauthToken + } + if validString(o.PrivateKey) && *o.ServiceProvider != *ServiceProvider(ServiceProviderAzureDevOpsServer) { + return ErrUnsupportedPrivateKey + } + return nil +} + +func (o *OAuthClientListOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateOauthClientIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func validateOauthClientIncludeParams(params []OAuthClientIncludeOpt) error { + for _, p := range params { + switch p { + case OauthClientOauthTokens: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/oauth_token.go b/vendor/github.com/hashicorp/go-tfe/oauth_token.go index 627b257b..2c882498 100644 --- a/vendor/github.com/hashicorp/go-tfe/oauth_token.go +++ b/vendor/github.com/hashicorp/go-tfe/oauth_token.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" "time" @@ -18,7 +17,7 @@ var _ OAuthTokens = (*oAuthTokens)(nil) // https://www.terraform.io/docs/cloud/api/oauth-tokens.html type OAuthTokens interface { // List all the OAuth tokens for a given organization. - List(ctx context.Context, organization string, options OAuthTokenListOptions) (*OAuthTokenList, error) + List(ctx context.Context, organization string, options *OAuthTokenListOptions) (*OAuthTokenList, error) // Read a OAuth token by its ID. Read(ctx context.Context, oAuthTokenID string) (*OAuthToken, error) @@ -59,14 +58,26 @@ type OAuthTokenListOptions struct { ListOptions } +// OAuthTokenUpdateOptions represents the options for updating an OAuth token. +type OAuthTokenUpdateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,oauth-tokens"` + + // Optional: A private SSH key to be used for git clone operations. + PrivateSSHKey *string `jsonapi:"attr,ssh-key,omitempty"` +} + // List all the OAuth tokens for a given organization. -func (s *oAuthTokens) List(ctx context.Context, organization string, options OAuthTokenListOptions) (*OAuthTokenList, error) { +func (s *oAuthTokens) List(ctx context.Context, organization string, options *OAuthTokenListOptions) (*OAuthTokenList, error) { if !validStringID(&organization) { return nil, ErrInvalidOrg } u := fmt.Sprintf("organizations/%s/oauth-tokens", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) + req, err := s.client.newRequest("GET", u, options) if err != nil { return nil, err } @@ -83,7 +94,7 @@ func (s *oAuthTokens) List(ctx context.Context, organization string, options OAu // Read an OAuth token by its ID. func (s *oAuthTokens) Read(ctx context.Context, oAuthTokenID string) (*OAuthToken, error) { if !validStringID(&oAuthTokenID) { - return nil, errors.New("invalid value for OAuth token ID") + return nil, ErrInvalidOauthTokenID } u := fmt.Sprintf("oauth-tokens/%s", url.QueryEscape(oAuthTokenID)) @@ -101,22 +112,10 @@ func (s *oAuthTokens) Read(ctx context.Context, oAuthTokenID string) (*OAuthToke return ot, err } -// OAuthTokenUpdateOptions represents the options for updating an OAuth token. -type OAuthTokenUpdateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,oauth-tokens"` - - // A private SSH key to be used for git clone operations. - PrivateSSHKey *string `jsonapi:"attr,ssh-key"` -} - // Update an existing OAuth token. func (s *oAuthTokens) Update(ctx context.Context, oAuthTokenID string, options OAuthTokenUpdateOptions) (*OAuthToken, error) { if !validStringID(&oAuthTokenID) { - return nil, errors.New("invalid value for OAuth token ID") + return nil, ErrInvalidOauthTokenID } u := fmt.Sprintf("oauth-tokens/%s", url.QueryEscape(oAuthTokenID)) @@ -137,7 +136,7 @@ func (s *oAuthTokens) Update(ctx context.Context, oAuthTokenID string, options O // Delete an OAuth token by its ID. func (s *oAuthTokens) Delete(ctx context.Context, oAuthTokenID string) error { if !validStringID(&oAuthTokenID) { - return errors.New("invalid value for OAuth token ID") + return ErrInvalidOauthTokenID } u := fmt.Sprintf("oauth-tokens/%s", url.QueryEscape(oAuthTokenID)) diff --git a/vendor/github.com/hashicorp/go-tfe/organization.go b/vendor/github.com/hashicorp/go-tfe/organization.go index 2b70d629..8b3b3941 100644 --- a/vendor/github.com/hashicorp/go-tfe/organization.go +++ b/vendor/github.com/hashicorp/go-tfe/organization.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" "time" @@ -18,7 +17,7 @@ var _ Organizations = (*organizations)(nil) // https://www.terraform.io/docs/cloud/api/organizations.html type Organizations interface { // List all the organizations visible to the current user. - List(ctx context.Context, options OrganizationListOptions) (*OrganizationList, error) + List(ctx context.Context, options *OrganizationListOptions) (*OrganizationList, error) // Create a new organization with the given options. Create(ctx context.Context, options OrganizationCreateOptions) (*Organization, error) @@ -32,14 +31,14 @@ type Organizations interface { // Delete an organization by its name. Delete(ctx context.Context, organization string) error - // Capacity shows the current run capacity of an organization. - Capacity(ctx context.Context, organization string) (*Capacity, error) + // ReadCapacity shows the current run capacity of an organization. + ReadCapacity(ctx context.Context, organization string) (*Capacity, error) - // Entitlements shows the entitlements of an organization. - Entitlements(ctx context.Context, organization string) (*Entitlements, error) + // ReadEntitlements shows the entitlements of an organization. + ReadEntitlements(ctx context.Context, organization string) (*Entitlements, error) - // RunQueue shows the current run queue of an organization. - RunQueue(ctx context.Context, organization string, options RunQueueOptions) (*RunQueue, error) + // ReadRunQueue shows the current run queue of an organization. + ReadRunQueue(ctx context.Context, organization string, options ReadRunQueueOptions) (*RunQueue, error) } // organizations implements Organizations. @@ -95,6 +94,7 @@ type Entitlements struct { CostEstimation bool `jsonapi:"attr,cost-estimation"` Operations bool `jsonapi:"attr,operations"` PrivateModuleRegistry bool `jsonapi:"attr,private-module-registry"` + RunTasks bool `jsonapi:"attr,run-tasks"` SSO bool `jsonapi:"attr,sso"` Sentinel bool `jsonapi:"attr,sentinel"` StateStorage bool `jsonapi:"attr,state-storage"` @@ -114,6 +114,7 @@ type OrganizationPermissions struct { CanCreateWorkspace bool `jsonapi:"attr,can-create-workspace"` CanCreateWorkspaceMigration bool `jsonapi:"attr,can-create-workspace-migration"` CanDestroy bool `jsonapi:"attr,can-destroy"` + CanManageRunTasks bool `jsonapi:"attr,can-manage-run-tasks"` CanTraverse bool `jsonapi:"attr,can-traverse"` CanUpdate bool `jsonapi:"attr,can-update"` CanUpdateAPIToken bool `jsonapi:"attr,can-update-api-token"` @@ -126,22 +127,6 @@ type OrganizationListOptions struct { ListOptions } -// List all the organizations visible to the current user. -func (s *organizations) List(ctx context.Context, options OrganizationListOptions) (*OrganizationList, error) { - req, err := s.client.newRequest("GET", "organizations", &options) - if err != nil { - return nil, err - } - - orgl := &OrganizationList{} - err = s.client.do(ctx, req, orgl) - if err != nil { - return nil, err - } - - return orgl, nil -} - // OrganizationCreateOptions represents the options for creating an organization. type OrganizationCreateOptions struct { // Type is a public field utilized by JSON:API to @@ -150,12 +135,45 @@ type OrganizationCreateOptions struct { // https://jsonapi.org/format/#crud-creating Type string `jsonapi:"primary,organizations"` - // Name of the organization. + // Required: Name of the organization. Name *string `jsonapi:"attr,name"` - // Admin email address. + // Required: Admin email address. Email *string `jsonapi:"attr,email"` + // Optional: Session expiration (minutes). + SessionRemember *int `jsonapi:"attr,session-remember,omitempty"` + + // Optional: Session timeout after inactivity (minutes). + SessionTimeout *int `jsonapi:"attr,session-timeout,omitempty"` + + // Optional: Authentication policy. + CollaboratorAuthPolicy *AuthPolicyType `jsonapi:"attr,collaborator-auth-policy,omitempty"` + + // Optional: Enable Cost Estimation + CostEstimationEnabled *bool `jsonapi:"attr,cost-estimation-enabled,omitempty"` + + // Optional: The name of the "owners" team + OwnersTeamSAMLRoleID *string `jsonapi:"attr,owners-team-saml-role-id,omitempty"` + + // Optional: SendPassingStatusesForUntriggeredSpeculativePlans toggles behavior of untriggered speculative plans to send status updates to version control systems like GitHub. + SendPassingStatusesForUntriggeredSpeculativePlans *bool `jsonapi:"attr,send-passing-statuses-for-untriggered-speculative-plans,omitempty"` +} + +// OrganizationUpdateOptions represents the options for updating an organization. +type OrganizationUpdateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,organizations"` + + // New name for the organization. + Name *string `jsonapi:"attr,name,omitempty"` + + // New admin email address. + Email *string `jsonapi:"attr,email,omitempty"` + // Session expiration (minutes). SessionRemember *int `jsonapi:"attr,session-remember,omitempty"` @@ -175,17 +193,25 @@ type OrganizationCreateOptions struct { SendPassingStatusesForUntriggeredSpeculativePlans *bool `jsonapi:"attr,send-passing-statuses-for-untriggered-speculative-plans,omitempty"` } -func (o OrganizationCreateOptions) valid() error { - if !validString(o.Name) { - return ErrRequiredName - } - if !validStringID(o.Name) { - return ErrInvalidName +// ReadRunQueueOptions represents the options for showing the queue. +type ReadRunQueueOptions struct { + ListOptions +} + +// List all the organizations visible to the current user. +func (s *organizations) List(ctx context.Context, options *OrganizationListOptions) (*OrganizationList, error) { + req, err := s.client.newRequest("GET", "organizations", options) + if err != nil { + return nil, err } - if !validString(o.Email) { - return errors.New("email is required") + + orgl := &OrganizationList{} + err = s.client.do(ctx, req, orgl) + if err != nil { + return nil, err } - return nil + + return orgl, nil } // Create a new organization with the given options. @@ -229,39 +255,6 @@ func (s *organizations) Read(ctx context.Context, organization string) (*Organiz return org, nil } -// OrganizationUpdateOptions represents the options for updating an organization. -type OrganizationUpdateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,organizations"` - - // New name for the organization. - Name *string `jsonapi:"attr,name,omitempty"` - - // New admin email address. - Email *string `jsonapi:"attr,email,omitempty"` - - // Session expiration (minutes). - SessionRemember *int `jsonapi:"attr,session-remember,omitempty"` - - // Session timeout after inactivity (minutes). - SessionTimeout *int `jsonapi:"attr,session-timeout,omitempty"` - - // Authentication policy. - CollaboratorAuthPolicy *AuthPolicyType `jsonapi:"attr,collaborator-auth-policy,omitempty"` - - // Enable Cost Estimation - CostEstimationEnabled *bool `jsonapi:"attr,cost-estimation-enabled,omitempty"` - - // The name of the "owners" team - OwnersTeamSAMLRoleID *string `jsonapi:"attr,owners-team-saml-role-id,omitempty"` - - // SendPassingStatusesForUntriggeredSpeculativePlans toggles behavior of untriggered speculative plans to send status updates to version control systems like GitHub. - SendPassingStatusesForUntriggeredSpeculativePlans *bool `jsonapi:"attr,send-passing-statuses-for-untriggered-speculative-plans,omitempty"` -} - // Update attributes of an existing organization. func (s *organizations) Update(ctx context.Context, organization string, options OrganizationUpdateOptions) (*Organization, error) { if !validStringID(&organization) { @@ -298,8 +291,8 @@ func (s *organizations) Delete(ctx context.Context, organization string) error { return s.client.do(ctx, req, nil) } -// Capacity shows the currently used capacity of an organization. -func (s *organizations) Capacity(ctx context.Context, organization string) (*Capacity, error) { +// ReadCapacity shows the currently used capacity of an organization. +func (s *organizations) ReadCapacity(ctx context.Context, organization string) (*Capacity, error) { if !validStringID(&organization) { return nil, ErrInvalidOrg } @@ -319,8 +312,8 @@ func (s *organizations) Capacity(ctx context.Context, organization string) (*Cap return c, nil } -// Entitlements shows the entitlements of an organization. -func (s *organizations) Entitlements(ctx context.Context, organization string) (*Entitlements, error) { +// ReadEntitlements shows the entitlements of an organization. +func (s *organizations) ReadEntitlements(ctx context.Context, organization string) (*Entitlements, error) { if !validStringID(&organization) { return nil, ErrInvalidOrg } @@ -340,13 +333,8 @@ func (s *organizations) Entitlements(ctx context.Context, organization string) ( return e, nil } -// RunQueueOptions represents the options for showing the queue. -type RunQueueOptions struct { - ListOptions -} - -// RunQueue shows the current run queue of an organization. -func (s *organizations) RunQueue(ctx context.Context, organization string, options RunQueueOptions) (*RunQueue, error) { +// ReadRunQueue shows the current run queue of an organization. +func (s *organizations) ReadRunQueue(ctx context.Context, organization string, options ReadRunQueueOptions) (*RunQueue, error) { if !validStringID(&organization) { return nil, ErrInvalidOrg } @@ -365,3 +353,16 @@ func (s *organizations) RunQueue(ctx context.Context, organization string, optio return rq, nil } + +func (o OrganizationCreateOptions) valid() error { + if !validString(o.Name) { + return ErrRequiredName + } + if !validStringID(o.Name) { + return ErrInvalidName + } + if !validString(o.Email) { + return ErrRequiredEmail + } + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/organization_membership.go b/vendor/github.com/hashicorp/go-tfe/organization_membership.go index 63c6bfe3..aab19eea 100644 --- a/vendor/github.com/hashicorp/go-tfe/organization_membership.go +++ b/vendor/github.com/hashicorp/go-tfe/organization_membership.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" ) @@ -17,7 +16,7 @@ var _ OrganizationMemberships = (*organizationMemberships)(nil) // https://www.terraform.io/docs/cloud/api/organization-memberships.html type OrganizationMemberships interface { // List all the organization memberships of the given organization. - List(ctx context.Context, organization string, options OrganizationMembershipListOptions) (*OrganizationMembershipList, error) + List(ctx context.Context, organization string, options *OrganizationMembershipListOptions) (*OrganizationMembershipList, error) // Create a new organization membership with the given options. Create(ctx context.Context, organization string, options OrganizationMembershipCreateOptions) (*OrganizationMembership, error) @@ -40,10 +39,9 @@ type organizationMemberships struct { // OrganizationMembershipStatus represents an organization membership status. type OrganizationMembershipStatus string -// List all available organization membership statuses. const ( - OrganizationMembershipActive = "active" - OrganizationMembershipInvited = "invited" + OrganizationMembershipActive OrganizationMembershipStatus = "active" + OrganizationMembershipInvited OrganizationMembershipStatus = "invited" ) // OrganizationMembershipList represents a list of organization memberships. @@ -64,21 +62,56 @@ type OrganizationMembership struct { Teams []*Team `jsonapi:"relation,teams"` } +// OrgMembershipIncludeOpt represents the available options for include query params. +// https://www.terraform.io/cloud-docs/api-docs/organization-memberships#available-related-resources +type OrgMembershipIncludeOpt string + +const ( + OrgMembershipUser OrgMembershipIncludeOpt = "user" + OrgMembershipTeam OrgMembershipIncludeOpt = "teams" +) + // OrganizationMembershipListOptions represents the options for listing organization memberships. type OrganizationMembershipListOptions struct { ListOptions + // Optional: A list of relations to include. See available resources + // https://www.terraform.io/cloud-docs/api-docs/organization-memberships#available-related-resources + Include []OrgMembershipIncludeOpt `url:"include,omitempty"` + + // Optional: A list of organization member emails to filter by. + Emails []string `url:"filter[email],omitempty"` +} + +// OrganizationMembershipCreateOptions represents the options for creating an organization membership. +type OrganizationMembershipCreateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,organization-memberships"` + + // Required: User's email address. + Email *string `jsonapi:"attr,email"` +} - Include string `url:"include"` +// OrganizationMembershipReadOptions represents the options for reading organization memberships. +type OrganizationMembershipReadOptions struct { + // Optional: A list of relations to include. See available resources + // https://www.terraform.io/cloud-docs/api-docs/organization-memberships#available-related-resources + Include []OrgMembershipIncludeOpt `url:"include,omitempty"` } // List all the organization memberships of the given organization. -func (s *organizationMemberships) List(ctx context.Context, organization string, options OrganizationMembershipListOptions) (*OrganizationMembershipList, error) { +func (s *organizationMemberships) List(ctx context.Context, organization string, options *OrganizationMembershipListOptions) (*OrganizationMembershipList, error) { if !validStringID(&organization) { return nil, ErrInvalidOrg } + if err := options.valid(); err != nil { + return nil, err + } u := fmt.Sprintf("organizations/%s/organization-memberships", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) + req, err := s.client.newRequest("GET", u, options) if err != nil { return nil, err } @@ -92,25 +125,6 @@ func (s *organizationMemberships) List(ctx context.Context, organization string, return ml, nil } -// OrganizationMembershipCreateOptions represents the options for creating an organization membership. -type OrganizationMembershipCreateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,organization-memberships"` - - // User's email address. - Email *string `jsonapi:"attr,email"` -} - -func (o OrganizationMembershipCreateOptions) valid() error { - if o.Email == nil { - return errors.New("email is required") - } - return nil -} - // Create an organization membership with the given options. func (s *organizationMemberships) Create(ctx context.Context, organization string, options OrganizationMembershipCreateOptions) (*OrganizationMembership, error) { if !validStringID(&organization) { @@ -140,15 +154,13 @@ func (s *organizationMemberships) Read(ctx context.Context, organizationMembersh return s.ReadWithOptions(ctx, organizationMembershipID, OrganizationMembershipReadOptions{}) } -// OrganizationMembershipReadOptions represents the options for reading organization memberships. -type OrganizationMembershipReadOptions struct { - Include string `url:"include"` -} - // Read an organization membership by ID with options func (s *organizationMemberships) ReadWithOptions(ctx context.Context, organizationMembershipID string, options OrganizationMembershipReadOptions) (*OrganizationMembership, error) { if !validStringID(&organizationMembershipID) { - return nil, errors.New("invalid value for membership") + return nil, ErrInvalidMembership + } + if err := options.valid(); err != nil { + return nil, err } u := fmt.Sprintf("organization-memberships/%s", url.QueryEscape(organizationMembershipID)) @@ -169,7 +181,7 @@ func (s *organizationMemberships) ReadWithOptions(ctx context.Context, organizat // Delete an organization membership by its ID. func (s *organizationMemberships) Delete(ctx context.Context, organizationMembershipID string) error { if !validStringID(&organizationMembershipID) { - return errors.New("invalid value for membership") + return ErrInvalidMembership } u := fmt.Sprintf("organization-memberships/%s", url.QueryEscape(organizationMembershipID)) @@ -180,3 +192,57 @@ func (s *organizationMemberships) Delete(ctx context.Context, organizationMember return s.client.do(ctx, req, nil) } + +func (o OrganizationMembershipCreateOptions) valid() error { + if o.Email == nil { + return ErrRequiredEmail + } + return nil +} + +func (o *OrganizationMembershipListOptions) valid() error { + if o == nil { + return nil + } + + if err := validateOrgMembershipIncludeParams(o.Include); err != nil { + return err + } + + if err := validateOrgMembershipEmailParams(o.Emails); err != nil { + return err + } + + return nil +} + +func (o OrganizationMembershipReadOptions) valid() error { + if err := validateOrgMembershipIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func validateOrgMembershipIncludeParams(params []OrgMembershipIncludeOpt) error { + for _, p := range params { + switch p { + case OrgMembershipUser, OrgMembershipTeam: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil +} + +func validateOrgMembershipEmailParams(emails []string) error { + for _, email := range emails { + if !validEmail(email) { + return ErrInvalidEmail + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/organization_tags.go b/vendor/github.com/hashicorp/go-tfe/organization_tags.go index faff4f73..bbf756f7 100644 --- a/vendor/github.com/hashicorp/go-tfe/organization_tags.go +++ b/vendor/github.com/hashicorp/go-tfe/organization_tags.go @@ -9,9 +9,13 @@ import ( var _ OrganizationTags = (*organizationTags)(nil) +// OrganizationMemberships describes all the list of tags used with all resources across the organization. +// +// TFE API docs: +// https://www.terraform.io/cloud-docs/api-docs/organization-tags type OrganizationTags interface { // List all tags within an organization - List(ctx context.Context, organization string, options OrganizationTagsListOptions) (*OrganizationTagsList, error) + List(ctx context.Context, organization string, options *OrganizationTagsListOptions) (*OrganizationTagsList, error) // Delete tags from an organization Delete(ctx context.Context, organization string, options OrganizationTagsDeleteOptions) error @@ -20,6 +24,7 @@ type OrganizationTags interface { AddWorkspaces(ctx context.Context, tag string, options AddWorkspacesToTagOptions) error } +// organizationTags implements OrganizationTags. type organizationTags struct { client *Client } @@ -32,10 +37,11 @@ type OrganizationTagsList struct { // OrganizationTag represents a Terraform Enterprise Organization tag type OrganizationTag struct { - ID string `jsonapi:"primary,tags"` + ID string `jsonapi:"primary,tags"` + // Optional: Name string `jsonapi:"attr,name,omitempty"` - // Number of workspaces that have this tag + // Optional: Number of workspaces that have this tag InstanceCount int `jsonapi:"attr,instance-count,omitempty"` // The org this tag belongs to @@ -45,18 +51,38 @@ type OrganizationTag struct { // OrganizationTagsListOptions represents the options for listing organization tags type OrganizationTagsListOptions struct { ListOptions + // Optional: + Filter string `url:"filter[exclude][taggable][id],omitempty"` +} + +// OrganizationTagsDeleteOptions represents the request body for deleting a tag in an organization +type OrganizationTagsDeleteOptions struct { + IDs []string // Required +} + +// AddWorkspacesToTagOptions represents the request body to add a workspace to a tag +type AddWorkspacesToTagOptions struct { + WorkspaceIDs []string // Required +} + +// this represents a single tag ID +type tagID struct { + ID string `jsonapi:"primary,tags"` +} - Filter *string `url:"filter[exclude][taggable][id],omitempty"` +// this represents a single workspace ID +type workspaceID struct { + ID string `jsonapi:"primary,workspaces"` } // List all the tags in an organization. You can provide query params through OrganizationTagsListOptions -func (s *organizationTags) List(ctx context.Context, organization string, options OrganizationTagsListOptions) (*OrganizationTagsList, error) { +func (s *organizationTags) List(ctx context.Context, organization string, options *OrganizationTagsListOptions) (*OrganizationTagsList, error) { if !validStringID(&organization) { return nil, ErrInvalidOrg } u := fmt.Sprintf("organizations/%s/tags", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) + req, err := s.client.newRequest("GET", u, options) if err != nil { return nil, err } @@ -70,31 +96,6 @@ func (s *organizationTags) List(ctx context.Context, organization string, option return tags, nil } -// OrganizationTagsDeleteOptions represents the request body for deleting a tag in an organization -type OrganizationTagsDeleteOptions struct { - IDs []string -} - -// this represents a single tag ID sent over the wire -type tagID struct { - ID string `jsonapi:"primary,tags"` -} - -func (opts *OrganizationTagsDeleteOptions) valid() error { - if opts.IDs == nil || len(opts.IDs) == 0 { - return errors.New("you must specify at least one tag id to remove") - } - - for _, id := range opts.IDs { - if !validStringID(&id) { - errorMsg := fmt.Sprintf("%s is not a valid id value", id) - return errors.New(errorMsg) - } - } - - return nil -} - // Delete tags from a Terraform Enterprise organization func (s *organizationTags) Delete(ctx context.Context, organization string, options OrganizationTagsDeleteOptions) error { if !validStringID(&organization) { @@ -119,35 +120,10 @@ func (s *organizationTags) Delete(ctx context.Context, organization string, opti return s.client.do(ctx, req, nil) } -// AddWorkspacesToTagOptions represents the request body to add a workspace to a tag -type AddWorkspacesToTagOptions struct { - WorkspaceIDs []string -} - -func (w *AddWorkspacesToTagOptions) valid() error { - if w.WorkspaceIDs == nil || len(w.WorkspaceIDs) == 0 { - return errors.New("you must specify at least one workspace to add tag to") - } - - for _, id := range w.WorkspaceIDs { - if !validStringID(&id) { - errorMsg := fmt.Sprintf("%s is not a valid id value", id) - return errors.New(errorMsg) - } - } - - return nil -} - -// this represents how workspace IDs will be sent over the wire -type workspaceID struct { - ID string `jsonapi:"primary,workspaces"` -} - // Add workspaces to a tag func (s *organizationTags) AddWorkspaces(ctx context.Context, tag string, options AddWorkspacesToTagOptions) error { if !validStringID(&tag) { - return errors.New("invalid tag id") + return ErrInvalidTag } if err := options.valid(); err != nil { @@ -167,3 +143,33 @@ func (s *organizationTags) AddWorkspaces(ctx context.Context, tag string, option return s.client.do(ctx, req, nil) } + +func (opts *OrganizationTagsDeleteOptions) valid() error { + if opts.IDs == nil || len(opts.IDs) == 0 { + return ErrRequiredTagID + } + + for _, id := range opts.IDs { + if !validStringID(&id) { + errorMsg := fmt.Sprintf("%s is not a valid id value", id) + return errors.New(errorMsg) + } + } + + return nil +} + +func (w *AddWorkspacesToTagOptions) valid() error { + if w.WorkspaceIDs == nil || len(w.WorkspaceIDs) == 0 { + return ErrRequiredTagWorkspaceID + } + + for _, id := range w.WorkspaceIDs { + if !validStringID(&id) { + errorMsg := fmt.Sprintf("%s is not a valid id value", id) + return errors.New(errorMsg) + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/organization_token.go b/vendor/github.com/hashicorp/go-tfe/organization_token.go index 3b8f8ba9..263f38cf 100644 --- a/vendor/github.com/hashicorp/go-tfe/organization_token.go +++ b/vendor/github.com/hashicorp/go-tfe/organization_token.go @@ -16,8 +16,8 @@ var _ OrganizationTokens = (*organizationTokens)(nil) // TFE API docs: // https://www.terraform.io/docs/cloud/api/organization-tokens.html type OrganizationTokens interface { - // Generate a new organization token, replacing any existing token. - Generate(ctx context.Context, organization string) (*OrganizationToken, error) + // Create a new organization token, replacing any existing token. + Create(ctx context.Context, organization string) (*OrganizationToken, error) // Read an organization token. Read(ctx context.Context, organization string) (*OrganizationToken, error) @@ -40,8 +40,8 @@ type OrganizationToken struct { Token string `jsonapi:"attr,token"` } -// Generate a new organization token, replacing any existing token. -func (s *organizationTokens) Generate(ctx context.Context, organization string) (*OrganizationToken, error) { +// Create a new organization token, replacing any existing token. +func (s *organizationTokens) Create(ctx context.Context, organization string) (*OrganizationToken, error) { if !validStringID(&organization) { return nil, ErrInvalidOrg } diff --git a/vendor/github.com/hashicorp/go-tfe/plan.go b/vendor/github.com/hashicorp/go-tfe/plan.go index 011d7f7d..67674be8 100644 --- a/vendor/github.com/hashicorp/go-tfe/plan.go +++ b/vendor/github.com/hashicorp/go-tfe/plan.go @@ -3,7 +3,6 @@ package tfe import ( "bytes" "context" - "errors" "fmt" "io" "net/url" @@ -16,7 +15,7 @@ var _ Plans = (*plans)(nil) // Plans describes all the plan related methods that the Terraform Enterprise // API supports. // -// TFE API docs: https://www.terraform.io/docs/cloud/api/plan.html +// TFE API docs: https://www.terraform.io/cloud-docs/api-docs/plans type Plans interface { // Read a plan by its ID. Read(ctx context.Context, planID string) (*Plan, error) @@ -25,7 +24,7 @@ type Plans interface { Logs(ctx context.Context, planID string) (io.Reader, error) // Retrieve the JSON execution plan - JSONOutput(ctx context.Context, planID string) ([]byte, error) + ReadJSONOutput(ctx context.Context, planID string) ([]byte, error) } // plans implements Plans. @@ -36,7 +35,7 @@ type plans struct { // PlanStatus represents a plan state. type PlanStatus string -//List all available plan statuses. +// List all available plan statuses. const ( PlanCanceled PlanStatus = "canceled" PlanCreated PlanStatus = "created" @@ -77,7 +76,7 @@ type PlanStatusTimestamps struct { // Read a plan by its ID. func (s *plans) Read(ctx context.Context, planID string) (*Plan, error) { if !validStringID(&planID) { - return nil, errors.New("invalid value for plan ID") + return nil, ErrInvalidPlanID } u := fmt.Sprintf("plans/%s", url.QueryEscape(planID)) @@ -98,7 +97,7 @@ func (s *plans) Read(ctx context.Context, planID string) (*Plan, error) { // Logs retrieves the logs of a plan. func (s *plans) Logs(ctx context.Context, planID string) (io.Reader, error) { if !validStringID(&planID) { - return nil, errors.New("invalid value for plan ID") + return nil, ErrInvalidPlanID } // Get the plan to make sure it exists. @@ -114,7 +113,7 @@ func (s *plans) Logs(ctx context.Context, planID string) (io.Reader, error) { u, err := url.Parse(p.LogReadURL) if err != nil { - return nil, fmt.Errorf("invalid log URL: %v", err) + return nil, fmt.Errorf("invalid log URL: %w", err) } done := func() (bool, error) { @@ -140,9 +139,9 @@ func (s *plans) Logs(ctx context.Context, planID string) (io.Reader, error) { } // Retrieve the JSON execution plan -func (s *plans) JSONOutput(ctx context.Context, planID string) ([]byte, error) { +func (s *plans) ReadJSONOutput(ctx context.Context, planID string) ([]byte, error) { if !validStringID(&planID) { - return nil, errors.New("invalid value for plan ID") + return nil, ErrInvalidPlanID } u := fmt.Sprintf("plans/%s/json-output", url.QueryEscape(planID)) diff --git a/vendor/github.com/hashicorp/go-tfe/plan_export.go b/vendor/github.com/hashicorp/go-tfe/plan_export.go index 4d4ed05d..35dc907b 100644 --- a/vendor/github.com/hashicorp/go-tfe/plan_export.go +++ b/vendor/github.com/hashicorp/go-tfe/plan_export.go @@ -3,7 +3,6 @@ package tfe import ( "bytes" "context" - "errors" "fmt" "net/url" "time" @@ -81,23 +80,14 @@ type PlanExportCreateOptions struct { // https://jsonapi.org/format/#crud-creating Type string `jsonapi:"primary,plan-exports"` - // The plan to export. + // Required: The plan to export. Plan *Plan `jsonapi:"relation,plan"` - // The name of the policy set. + // Required: The name of the policy set. DataType *PlanExportDataType `jsonapi:"attr,data-type"` } -func (o PlanExportCreateOptions) valid() error { - if o.Plan == nil { - return errors.New("plan is required") - } - if o.DataType == nil { - return errors.New("data type is required") - } - return nil -} - +// Create a plan export func (s *planExports) Create(ctx context.Context, options PlanExportCreateOptions) (*PlanExport, error) { if err := options.valid(); err != nil { return nil, err @@ -120,7 +110,7 @@ func (s *planExports) Create(ctx context.Context, options PlanExportCreateOption // Read a plan export by its ID. func (s *planExports) Read(ctx context.Context, planExportID string) (*PlanExport, error) { if !validStringID(&planExportID) { - return nil, errors.New("invalid value for plan export ID") + return nil, ErrInvalidPlanExportID } u := fmt.Sprintf("plan-exports/%s", url.QueryEscape(planExportID)) @@ -141,7 +131,7 @@ func (s *planExports) Read(ctx context.Context, planExportID string) (*PlanExpor // Delete a plan export by ID. func (s *planExports) Delete(ctx context.Context, planExportID string) error { if !validStringID(&planExportID) { - return errors.New("invalid value for plan export ID") + return ErrInvalidPlanExportID } u := fmt.Sprintf("plan-exports/%s", url.QueryEscape(planExportID)) @@ -156,7 +146,7 @@ func (s *planExports) Delete(ctx context.Context, planExportID string) error { // Download a plan export's data. Data is exported in a .tar.gz format. func (s *planExports) Download(ctx context.Context, planExportID string) ([]byte, error) { if !validStringID(&planExportID) { - return nil, errors.New("invalid value for plan export ID") + return nil, ErrInvalidPlanExportID } u := fmt.Sprintf("plan-exports/%s/download", url.QueryEscape(planExportID)) @@ -173,3 +163,13 @@ func (s *planExports) Download(ctx context.Context, planExportID string) ([]byte return buf.Bytes(), nil } + +func (o PlanExportCreateOptions) valid() error { + if o.Plan == nil { + return ErrRequiredPlan + } + if o.DataType == nil { + return ErrRequiredDataType + } + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/policy.go b/vendor/github.com/hashicorp/go-tfe/policy.go index 16aceb11..49a293a5 100644 --- a/vendor/github.com/hashicorp/go-tfe/policy.go +++ b/vendor/github.com/hashicorp/go-tfe/policy.go @@ -3,7 +3,6 @@ package tfe import ( "bytes" "context" - "errors" "fmt" "net/url" "time" @@ -18,7 +17,7 @@ var _ Policies = (*policies)(nil) // TFE API docs: https://www.terraform.io/docs/cloud/api/policies.html type Policies interface { // List all the policies for a given organization - List(ctx context.Context, organization string, options PolicyListOptions) (*PolicyList, error) + List(ctx context.Context, organization string, options *PolicyListOptions) (*PolicyList, error) // Create a policy and associate it with an organization. Create(ctx context.Context, organization string, options PolicyCreateOptions) (*Policy, error) @@ -79,33 +78,18 @@ type Enforcement struct { Mode EnforcementLevel `jsonapi:"attr,mode"` } +// EnforcementOptions represents the enforcement options of a policy. +type EnforcementOptions struct { + Path *string `json:"path"` + Mode *EnforcementLevel `json:"mode"` +} + // PolicyListOptions represents the options for listing policies. type PolicyListOptions struct { ListOptions - // A search string (partial policy name) used to filter the results. - Search *string `url:"search[name],omitempty"` -} - -// List all the policies for a given organization -func (s *policies) List(ctx context.Context, organization string, options PolicyListOptions) (*PolicyList, error) { - if !validStringID(&organization) { - return nil, ErrInvalidOrg - } - - u := fmt.Sprintf("organizations/%s/policies", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - pl := &PolicyList{} - err = s.client.do(ctx, req, pl) - if err != nil { - return nil, err - } - - return pl, nil + // Optional: A search string (partial policy name) used to filter the results. + Search string `url:"search[name],omitempty"` } // PolicyCreateOptions represents the options for creating a new policy. @@ -116,41 +100,50 @@ type PolicyCreateOptions struct { // https://jsonapi.org/format/#crud-creating Type string `jsonapi:"primary,policies"` - // The name of the policy. + // Required: The name of the policy. Name *string `jsonapi:"attr,name"` - // A description of the policy's purpose. + // Optional: A description of the policy's purpose. Description *string `jsonapi:"attr,description,omitempty"` - // The enforcements of the policy. + // Required: The enforcements of the policy. Enforce []*EnforcementOptions `jsonapi:"attr,enforce"` } -// EnforcementOptions represents the enforcement options of a policy. -type EnforcementOptions struct { - Path *string `json:"path,omitempty"` - Mode *EnforcementLevel `json:"mode"` +// PolicyUpdateOptions represents the options for updating a policy. +type PolicyUpdateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,policies"` + + // Optional: A description of the policy's purpose. + Description *string `jsonapi:"attr,description,omitempty"` + + // Optional: The enforcements of the policy. + Enforce []*EnforcementOptions `jsonapi:"attr,enforce,omitempty"` } -func (o PolicyCreateOptions) valid() error { - if !validString(o.Name) { - return ErrRequiredName - } - if !validStringID(o.Name) { - return ErrInvalidName +// List all the policies for a given organization +func (s *policies) List(ctx context.Context, organization string, options *PolicyListOptions) (*PolicyList, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg } - if o.Enforce == nil { - return errors.New("enforce is required") + + u := fmt.Sprintf("organizations/%s/policies", url.QueryEscape(organization)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err } - for _, e := range o.Enforce { - if !validString(e.Path) { - return errors.New("enforcement path is required") - } - if e.Mode == nil { - return errors.New("enforcement mode is required") - } + + pl := &PolicyList{} + err = s.client.do(ctx, req, pl) + if err != nil { + return nil, err } - return nil + + return pl, nil } // Create a policy and associate it with an organization. @@ -180,7 +173,7 @@ func (s *policies) Create(ctx context.Context, organization string, options Poli // Read a policy by its ID. func (s *policies) Read(ctx context.Context, policyID string) (*Policy, error) { if !validStringID(&policyID) { - return nil, errors.New("invalid value for policy ID") + return nil, ErrInvalidPolicyID } u := fmt.Sprintf("policies/%s", url.QueryEscape(policyID)) @@ -198,25 +191,10 @@ func (s *policies) Read(ctx context.Context, policyID string) (*Policy, error) { return p, err } -// PolicyUpdateOptions represents the options for updating a policy. -type PolicyUpdateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,policies"` - - // A description of the policy's purpose. - Description *string `jsonapi:"attr,description,omitempty"` - - // The enforcements of the policy. - Enforce []*EnforcementOptions `jsonapi:"attr,enforce,omitempty"` -} - // Update an existing policy. func (s *policies) Update(ctx context.Context, policyID string, options PolicyUpdateOptions) (*Policy, error) { if !validStringID(&policyID) { - return nil, errors.New("invalid value for policy ID") + return nil, ErrInvalidPolicyID } u := fmt.Sprintf("policies/%s", url.QueryEscape(policyID)) @@ -237,7 +215,7 @@ func (s *policies) Update(ctx context.Context, policyID string, options PolicyUp // Delete a policy by its ID. func (s *policies) Delete(ctx context.Context, policyID string) error { if !validStringID(&policyID) { - return errors.New("invalid value for policy ID") + return ErrInvalidPolicyID } u := fmt.Sprintf("policies/%s", url.QueryEscape(policyID)) @@ -252,7 +230,7 @@ func (s *policies) Delete(ctx context.Context, policyID string) error { // Upload the policy content of the policy. func (s *policies) Upload(ctx context.Context, policyID string, content []byte) error { if !validStringID(&policyID) { - return errors.New("invalid value for policy ID") + return ErrInvalidPolicyID } u := fmt.Sprintf("policies/%s/upload", url.QueryEscape(policyID)) @@ -267,7 +245,7 @@ func (s *policies) Upload(ctx context.Context, policyID string, content []byte) // Download the policy content of the policy. func (s *policies) Download(ctx context.Context, policyID string) ([]byte, error) { if !validStringID(&policyID) { - return nil, errors.New("invalid value for policy ID") + return nil, ErrInvalidPolicyID } u := fmt.Sprintf("policies/%s/download", url.QueryEscape(policyID)) @@ -284,3 +262,24 @@ func (s *policies) Download(ctx context.Context, policyID string) ([]byte, error return buf.Bytes(), nil } + +func (o PolicyCreateOptions) valid() error { + if !validString(o.Name) { + return ErrRequiredName + } + if !validStringID(o.Name) { + return ErrInvalidName + } + if o.Enforce == nil { + return ErrRequiredEnforce + } + for _, e := range o.Enforce { + if !validString(e.Path) { + return ErrRequiredEnforcementPath + } + if e.Mode == nil { + return ErrRequiredEnforcementMode + } + } + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/policy_check.go b/vendor/github.com/hashicorp/go-tfe/policy_check.go index 7f201aca..0a1dea11 100644 --- a/vendor/github.com/hashicorp/go-tfe/policy_check.go +++ b/vendor/github.com/hashicorp/go-tfe/policy_check.go @@ -3,7 +3,6 @@ package tfe import ( "bytes" "context" - "errors" "fmt" "io" "net/url" @@ -20,7 +19,7 @@ var _ PolicyChecks = (*policyChecks)(nil) // https://www.terraform.io/docs/cloud/api/policy-checks.html type PolicyChecks interface { // List all policy checks of the given run. - List(ctx context.Context, runID string, options PolicyCheckListOptions) (*PolicyCheckList, error) + List(ctx context.Context, runID string, options *PolicyCheckListOptions) (*PolicyCheckList, error) // Read a policy check by its ID. Read(ctx context.Context, policyCheckID string) (*PolicyCheck, error) @@ -49,7 +48,7 @@ const ( // PolicyStatus represents a policy check state. type PolicyStatus string -//List all available policy check statuses. +// List all available policy check statuses. const ( PolicyCanceled PolicyStatus = "canceled" PolicyErrored PolicyStatus = "errored" @@ -111,19 +110,35 @@ type PolicyStatusTimestamps struct { SoftFailedAt time.Time `jsonapi:"attr,soft-failed-at,rfc3339"` } +// A list of relations to include +// https://www.terraform.io/cloud-docs/api-docs/policy-checks#available-related-resources +type PolicyCheckIncludeOpt string + +const ( + PolicyCheckRunWorkspace PolicyCheckIncludeOpt = "run.workspace" + PolicyCheckRun PolicyCheckIncludeOpt = "run" +) + // PolicyCheckListOptions represents the options for listing policy checks. type PolicyCheckListOptions struct { ListOptions + + // Optional: A list of relations to include. See available resources + // https://www.terraform.io/cloud-docs/api-docs/policy-checks#available-related-resources + Include []PolicyCheckIncludeOpt `url:"include,omitempty"` } // List all policy checks of the given run. -func (s *policyChecks) List(ctx context.Context, runID string, options PolicyCheckListOptions) (*PolicyCheckList, error) { +func (s *policyChecks) List(ctx context.Context, runID string, options *PolicyCheckListOptions) (*PolicyCheckList, error) { if !validStringID(&runID) { return nil, ErrInvalidRunID } + if err := options.valid(); err != nil { + return nil, err + } u := fmt.Sprintf("runs/%s/policy-checks", url.QueryEscape(runID)) - req, err := s.client.newRequest("GET", u, &options) + req, err := s.client.newRequest("GET", u, options) if err != nil { return nil, err } @@ -140,7 +155,7 @@ func (s *policyChecks) List(ctx context.Context, runID string, options PolicyChe // Read a policy check by its ID. func (s *policyChecks) Read(ctx context.Context, policyCheckID string) (*PolicyCheck, error) { if !validStringID(&policyCheckID) { - return nil, errors.New("invalid value for policy check ID") + return nil, ErrInvalidPolicyCheckID } u := fmt.Sprintf("policy-checks/%s", url.QueryEscape(policyCheckID)) @@ -161,7 +176,7 @@ func (s *policyChecks) Read(ctx context.Context, policyCheckID string) (*PolicyC // Override a soft-mandatory or warning policy. func (s *policyChecks) Override(ctx context.Context, policyCheckID string) (*PolicyCheck, error) { if !validStringID(&policyCheckID) { - return nil, errors.New("invalid value for policy check ID") + return nil, ErrInvalidPolicyCheckID } u := fmt.Sprintf("policy-checks/%s/actions/override", url.QueryEscape(policyCheckID)) @@ -182,7 +197,7 @@ func (s *policyChecks) Override(ctx context.Context, policyCheckID string) (*Pol // Logs retrieves the logs of a policy check. func (s *policyChecks) Logs(ctx context.Context, policyCheckID string) (io.Reader, error) { if !validStringID(&policyCheckID) { - return nil, errors.New("invalid value for policy check ID") + return nil, ErrInvalidPolicyCheckID } // Loop until the context is canceled or the policy check is finished @@ -219,3 +234,28 @@ func (s *policyChecks) Logs(ctx context.Context, policyCheckID string) (io.Reade return logs, nil } } + +func (o *PolicyCheckListOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validatePolicyCheckIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func validatePolicyCheckIncludeParams(params []PolicyCheckIncludeOpt) error { + for _, p := range params { + switch p { + case PolicyCheckRunWorkspace, PolicyCheckRun: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/policy_set.go b/vendor/github.com/hashicorp/go-tfe/policy_set.go index a922e437..ff0e021a 100644 --- a/vendor/github.com/hashicorp/go-tfe/policy_set.go +++ b/vendor/github.com/hashicorp/go-tfe/policy_set.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" "time" @@ -14,10 +13,10 @@ var _ PolicySets = (*policySets)(nil) // PolicySets describes all the policy set related methods that the Terraform // Enterprise API supports. // -// TFE API docs: https://www.terraform.io/docs/cloud/api/policies.html +// TFE API docs: https://www.terraform.io/docs/cloud/api/policy-sets.html type PolicySets interface { // List all the policy sets for a given organization. - List(ctx context.Context, organization string, options PolicySetListOptions) (*PolicySetList, error) + List(ctx context.Context, organization string, options *PolicySetListOptions) (*PolicySetList, error) // Create a policy set and associate it with an organization. Create(ctx context.Context, organization string, options PolicySetCreateOptions) (*PolicySet, error) @@ -88,33 +87,32 @@ type PolicySet struct { CurrentVersion *PolicySetVersion `jsonapi:"relation,current-version"` } +// PolicySetIncludeOpt represents the available options for include query params. +// https://www.terraform.io/cloud-docs/api-docs/policy-sets#available-related-resources +type PolicySetIncludeOpt string + +const ( + PolicySetPolicies PolicySetIncludeOpt = "policies" + PolicySetWorkspaces PolicySetIncludeOpt = "workspaces" + PolicySetCurrentVersion PolicySetIncludeOpt = "current_version" + PolicySetNewestVersion PolicySetIncludeOpt = "newest_version" +) + // PolicySetListOptions represents the options for listing policy sets. type PolicySetListOptions struct { ListOptions - // A search string (partial policy set name) used to filter the results. - Search *string `url:"search[name],omitempty"` + // Optional: A search string (partial policy set name) used to filter the results. + Search string `url:"search[name],omitempty"` } -// List all the policies for a given organization. -func (s *policySets) List(ctx context.Context, organization string, options PolicySetListOptions) (*PolicySetList, error) { - if !validStringID(&organization) { - return nil, ErrInvalidOrg - } - - u := fmt.Sprintf("organizations/%s/policy-sets", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - psl := &PolicySetList{} - err = s.client.do(ctx, req, psl) - if err != nil { - return nil, err - } - - return psl, nil +// PolicySetReadOptions are read options. +// For a full list of relations, please see: +// https://www.terraform.io/docs/cloud/api/policy-sets.html#relationships +type PolicySetReadOptions struct { + // Optional: A list of relations to include. See available resources + // https://www.terraform.io/cloud-docs/api-docs/policy-sets#available-related-resources + Include []PolicySetIncludeOpt `url:"include,omitempty"` } // PolicySetCreateOptions represents the options for creating a new policy set. @@ -125,42 +123,112 @@ type PolicySetCreateOptions struct { // https://jsonapi.org/format/#crud-creating Type string `jsonapi:"primary,policy-sets"` - // The name of the policy set. + // Required: The name of the policy set. Name *string `jsonapi:"attr,name"` - // The description of the policy set. + // Optional: The description of the policy set. Description *string `jsonapi:"attr,description,omitempty"` - // Whether or not the policy set is global. + // Optional: Whether or not the policy set is global. Global *bool `jsonapi:"attr,global,omitempty"` - // The sub-path within the attached VCS repository to ingress. All + // Optional: The sub-path within the attached VCS repository to ingress. All // files and directories outside of this sub-path will be ignored. // This option may only be specified when a VCS repo is present. PoliciesPath *string `jsonapi:"attr,policies-path,omitempty"` - // The initial members of the policy set. + // Optional: The initial members of the policy set. Policies []*Policy `jsonapi:"relation,policies,omitempty"` - // VCS repository information. When present, the policies and + // Optional: VCS repository information. When present, the policies and // configuration will be sourced from the specified VCS repository // instead of being defined within the policy set itself. Note that // this option is mutually exclusive with the Policies option and // both cannot be used at the same time. VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"` - // The initial list of workspaces for which the policy set should be enforced. + // Optional: The initial list of workspaces for which the policy set should be enforced. Workspaces []*Workspace `jsonapi:"relation,workspaces,omitempty"` } -func (o PolicySetCreateOptions) valid() error { - if !validString(o.Name) { - return ErrRequiredName +// PolicySetUpdateOptions represents the options for updating a policy set. +type PolicySetUpdateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,policy-sets"` + + // Optional: The name of the policy set. + Name *string `jsonapi:"attr,name,omitempty"` + + // Optional: The description of the policy set. + Description *string `jsonapi:"attr,description,omitempty"` + + // Optional: Whether or not the policy set is global. + Global *bool `jsonapi:"attr,global,omitempty"` + + // Optional: The sub-path within the attached VCS repository to ingress. All + // files and directories outside of this sub-path will be ignored. + // This option may only be specified when a VCS repo is present. + PoliciesPath *string `jsonapi:"attr,policies-path,omitempty"` + + // Optional: VCS repository information. When present, the policies and + // configuration will be sourced from the specified VCS repository + // instead of being defined within the policy set itself. Note that + // specifying this option may only be used on policy sets with no + // directly-attached policies (*PolicySet.Policies). Specifying this + // option when policies are already present will result in an error. + VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"` +} + +// PolicySetAddPoliciesOptions represents the options for adding policies +// to a policy set. +type PolicySetAddPoliciesOptions struct { + // The policies to add to the policy set. + Policies []*Policy +} + +// PolicySetRemovePoliciesOptions represents the options for removing +// policies from a policy set. +type PolicySetRemovePoliciesOptions struct { + // The policies to remove from the policy set. + Policies []*Policy +} + +// PolicySetAddWorkspacesOptions represents the options for adding workspaces +// to a policy set. +type PolicySetAddWorkspacesOptions struct { + // The workspaces to add to the policy set. + Workspaces []*Workspace +} + +// PolicySetRemoveWorkspacesOptions represents the options for removing +// workspaces from a policy set. +type PolicySetRemoveWorkspacesOptions struct { + // The workspaces to remove from the policy set. + Workspaces []*Workspace +} + +// List all the policies for a given organization. +func (s *policySets) List(ctx context.Context, organization string, options *PolicySetListOptions) (*PolicySetList, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg } - if !validStringID(o.Name) { - return ErrInvalidName + + u := fmt.Sprintf("organizations/%s/policy-sets", url.QueryEscape(organization)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err } - return nil + + psl := &PolicySetList{} + err = s.client.do(ctx, req, psl) + if err != nil { + return nil, err + } + + return psl, nil } // Create a policy set and associate it with an organization. @@ -187,13 +255,6 @@ func (s *policySets) Create(ctx context.Context, organization string, options Po return ps, err } -// PolicySetReadOptions are read options. -// For a full list of relations, please see: -// https://www.terraform.io/docs/cloud/api/policy-sets.html#relationships -type PolicySetReadOptions struct { - Include string `url:"include"` -} - // Read a policy set by its ID. func (s *policySets) Read(ctx context.Context, policySetID string) (*PolicySet, error) { return s.ReadWithOptions(ctx, policySetID, nil) @@ -202,7 +263,10 @@ func (s *policySets) Read(ctx context.Context, policySetID string) (*PolicySet, // ReadWithOptions reads a policy by its ID using the options supplied. func (s *policySets) ReadWithOptions(ctx context.Context, policySetID string, options *PolicySetReadOptions) (*PolicySet, error) { if !validStringID(&policySetID) { - return nil, errors.New("invalid value for policy set ID") + return nil, ErrInvalidPolicySetID + } + if err := options.valid(); err != nil { + return nil, err } u := fmt.Sprintf("policy-sets/%s", url.QueryEscape(policySetID)) @@ -220,48 +284,10 @@ func (s *policySets) ReadWithOptions(ctx context.Context, policySetID string, op return ps, err } -// PolicySetUpdateOptions represents the options for updating a policy set. -type PolicySetUpdateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,policy-sets"` - - /// The name of the policy set. - Name *string `jsonapi:"attr,name,omitempty"` - - // The description of the policy set. - Description *string `jsonapi:"attr,description,omitempty"` - - // Whether or not the policy set is global. - Global *bool `jsonapi:"attr,global,omitempty"` - - // The sub-path within the attached VCS repository to ingress. All - // files and directories outside of this sub-path will be ignored. - // This option may only be specified when a VCS repo is present. - PoliciesPath *string `jsonapi:"attr,policies-path,omitempty"` - - // VCS repository information. When present, the policies and - // configuration will be sourced from the specified VCS repository - // instead of being defined within the policy set itself. Note that - // specifying this option may only be used on policy sets with no - // directly-attached policies (*PolicySet.Policies). Specifying this - // option when policies are already present will result in an error. - VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"` -} - -func (o PolicySetUpdateOptions) valid() error { - if o.Name != nil && !validStringID(o.Name) { - return ErrInvalidName - } - return nil -} - // Update an existing policy set. func (s *policySets) Update(ctx context.Context, policySetID string, options PolicySetUpdateOptions) (*PolicySet, error) { if !validStringID(&policySetID) { - return nil, errors.New("invalid value for policy set ID") + return nil, ErrInvalidPolicySetID } if err := options.valid(); err != nil { return nil, err @@ -282,27 +308,10 @@ func (s *policySets) Update(ctx context.Context, policySetID string, options Pol return ps, err } -// PolicySetAddPoliciesOptions represents the options for adding policies -// to a policy set. -type PolicySetAddPoliciesOptions struct { - /// The policies to add to the policy set. - Policies []*Policy -} - -func (o PolicySetAddPoliciesOptions) valid() error { - if o.Policies == nil { - return errors.New("policies is required") - } - if len(o.Policies) == 0 { - return errors.New("must provide at least one policy") - } - return nil -} - -// Add policies to a policy set +// AddPolicies adds policies to a policy set func (s *policySets) AddPolicies(ctx context.Context, policySetID string, options PolicySetAddPoliciesOptions) error { if !validStringID(&policySetID) { - return errors.New("invalid value for policy set ID") + return ErrInvalidPolicySetID } if err := options.valid(); err != nil { return err @@ -317,27 +326,10 @@ func (s *policySets) AddPolicies(ctx context.Context, policySetID string, option return s.client.do(ctx, req, nil) } -// PolicySetRemovePoliciesOptions represents the options for removing -// policies from a policy set. -type PolicySetRemovePoliciesOptions struct { - /// The policies to remove from the policy set. - Policies []*Policy -} - -func (o PolicySetRemovePoliciesOptions) valid() error { - if o.Policies == nil { - return errors.New("policies is required") - } - if len(o.Policies) == 0 { - return errors.New("must provide at least one policy") - } - return nil -} - -// Remove policies from a policy set +// RemovePolicies remove policies from a policy set func (s *policySets) RemovePolicies(ctx context.Context, policySetID string, options PolicySetRemovePoliciesOptions) error { if !validStringID(&policySetID) { - return errors.New("invalid value for policy set ID") + return ErrInvalidPolicySetID } if err := options.valid(); err != nil { return err @@ -352,27 +344,10 @@ func (s *policySets) RemovePolicies(ctx context.Context, policySetID string, opt return s.client.do(ctx, req, nil) } -// PolicySetAddWorkspacesOptions represents the options for adding workspaces -// to a policy set. -type PolicySetAddWorkspacesOptions struct { - /// The workspaces to add to the policy set. - Workspaces []*Workspace -} - -func (o PolicySetAddWorkspacesOptions) valid() error { - if o.Workspaces == nil { - return errors.New("workspaces is required") - } - if len(o.Workspaces) == 0 { - return errors.New("must provide at least one workspace") - } - return nil -} - -// Add workspaces to a policy set. +// Addworkspaces adds workspaces to a policy set. func (s *policySets) AddWorkspaces(ctx context.Context, policySetID string, options PolicySetAddWorkspacesOptions) error { if !validStringID(&policySetID) { - return errors.New("invalid value for policy set ID") + return ErrInvalidPolicySetID } if err := options.valid(); err != nil { return err @@ -387,27 +362,10 @@ func (s *policySets) AddWorkspaces(ctx context.Context, policySetID string, opti return s.client.do(ctx, req, nil) } -// PolicySetRemoveWorkspacesOptions represents the options for removing -// workspaces from a policy set. -type PolicySetRemoveWorkspacesOptions struct { - /// The workspaces to remove from the policy set. - Workspaces []*Workspace -} - -func (o PolicySetRemoveWorkspacesOptions) valid() error { - if o.Workspaces == nil { - return errors.New("workspaces is required") - } - if len(o.Workspaces) == 0 { - return errors.New("must provide at least one workspace") - } - return nil -} - -// Remove workspaces from a policy set. +// RemoveWorkspaces removes workspaces from a policy set. func (s *policySets) RemoveWorkspaces(ctx context.Context, policySetID string, options PolicySetRemoveWorkspacesOptions) error { if !validStringID(&policySetID) { - return errors.New("invalid value for policy set ID") + return ErrInvalidPolicySetID } if err := options.valid(); err != nil { return err @@ -425,7 +383,7 @@ func (s *policySets) RemoveWorkspaces(ctx context.Context, policySetID string, o // Delete a policy set by its ID. func (s *policySets) Delete(ctx context.Context, policySetID string) error { if !validStringID(&policySetID) { - return errors.New("invalid value for policy set ID") + return ErrInvalidPolicySetID } u := fmt.Sprintf("policy-sets/%s", url.QueryEscape(policySetID)) @@ -436,3 +394,85 @@ func (s *policySets) Delete(ctx context.Context, policySetID string) error { return s.client.do(ctx, req, nil) } + +func (o PolicySetCreateOptions) valid() error { + if !validString(o.Name) { + return ErrRequiredName + } + if !validStringID(o.Name) { + return ErrInvalidName + } + return nil +} + +func (o PolicySetRemoveWorkspacesOptions) valid() error { + if o.Workspaces == nil { + return ErrWorkspacesRequired + } + if len(o.Workspaces) == 0 { + return ErrWorkspaceMinLimit + } + return nil +} + +func (o PolicySetUpdateOptions) valid() error { + if o.Name != nil && !validStringID(o.Name) { + return ErrInvalidName + } + return nil +} + +func (o PolicySetAddPoliciesOptions) valid() error { + if o.Policies == nil { + return ErrRequiredPolicies + } + if len(o.Policies) == 0 { + return ErrInvalidPolicies + } + return nil +} + +func (o PolicySetRemovePoliciesOptions) valid() error { + if o.Policies == nil { + return ErrRequiredPolicies + } + if len(o.Policies) == 0 { + return ErrInvalidPolicies + } + return nil +} + +func (o PolicySetAddWorkspacesOptions) valid() error { + if o.Workspaces == nil { + return ErrWorkspacesRequired + } + if len(o.Workspaces) == 0 { + return ErrWorkspaceMinLimit + } + return nil +} + +func (o *PolicySetReadOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validatePolicySetIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func validatePolicySetIncludeParams(params []PolicySetIncludeOpt) error { + for _, p := range params { + switch p { + case PolicySetPolicies, PolicySetWorkspaces, PolicySetCurrentVersion, PolicySetNewestVersion: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/policy_set_parameter.go b/vendor/github.com/hashicorp/go-tfe/policy_set_parameter.go index d6b0971a..5d520175 100644 --- a/vendor/github.com/hashicorp/go-tfe/policy_set_parameter.go +++ b/vendor/github.com/hashicorp/go-tfe/policy_set_parameter.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" ) @@ -16,7 +15,7 @@ var _ PolicySetParameters = (*policySetParameters)(nil) // TFE API docs: https://www.terraform.io/docs/cloud/api/policy-set-params.html type PolicySetParameters interface { // List all the parameters associated with the given policy-set. - List(ctx context.Context, policySetID string, options PolicySetParameterListOptions) (*PolicySetParameterList, error) + List(ctx context.Context, policySetID string, options *PolicySetParameterListOptions) (*PolicySetParameterList, error) // Create is used to create a new parameter. Create(ctx context.Context, policySetID string, options PolicySetParameterCreateOptions) (*PolicySetParameter, error) @@ -59,72 +58,70 @@ type PolicySetParameterListOptions struct { ListOptions } -func (o PolicySetParameterListOptions) valid() error { - return nil -} +// PolicySetParameterCreateOptions represents the options for creating a new parameter. +type PolicySetParameterCreateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,vars"` -// List all the parameters associated with the given policy-set. -func (s *policySetParameters) List(ctx context.Context, policySetID string, options PolicySetParameterListOptions) (*PolicySetParameterList, error) { - if !validStringID(&policySetID) { - return nil, errors.New("invalid value for policy set ID") - } - if err := options.valid(); err != nil { - return nil, err - } + // Required: The name of the parameter. + Key *string `jsonapi:"attr,key"` - u := fmt.Sprintf("policy-sets/%s/parameters", policySetID) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } + // Optional: The value of the parameter. + Value *string `jsonapi:"attr,value,omitempty"` - vl := &PolicySetParameterList{} - err = s.client.do(ctx, req, vl) - if err != nil { - return nil, err - } + // Required: The Category of the parameter, should always be "policy-set" + Category *CategoryType `jsonapi:"attr,category"` - return vl, nil + // Optional: Whether the value is sensitive. + Sensitive *bool `jsonapi:"attr,sensitive,omitempty"` } -// PolicySetParameterCreateOptions represents the options for creating a new parameter. -type PolicySetParameterCreateOptions struct { +// PolicySetParameterUpdateOptions represents the options for updating a parameter. +type PolicySetParameterUpdateOptions struct { // Type is a public field utilized by JSON:API to // set the resource type via the field tag. // It is not a user-defined value and does not need to be set. // https://jsonapi.org/format/#crud-creating Type string `jsonapi:"primary,vars"` - // The name of the parameter. - Key *string `jsonapi:"attr,key"` + // Optional: The name of the parameter. + Key *string `jsonapi:"attr,key,omitempty"` - // The value of the parameter. + // Optional: The value of the parameter. Value *string `jsonapi:"attr,value,omitempty"` - // The Category of the parameter, should always be "policy-set" - Category *CategoryType `jsonapi:"attr,category"` - - // Whether the value is sensitive. + // Optional: Whether the value is sensitive. Sensitive *bool `jsonapi:"attr,sensitive,omitempty"` } -func (o PolicySetParameterCreateOptions) valid() error { - if !validString(o.Key) { - return errors.New("key is required") +// List all the parameters associated with the given policy-set. +func (s *policySetParameters) List(ctx context.Context, policySetID string, options *PolicySetParameterListOptions) (*PolicySetParameterList, error) { + if !validStringID(&policySetID) { + return nil, ErrInvalidPolicySetID } - if o.Category == nil { - return errors.New("category is required") + + u := fmt.Sprintf("policy-sets/%s/parameters", policySetID) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err } - if *o.Category != CategoryPolicySet { - return errors.New("category must be policy-set") + + vl := &PolicySetParameterList{} + err = s.client.do(ctx, req, vl) + if err != nil { + return nil, err } - return nil + + return vl, nil } // Create is used to create a new parameter. func (s *policySetParameters) Create(ctx context.Context, policySetID string, options PolicySetParameterCreateOptions) (*PolicySetParameter, error) { if !validStringID(&policySetID) { - return nil, errors.New("invalid value for policy set ID") + return nil, ErrInvalidPolicySetID } if err := options.valid(); err != nil { return nil, err @@ -146,12 +143,12 @@ func (s *policySetParameters) Create(ctx context.Context, policySetID string, op } // Read a parameter by its ID. -func (s *policySetParameters) Read(ctx context.Context, policySetID string, parameterID string) (*PolicySetParameter, error) { +func (s *policySetParameters) Read(ctx context.Context, policySetID, parameterID string) (*PolicySetParameter, error) { if !validStringID(&policySetID) { - return nil, errors.New("invalid value for policy set ID") + return nil, ErrInvalidPolicySetID } if !validStringID(¶meterID) { - return nil, errors.New("invalid value for parameter ID") + return nil, ErrInvalidParamID } u := fmt.Sprintf("policy-sets/%s/parameters/%s", url.QueryEscape(policySetID), url.QueryEscape(parameterID)) @@ -169,31 +166,13 @@ func (s *policySetParameters) Read(ctx context.Context, policySetID string, para return p, err } -// PolicySetParameterUpdateOptions represents the options for updating a parameter. -type PolicySetParameterUpdateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,vars"` - - // The name of the parameter. - Key *string `jsonapi:"attr,key,omitempty"` - - // The value of the parameter. - Value *string `jsonapi:"attr,value,omitempty"` - - // Whether the value is sensitive. - Sensitive *bool `jsonapi:"attr,sensitive,omitempty"` -} - // Update values of an existing parameter. -func (s *policySetParameters) Update(ctx context.Context, policySetID string, parameterID string, options PolicySetParameterUpdateOptions) (*PolicySetParameter, error) { +func (s *policySetParameters) Update(ctx context.Context, policySetID, parameterID string, options PolicySetParameterUpdateOptions) (*PolicySetParameter, error) { if !validStringID(&policySetID) { - return nil, errors.New("invalid value for policy set ID") + return nil, ErrInvalidPolicySetID } if !validStringID(¶meterID) { - return nil, errors.New("invalid value for parameter ID") + return nil, ErrInvalidParamID } u := fmt.Sprintf("policy-sets/%s/parameters/%s", url.QueryEscape(policySetID), url.QueryEscape(parameterID)) @@ -212,12 +191,12 @@ func (s *policySetParameters) Update(ctx context.Context, policySetID string, pa } // Delete a parameter by its ID. -func (s *policySetParameters) Delete(ctx context.Context, policySetID string, parameterID string) error { +func (s *policySetParameters) Delete(ctx context.Context, policySetID, parameterID string) error { if !validStringID(&policySetID) { - return errors.New("invalid value for policy set ID") + return ErrInvalidPolicySetID } if !validStringID(¶meterID) { - return errors.New("invalid value for parameter ID") + return ErrInvalidParamID } u := fmt.Sprintf("policy-sets/%s/parameters/%s", url.QueryEscape(policySetID), url.QueryEscape(parameterID)) @@ -228,3 +207,16 @@ func (s *policySetParameters) Delete(ctx context.Context, policySetID string, pa return s.client.do(ctx, req, nil) } + +func (o PolicySetParameterCreateOptions) valid() error { + if !validString(o.Key) { + return ErrRequiredKey + } + if o.Category == nil { + return ErrRequiredCategory + } + if *o.Category != CategoryPolicySet { + return ErrInvalidCategory + } + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/policy_set_version.go b/vendor/github.com/hashicorp/go-tfe/policy_set_version.go index 6c028441..6d31ae87 100644 --- a/vendor/github.com/hashicorp/go-tfe/policy_set_version.go +++ b/vendor/github.com/hashicorp/go-tfe/policy_set_version.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" "time" @@ -48,7 +47,7 @@ const ( // PolicySetVersionStatus represents a policy set version status. type PolicySetVersionStatus string -//List all available policy set version statuses. +// List all available policy set version statuses. const ( PolicySetVersionErrored PolicySetVersionStatus = "errored" PolicySetVersionIngressing PolicySetVersionStatus = "ingressing" @@ -65,6 +64,7 @@ type PolicySetVersionStatusTimestamps struct { ErroredAt time.Time `jsonapi:"attr,errored-at,rfc3339"` } +// PolicySetVersion represents a Terraform Enterprise Policy Set Version type PolicySetVersion struct { ID string `jsonapi:"primary,policy-set-versions"` Source PolicySetVersionSource `jsonapi:"attr,source"` @@ -98,7 +98,7 @@ func (p PolicySetVersion) uploadURL() (string, error) { // Create is used to create a new Policy Set Version. func (p *policySetVersions) Create(ctx context.Context, policySetID string) (*PolicySetVersion, error) { if !validStringID(&policySetID) { - return nil, errors.New("invalid value for policy set ID") + return nil, ErrInvalidPolicySetID } u := fmt.Sprintf("policy-sets/%s/versions", url.QueryEscape(policySetID)) @@ -119,7 +119,7 @@ func (p *policySetVersions) Create(ctx context.Context, policySetID string) (*Po // Read is used to read a Policy Set Version by its ID. func (p *policySetVersions) Read(ctx context.Context, policySetVersionID string) (*PolicySetVersion, error) { if !validStringID(&policySetVersionID) { - return nil, errors.New("invalid value for policy set ID") + return nil, ErrInvalidPolicySetID } u := fmt.Sprintf("policy-set-versions/%s", url.QueryEscape(policySetVersionID)) diff --git a/vendor/github.com/hashicorp/go-tfe/registry_module.go b/vendor/github.com/hashicorp/go-tfe/registry_module.go index 45b9abae..1e0605f3 100644 --- a/vendor/github.com/hashicorp/go-tfe/registry_module.go +++ b/vendor/github.com/hashicorp/go-tfe/registry_module.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" ) @@ -15,26 +14,29 @@ var _ RegistryModules = (*registryModules)(nil) // // TFE API docs: https://www.terraform.io/docs/cloud/api/modules.html type RegistryModules interface { + // List all the registory modules within an organization + List(ctx context.Context, organization string, options *RegistryModuleListOptions) (*RegistryModuleList, error) + // Create a registry module without a VCS repo Create(ctx context.Context, organization string, options RegistryModuleCreateOptions) (*RegistryModule, error) // Create a registry module version - CreateVersion(ctx context.Context, organization string, name string, provider string, options RegistryModuleCreateVersionOptions) (*RegistryModuleVersion, error) + CreateVersion(ctx context.Context, moduleID RegistryModuleID, options RegistryModuleCreateVersionOptions) (*RegistryModuleVersion, error) // Create and publish a registry module with a VCS repo CreateWithVCSConnection(ctx context.Context, options RegistryModuleCreateWithVCSConnectionOptions) (*RegistryModule, error) // Read a registry module - Read(ctx context.Context, organization string, name string, provider string) (*RegistryModule, error) + Read(ctx context.Context, moduleID RegistryModuleID) (*RegistryModule, error) // Delete a registry module Delete(ctx context.Context, organization string, name string) error // Delete a specific registry module provider - DeleteProvider(ctx context.Context, organization string, name string, provider string) error + DeleteProvider(ctx context.Context, moduleID RegistryModuleID) error // Delete a specific registry module version - DeleteVersion(ctx context.Context, organization string, name string, provider string, version string) error + DeleteVersion(ctx context.Context, moduleID RegistryModuleID, version string) error // Upload Terraform configuration files for the provided registry module version. It // requires a path to the configuration files on disk, which will be packaged by @@ -72,6 +74,22 @@ const ( RegistryModuleVersionStatusOk RegistryModuleVersionStatus = "ok" ) +// RegistryModuleID represents the set of IDs that identify a RegistryModule +type RegistryModuleID struct { + // The organization the module belongs to, see RegistryModule.Organization.Name + Organization string + // The name of the module, see RegistryModule.Name + Name string + // The module's provider, see RegistryModule.Provider + Provider string +} + +// RegistryModuleList represents a list of registry modules. +type RegistryModuleList struct { + *Pagination + Items []*RegistryModule +} + // RegistryModule represents a registry module type RegistryModule struct { ID string `jsonapi:"primary,registry-modules"` @@ -104,28 +122,6 @@ type RegistryModuleVersion struct { Links map[string]interface{} `jsonapi:"links,omitempty"` } -// Upload uploads Terraform configuration files for the provided registry module version. It -// requires a path to the configuration files on disk, which will be packaged by -// hashicorp/go-slug before being uploaded. -func (r *registryModules) Upload(ctx context.Context, rmv RegistryModuleVersion, path string) error { - uploadURL, ok := rmv.Links["upload"].(string) - if !ok { - return fmt.Errorf("provided RegistryModuleVersion does not contain an upload link") - } - - body, err := packContents(path) - if err != nil { - return err - } - - req, err := r.client.newRequest("PUT", uploadURL, body) - if err != nil { - return err - } - - return r.client.do(ctx, req, nil) -} - type RegistryModulePermissions struct { CanDelete bool `jsonapi:"attr,can-delete"` CanResync bool `jsonapi:"attr,can-resync"` @@ -138,6 +134,11 @@ type RegistryModuleVersionStatuses struct { Error string `jsonapi:"attr,error"` } +// RegistryModuleListOptions represents the options for listing registry modules. +type RegistryModuleListOptions struct { + ListOptions +} + // RegistryModuleCreateOptions is used when creating a registry module without a VCS repo type RegistryModuleCreateOptions struct { // Type is a public field utilized by JSON:API to @@ -145,25 +146,82 @@ type RegistryModuleCreateOptions struct { // It is not a user-defined value and does not need to be set. // https://jsonapi.org/format/#crud-creating Type string `jsonapi:"primary,registry-modules"` - - Name *string `jsonapi:"attr,name"` + // Required: + Name *string `jsonapi:"attr,name"` + // Required: Provider *string `jsonapi:"attr,provider"` } -func (o RegistryModuleCreateOptions) valid() error { - if !validString(o.Name) { - return ErrRequiredName +// RegistryModuleCreateVersionOptions is used when creating a registry module version +type RegistryModuleCreateVersionOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,registry-module-versions"` + + Version *string `jsonapi:"attr,version"` +} + +// RegistryModuleCreateWithVCSConnectionOptions is used when creating a registry module with a VCS repo +type RegistryModuleCreateWithVCSConnectionOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,registry-modules"` + + // Required: VCS repository information + VCSRepo *RegistryModuleVCSRepoOptions `jsonapi:"attr,vcs-repo"` +} + +type RegistryModuleVCSRepoOptions struct { + Identifier *string `json:"identifier"` // Required + OAuthTokenID *string `json:"oauth-token-id"` // Required + DisplayIdentifier *string `json:"display-identifier"` // Required +} + +// List all the registory modules within an organization. +func (s *registryModules) List(ctx context.Context, organization string, options *RegistryModuleListOptions) (*RegistryModuleList, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg } - if !validStringID(o.Name) { - return ErrInvalidName + + u := fmt.Sprintf("organizations/%s/registry-modules", url.QueryEscape(organization)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err } - if !validString(o.Provider) { - return errors.New("provider is required") + + ml := &RegistryModuleList{} + err = s.client.do(ctx, req, ml) + if err != nil { + return nil, err } - if !validStringID(o.Provider) { - return errors.New("invalid value for provider") + + return ml, nil +} + +// Upload uploads Terraform configuration files for the provided registry module version. It +// requires a path to the configuration files on disk, which will be packaged by +// hashicorp/go-slug before being uploaded. +func (r *registryModules) Upload(ctx context.Context, rmv RegistryModuleVersion, path string) error { + uploadURL, ok := rmv.Links["upload"].(string) + if !ok { + return fmt.Errorf("provided RegistryModuleVersion does not contain an upload link") } - return nil + + body, err := packContents(path) + if err != nil { + return err + } + + req, err := r.client.newRequest("PUT", uploadURL, body) + if err != nil { + return err + } + + return r.client.do(ctx, req, nil) } // Create a new registry module without a VCS repo @@ -193,53 +251,21 @@ func (r *registryModules) Create(ctx context.Context, organization string, optio return rm, nil } -// RegistryModuleCreateVersionOptions is used when creating a registry module version -type RegistryModuleCreateVersionOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,registry-module-versions"` - - Version *string `jsonapi:"attr,version"` -} - -func (o RegistryModuleCreateVersionOptions) valid() error { - if !validString(o.Version) { - return errors.New("version is required") - } - if !validStringID(o.Version) { - return errors.New("invalid value for version") +// CreateVersion creates a new registry module version +func (r *registryModules) CreateVersion(ctx context.Context, moduleID RegistryModuleID, options RegistryModuleCreateVersionOptions) (*RegistryModuleVersion, error) { + if err := moduleID.valid(); err != nil { + return nil, err } - return nil -} -// Create a new registry module version -func (r *registryModules) CreateVersion(ctx context.Context, organization string, name string, provider string, options RegistryModuleCreateVersionOptions) (*RegistryModuleVersion, error) { - if !validStringID(&organization) { - return nil, ErrInvalidOrg - } - if !validString(&name) { - return nil, ErrRequiredName - } - if !validStringID(&name) { - return nil, ErrInvalidName - } - if !validString(&provider) { - return nil, errors.New("provider is required") - } - if !validStringID(&provider) { - return nil, errors.New("invalid value for provider") - } if err := options.valid(); err != nil { return nil, err } u := fmt.Sprintf( "registry-modules/%s/%s/%s/versions", - url.QueryEscape(organization), - url.QueryEscape(name), - url.QueryEscape(provider), + url.QueryEscape(moduleID.Organization), + url.QueryEscape(moduleID.Name), + url.QueryEscape(moduleID.Provider), ) req, err := r.client.newRequest("POST", u, &options) if err != nil { @@ -255,49 +281,12 @@ func (r *registryModules) CreateVersion(ctx context.Context, organization string return rmv, nil } -// RegistryModuleCreateWithVCSConnectionOptions is used when creating a registry module with a VCS repo -type RegistryModuleCreateWithVCSConnectionOptions struct { - ID string `jsonapi:"primary,registry-modules"` - - // VCS repository information - VCSRepo *RegistryModuleVCSRepoOptions `jsonapi:"attr,vcs-repo"` -} - -func (o RegistryModuleCreateWithVCSConnectionOptions) valid() error { - if o.VCSRepo == nil { - return errors.New("vcs repo is required") - } - return o.VCSRepo.valid() -} - -type RegistryModuleVCSRepoOptions struct { - Identifier *string `json:"identifier"` - OAuthTokenID *string `json:"oauth-token-id"` - DisplayIdentifier *string `json:"display-identifier"` -} - -func (o RegistryModuleVCSRepoOptions) valid() error { - if !validString(o.Identifier) { - return errors.New("identifier is required") - } - if !validString(o.OAuthTokenID) { - return errors.New("oauth token ID is required") - } - if !validString(o.DisplayIdentifier) { - return errors.New("display identifier is required") - } - return nil -} - // CreateWithVCSConnection is used to create and publish a new registry module with a VCS repo func (r *registryModules) CreateWithVCSConnection(ctx context.Context, options RegistryModuleCreateWithVCSConnectionOptions) (*RegistryModule, error) { if err := options.valid(); err != nil { return nil, err } - // Make sure we don't send a user provided ID. - options.ID = "" - req, err := r.client.newRequest("POST", "registry-modules", &options) if err != nil { return nil, err @@ -313,28 +302,16 @@ func (r *registryModules) CreateWithVCSConnection(ctx context.Context, options R } // Read a specific registry module -func (r *registryModules) Read(ctx context.Context, organization string, name string, provider string) (*RegistryModule, error) { - if !validStringID(&organization) { - return nil, ErrInvalidOrg - } - if !validString(&name) { - return nil, ErrRequiredName - } - if !validStringID(&name) { - return nil, ErrInvalidName - } - if !validString(&provider) { - return nil, errors.New("provider is required") - } - if !validStringID(&provider) { - return nil, errors.New("invalid value for provider") +func (r *registryModules) Read(ctx context.Context, moduleID RegistryModuleID) (*RegistryModule, error) { + if err := moduleID.valid(); err != nil { + return nil, err } u := fmt.Sprintf( "registry-modules/show/%s/%s/%s", - url.QueryEscape(organization), - url.QueryEscape(name), - url.QueryEscape(provider), + url.QueryEscape(moduleID.Organization), + url.QueryEscape(moduleID.Name), + url.QueryEscape(moduleID.Provider), ) req, err := r.client.newRequest("GET", u, nil) if err != nil { @@ -351,7 +328,7 @@ func (r *registryModules) Read(ctx context.Context, organization string, name st } // Delete is used to delete the entire registry module -func (r *registryModules) Delete(ctx context.Context, organization string, name string) error { +func (r *registryModules) Delete(ctx context.Context, organization, name string) error { if !validStringID(&organization) { return ErrInvalidOrg } @@ -376,28 +353,16 @@ func (r *registryModules) Delete(ctx context.Context, organization string, name } // DeleteProvider is used to delete the specific registry module provider -func (r *registryModules) DeleteProvider(ctx context.Context, organization string, name string, provider string) error { - if !validStringID(&organization) { - return ErrInvalidOrg - } - if !validString(&name) { - return ErrRequiredName - } - if !validStringID(&name) { - return ErrInvalidName - } - if !validString(&provider) { - return errors.New("provider is required") - } - if !validStringID(&provider) { - return errors.New("invalid value for provider") +func (r *registryModules) DeleteProvider(ctx context.Context, moduleID RegistryModuleID) error { + if err := moduleID.valid(); err != nil { + return err } u := fmt.Sprintf( "registry-modules/actions/delete/%s/%s/%s", - url.QueryEscape(organization), - url.QueryEscape(name), - url.QueryEscape(provider), + url.QueryEscape(moduleID.Organization), + url.QueryEscape(moduleID.Name), + url.QueryEscape(moduleID.Provider), ) req, err := r.client.newRequest("POST", u, nil) if err != nil { @@ -408,34 +373,22 @@ func (r *registryModules) DeleteProvider(ctx context.Context, organization strin } // DeleteVersion is used to delete the specific registry module version -func (r *registryModules) DeleteVersion(ctx context.Context, organization string, name string, provider string, version string) error { - if !validStringID(&organization) { - return ErrInvalidOrg - } - if !validString(&name) { - return ErrRequiredName - } - if !validStringID(&name) { - return ErrInvalidName - } - if !validString(&provider) { - return errors.New("provider is required") - } - if !validStringID(&provider) { - return errors.New("invalid value for provider") +func (r *registryModules) DeleteVersion(ctx context.Context, moduleID RegistryModuleID, version string) error { + if err := moduleID.valid(); err != nil { + return err } if !validString(&version) { - return errors.New("version is required") + return ErrRequiredVersion } if !validStringID(&version) { - return errors.New("invalid value for version") + return ErrInvalidVersion } u := fmt.Sprintf( "registry-modules/actions/delete/%s/%s/%s/%s", - url.QueryEscape(organization), - url.QueryEscape(name), - url.QueryEscape(provider), + url.QueryEscape(moduleID.Organization), + url.QueryEscape(moduleID.Name), + url.QueryEscape(moduleID.Provider), url.QueryEscape(version), ) req, err := r.client.newRequest("POST", u, nil) @@ -445,3 +398,73 @@ func (r *registryModules) DeleteVersion(ctx context.Context, organization string return r.client.do(ctx, req, nil) } + +func (o RegistryModuleID) valid() error { + if !validStringID(&o.Organization) { + return ErrInvalidOrg + } + + if !validString(&o.Name) { + return ErrRequiredName + } + + if !validStringID(&o.Name) { + return ErrInvalidName + } + + if !validString(&o.Provider) { + return ErrRequiredProvider + } + + if !validStringID(&o.Provider) { + return ErrInvalidProvider + } + + return nil +} + +func (o RegistryModuleCreateOptions) valid() error { + if !validString(o.Name) { + return ErrRequiredName + } + if !validStringID(o.Name) { + return ErrInvalidName + } + if !validString(o.Provider) { + return ErrRequiredProvider + } + if !validStringID(o.Provider) { + return ErrInvalidProvider + } + return nil +} + +func (o RegistryModuleCreateVersionOptions) valid() error { + if !validString(o.Version) { + return ErrRequiredVersion + } + if !validStringID(o.Version) { + return ErrInvalidVersion + } + return nil +} + +func (o RegistryModuleCreateWithVCSConnectionOptions) valid() error { + if o.VCSRepo == nil { + return ErrRequiredVCSRepo + } + return o.VCSRepo.valid() +} + +func (o RegistryModuleVCSRepoOptions) valid() error { + if !validString(o.Identifier) { + return ErrRequiredIdentifier + } + if !validString(o.OAuthTokenID) { + return ErrRequiredOauthTokenID + } + if !validString(o.DisplayIdentifier) { + return ErrRequiredDisplayIdentifier + } + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/registry_provider.go b/vendor/github.com/hashicorp/go-tfe/registry_provider.go new file mode 100644 index 00000000..db32da99 --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/registry_provider.go @@ -0,0 +1,250 @@ +package tfe + +import ( + "context" + "fmt" + "net/url" +) + +// Compile-time proof of interface implementation. +var _ RegistryProviders = (*registryProviders)(nil) + +// RegistryProviders describes all the registry provider-related methods that the Terraform +// Enterprise API supports. +// +// TFE API docs: https://www.terraform.io/docs/cloud/api/providers.html +type RegistryProviders interface { + // List all the providers within an organization. + List(ctx context.Context, organization string, options *RegistryProviderListOptions) (*RegistryProviderList, error) + + // Create a registry provider. + Create(ctx context.Context, organization string, options RegistryProviderCreateOptions) (*RegistryProvider, error) + + // Read a registry provider. + Read(ctx context.Context, providerID RegistryProviderID, options *RegistryProviderReadOptions) (*RegistryProvider, error) + + // Delete a registry provider. + Delete(ctx context.Context, providerID RegistryProviderID) error +} + +// registryProviders implements RegistryProviders. +type registryProviders struct { + client *Client +} + +// RegistryName represents which registry is being targeted +type RegistryName string + +// List of available registry names +const ( + PrivateRegistry RegistryName = "private" + PublicRegistry RegistryName = "public" +) + +// RegistryProviderIncludeOps represents which jsonapi include can be used with registry providers +type RegistryProviderIncludeOps string + +// List of available includes +const ( + RegistryProviderVersionsInclude RegistryProviderIncludeOps = "registry-provider-versions" +) + +// RegistryProvider represents a registry provider +type RegistryProvider struct { + ID string `jsonapi:"primary,registry-providers"` + Name string `jsonapi:"attr,name"` + Namespace string `jsonapi:"attr,namespace"` + CreatedAt string `jsonapi:"attr,created-at,iso8601"` + UpdatedAt string `jsonapi:"attr,updated-at,iso8601"` + RegistryName RegistryName `jsonapi:"attr,registry-name"` + Permissions RegistryProviderPermissions `jsonapi:"attr,permissions"` + + // Relations + Organization *Organization `jsonapi:"relation,organization"` + RegistryProviderVersions []*RegistryProviderVersion `jsonapi:"relation,registry-provider-versions"` + + // Links + Links map[string]interface{} `jsonapi:"links,omitempty"` +} + +type RegistryProviderPermissions struct { + CanDelete bool `jsonapi:"attr,can-delete"` +} + +type RegistryProviderListOptions struct { + ListOptions + + // Optional: A query string to filter by registry_name + RegistryName RegistryName `url:"filter[registry_name],omitempty"` + + // Optional: A query string to filter by organization + OrganizationName string `url:"filter[organization_name],omitempty"` + + // Optional: A query string to do a fuzzy search + Search string `url:"q,omitempty"` + + // Optional: Include related jsonapi relationships + Include *[]RegistryProviderIncludeOps `url:"include,omitempty"` +} + +type RegistryProviderList struct { + *Pagination + Items []*RegistryProvider +} + +// RegistryProviderID is the multi key ID for addressing a provider +type RegistryProviderID struct { + OrganizationName string + RegistryName RegistryName + Namespace string + Name string +} + +// RegistryProviderCreateOptions is used when creating a registry provider +type RegistryProviderCreateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,registry-providers"` + + // Required: The name of the registry provider + Name string `jsonapi:"attr,name"` + + // Required: The namespace of the provider. For private providers, this is the same as the organization name + Namespace string `jsonapi:"attr,namespace"` + + // Required: Whether this is a publicly maintained provider or private. Must be either public or private. + RegistryName RegistryName `jsonapi:"attr,registry-name"` +} + +type RegistryProviderReadOptions struct { + // Optional: Include related jsonapi relationships + Include []RegistryProviderIncludeOps `url:"include,omitempty"` +} + +func (r *registryProviders) List(ctx context.Context, organization string, options *RegistryProviderListOptions) (*RegistryProviderList, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg + } + if err := options.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf("organizations/%s/registry-providers", url.QueryEscape(organization)) + req, err := r.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + pl := &RegistryProviderList{} + err = r.client.do(ctx, req, pl) + if err != nil { + return nil, err + } + + return pl, nil +} + +func (r *registryProviders) Create(ctx context.Context, organization string, options RegistryProviderCreateOptions) (*RegistryProvider, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg + } + + if err := options.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf( + "organizations/%s/registry-providers", + url.QueryEscape(organization), + ) + req, err := r.client.newRequest("POST", u, &options) + if err != nil { + return nil, err + } + + prv := &RegistryProvider{} + err = r.client.do(ctx, req, prv) + if err != nil { + return nil, err + } + + return prv, nil +} + +func (r *registryProviders) Read(ctx context.Context, providerID RegistryProviderID, options *RegistryProviderReadOptions) (*RegistryProvider, error) { + if err := providerID.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s", + url.QueryEscape(providerID.OrganizationName), + url.QueryEscape(string(providerID.RegistryName)), + url.QueryEscape(providerID.Namespace), + url.QueryEscape(providerID.Name), + ) + req, err := r.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + prv := &RegistryProvider{} + err = r.client.do(ctx, req, prv) + if err != nil { + return nil, err + } + + return prv, nil +} + +func (r *registryProviders) Delete(ctx context.Context, providerID RegistryProviderID) error { + if err := providerID.valid(); err != nil { + return err + } + + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s", + url.QueryEscape(providerID.OrganizationName), + url.QueryEscape(string(providerID.RegistryName)), + url.QueryEscape(providerID.Namespace), + url.QueryEscape(providerID.Name), + ) + req, err := r.client.newRequest("DELETE", u, nil) + if err != nil { + return err + } + + return r.client.do(ctx, req, nil) +} + +func (o RegistryProviderCreateOptions) valid() error { + if !validStringID(&o.Name) { + return ErrInvalidName + } + if !validStringID(&o.Namespace) { + return ErrInvalidNamespace + } + return nil +} + +func (id RegistryProviderID) valid() error { + if !validStringID(&id.OrganizationName) { + return ErrInvalidOrg + } + if !validStringID(&id.Name) { + return ErrInvalidName + } + if !validStringID(&id.Namespace) { + return ErrInvalidNamespace + } + if !validStringID((*string)(&id.RegistryName)) { + return ErrInvalidRegistryName + } + return nil +} + +func (o *RegistryProviderListOptions) valid() error { + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/registry_provider_platform.go b/vendor/github.com/hashicorp/go-tfe/registry_provider_platform.go new file mode 100644 index 00000000..9f2d9975 --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/registry_provider_platform.go @@ -0,0 +1,233 @@ +package tfe + +import ( + "context" + "fmt" + "net/url" +) + +// Compile-time proof of interface implementation +var _ RegistryProviderPlatforms = (*registryProviderPlatforms)(nil) + +// RegistryProviderPlatforms describes the registry provider platform methods supported by the Terraform Enterprise API. +// +// TFE API docs: https://www.terraform.io/cloud-docs/api-docs/private-registry/provider-versions-platforms#private-provider-versions-and-platforms-api +type RegistryProviderPlatforms interface { + // Create a provider platform for an organization + Create(ctx context.Context, versionID RegistryProviderVersionID, options RegistryProviderPlatformCreateOptions) (*RegistryProviderPlatform, error) + + // List all provider platforms for a single version + List(ctx context.Context, versionID RegistryProviderVersionID, options *RegistryProviderPlatformListOptions) (*RegistryProviderPlatformList, error) + + // Read a provider platform by ID + Read(ctx context.Context, platformID RegistryProviderPlatformID) (*RegistryProviderPlatform, error) + + // Delete a provider platform + Delete(ctx context.Context, platformID RegistryProviderPlatformID) error +} + +// registryProviders implements RegistryProviders +type registryProviderPlatforms struct { + client *Client +} + +// RegistryProviderPlatform represents a registry provider platform +type RegistryProviderPlatform struct { + ID string `jsonapi:"primary,registry-provider-platforms"` + OS string `jsonapi:"attr,os"` + Arch string `jsonapi:"attr,arch"` + Filename string `jsonapi:"attr,filename"` + Shasum string `jsonapi:"attr,shasum"` + + // Relations + RegistryProviderVersion *RegistryProviderVersion `jsonapi:"relation,registry-provider-version"` + + // Links + Links map[string]interface{} `jsonapi:"links,omitempty"` +} + +// RegistryProviderPlatformID is the multi key ID for identifying a provider platform +type RegistryProviderPlatformID struct { + RegistryProviderVersionID + OS string + Arch string +} + +// RegistryProviderPlatformCreateOptions represents the set of options for creating a registry provider platform +type RegistryProviderPlatformCreateOptions struct { + // Required: A valid operating system string + OS string `jsonapi:"attr,os"` + + // Required: A valid architecture string + Arch string `jsonapi:"attr,arch"` + + // Required: A valid shasum string + Shasum string `jsonapi:"attr,shasum"` + + // Required: A valid filename string + Filename string `jsonapi:"attr,filename"` +} + +type RegistryProviderPlatformList struct { + *Pagination + Items []*RegistryProviderPlatform +} + +type RegistryProviderPlatformListOptions struct { + ListOptions +} + +// Create a new registry provider platform +func (r *registryProviderPlatforms) Create(ctx context.Context, versionID RegistryProviderVersionID, options RegistryProviderPlatformCreateOptions) (*RegistryProviderPlatform, error) { + if err := versionID.valid(); err != nil { + return nil, err + } + + if err := options.valid(); err != nil { + return nil, err + } + + // POST /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s/versions/%s/platforms", + url.QueryEscape(versionID.OrganizationName), + url.QueryEscape(string(versionID.RegistryName)), + url.QueryEscape(versionID.Namespace), + url.QueryEscape(versionID.Name), + url.QueryEscape(versionID.Version), + ) + req, err := r.client.newRequest("POST", u, &options) + if err != nil { + return nil, err + } + + rpp := &RegistryProviderPlatform{} + err = r.client.do(ctx, req, rpp) + if err != nil { + return nil, err + } + + return rpp, nil +} + +// List all provider platforms for a single version +func (r *registryProviderPlatforms) List(ctx context.Context, versionID RegistryProviderVersionID, options *RegistryProviderPlatformListOptions) (*RegistryProviderPlatformList, error) { + if err := versionID.valid(); err != nil { + return nil, err + } + if err := options.valid(); err != nil { + return nil, err + } + + // GET /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s/versions/%s/platforms", + url.QueryEscape(versionID.RegistryProviderID.OrganizationName), + url.QueryEscape(string(versionID.RegistryProviderID.RegistryName)), + url.QueryEscape(versionID.RegistryProviderID.Namespace), + url.QueryEscape(versionID.RegistryProviderID.Name), + url.QueryEscape(versionID.Version), + ) + req, err := r.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + ppl := &RegistryProviderPlatformList{} + err = r.client.do(ctx, req, ppl) + if err != nil { + return nil, err + } + + return ppl, nil +} + +// Read is used to read an organization's example by ID +func (r *registryProviderPlatforms) Read(ctx context.Context, platformID RegistryProviderPlatformID) (*RegistryProviderPlatform, error) { + if err := platformID.valid(); err != nil { + return nil, err + } + + // GET /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms/:os/:arch + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s/versions/%s/platforms/%s/%s", + url.QueryEscape(platformID.RegistryProviderID.OrganizationName), + url.QueryEscape(string(platformID.RegistryProviderID.RegistryName)), + url.QueryEscape(platformID.RegistryProviderID.Namespace), + url.QueryEscape(platformID.RegistryProviderID.Name), + url.QueryEscape(platformID.RegistryProviderVersionID.Version), + url.QueryEscape(platformID.OS), + url.QueryEscape(platformID.Arch), + ) + req, err := r.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + rpp := &RegistryProviderPlatform{} + err = r.client.do(ctx, req, rpp) + + if err != nil { + return nil, err + } + + return rpp, nil +} + +// Delete a registry provider platform +func (r *registryProviderPlatforms) Delete(ctx context.Context, platformID RegistryProviderPlatformID) error { + if err := platformID.valid(); err != nil { + return err + } + + // DELETE /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms/:os/:arch + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s/versions/%s/platforms/%s/%s", + url.QueryEscape(platformID.OrganizationName), + url.QueryEscape(string(platformID.RegistryName)), + url.QueryEscape(platformID.Namespace), + url.QueryEscape(platformID.Name), + url.QueryEscape(platformID.Version), + url.QueryEscape(platformID.OS), + url.QueryEscape(platformID.Arch), + ) + req, err := r.client.newRequest("DELETE", u, nil) + if err != nil { + return err + } + + return r.client.do(ctx, req, nil) +} + +func (id RegistryProviderPlatformID) valid() error { + if err := id.RegistryProviderID.valid(); err != nil { + return err + } + if !validString(&id.OS) { + return ErrInvalidOS + } + if !validString(&id.Arch) { + return ErrInvalidArch + } + return nil +} + +func (o RegistryProviderPlatformCreateOptions) valid() error { + if !validString(&o.OS) { + return ErrRequiredOS + } + if !validString(&o.Arch) { + return ErrRequiredArch + } + if !validStringID(&o.Shasum) { + return ErrRequiredShasum + } + if !validStringID(&o.Filename) { + return ErrRequiredFilename + } + return nil +} + +func (o *RegistryProviderPlatformListOptions) valid() error { + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/registry_provider_version.go b/vendor/github.com/hashicorp/go-tfe/registry_provider_version.go new file mode 100644 index 00000000..f8455485 --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/registry_provider_version.go @@ -0,0 +1,274 @@ +package tfe + +import ( + "context" + "fmt" + "net/url" +) + +// Compile-time proof of interface implementation. +var _ RegistryProviderVersions = (*registryProviderVersions)(nil) + +// RegistryProviderVersions describes the registry provider version methods that +// the Terraform Enterprise API supports. +// +// TFE API docs: https://www.terraform.io/cloud-docs/api-docs/private-registry/provider-versions-platforms +type RegistryProviderVersions interface { + // List all versions for a single provider. + List(ctx context.Context, providerID RegistryProviderID, options *RegistryProviderVersionListOptions) (*RegistryProviderVersionList, error) + + // Create a registry provider version. + Create(ctx context.Context, providerID RegistryProviderID, options RegistryProviderVersionCreateOptions) (*RegistryProviderVersion, error) + + // Read a registry provider version. + Read(ctx context.Context, versionID RegistryProviderVersionID) (*RegistryProviderVersion, error) + + // Delete a registry provider version. + Delete(ctx context.Context, versionID RegistryProviderVersionID) error +} + +// registryProvidersVersions implements RegistryProvidersVersions +type registryProviderVersions struct { + client *Client +} + +// RegistryProviderVersion represents a registry provider version +type RegistryProviderVersion struct { + ID string `jsonapi:"primary,registry-provider-versions"` + Version string `jsonapi:"attr,version"` + CreatedAt string `jsonapi:"attr,created-at,iso8601"` + UpdatedAt string `jsonapi:"attr,updated-at,iso8601"` + KeyID string `jsonapi:"attr,key-id"` + Protocols []string `jsonapi:"attr,protocols"` + Permissions RegistryProviderVersionPermissions `jsonapi:"attr,permissions"` + ShasumsUploaded bool `jsonapi:"attr,shasums-uploaded"` + ShasumsSigUploaded bool `jsonapi:"attr,shasums-sig-uploaded"` + + // Relations + RegistryProvider *RegistryProvider `jsonapi:"relation,registry-provider"` + RegistryProviderPlatforms []*RegistryProviderPlatform `jsonapi:"relation,platforms"` + + // Links + Links map[string]interface{} `jsonapi:"links,omitempty"` +} + +// RegistryProviderVersionID is the multi key ID for addressing a version provider +type RegistryProviderVersionID struct { + RegistryProviderID + Version string +} + +type RegistryProviderVersionPermissions struct { + CanDelete bool `jsonapi:"attr,can-delete"` + CanUploadAsset bool `jsonapi:"attr,can-upload-asset"` +} + +type RegistryProviderVersionList struct { + *Pagination + Items []*RegistryProviderVersion +} + +type RegistryProviderVersionListOptions struct { + ListOptions +} + +type RegistryProviderVersionCreateOptions struct { + // Required: A valid semver version string. + Version string `jsonapi:"attr,version"` + + // Required: A valid gpg-key string. + KeyID string `jsonapi:"attr,key-id"` + + // Required: An array of Terraform provider API versions that this version supports. + Protocols []string `jsonapi:"attr,protocols"` +} + +// List registry provider versions +func (r *registryProviderVersions) List(ctx context.Context, providerID RegistryProviderID, options *RegistryProviderVersionListOptions) (*RegistryProviderVersionList, error) { + if err := providerID.valid(); err != nil { + return nil, err + } + if err := options.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s/versions", + url.QueryEscape(providerID.OrganizationName), + url.QueryEscape(string(providerID.RegistryName)), + url.QueryEscape(providerID.Namespace), + url.QueryEscape(providerID.Name), + ) + req, err := r.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + pvl := &RegistryProviderVersionList{} + err = r.client.do(ctx, req, pvl) + if err != nil { + return nil, err + } + + return pvl, nil +} + +// Create a registry provider version +func (r *registryProviderVersions) Create(ctx context.Context, providerID RegistryProviderID, options RegistryProviderVersionCreateOptions) (*RegistryProviderVersion, error) { + if err := providerID.valid(); err != nil { + return nil, err + } + + if providerID.RegistryName != PrivateRegistry { + return nil, ErrRequiredPrivateRegistry + } + + if err := options.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s/versions", + url.QueryEscape(providerID.OrganizationName), + url.QueryEscape(string(providerID.RegistryName)), + url.QueryEscape(providerID.Namespace), + url.QueryEscape(providerID.Name), + ) + req, err := r.client.newRequest("POST", u, &options) + if err != nil { + return nil, err + } + + prvv := &RegistryProviderVersion{} + err = r.client.do(ctx, req, prvv) + if err != nil { + return nil, err + } + + return prvv, nil +} + +// Read a registry provider version +func (r *registryProviderVersions) Read(ctx context.Context, versionID RegistryProviderVersionID) (*RegistryProviderVersion, error) { + if err := versionID.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s/versions/%s", + url.QueryEscape(versionID.OrganizationName), + url.QueryEscape(string(versionID.RegistryName)), + url.QueryEscape(versionID.Namespace), + url.QueryEscape(versionID.Name), + url.QueryEscape(versionID.Version), + ) + req, err := r.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + prvv := &RegistryProviderVersion{} + err = r.client.do(ctx, req, prvv) + if err != nil { + return nil, err + } + + return prvv, nil +} + +// Delete a registry provider version +func (r *registryProviderVersions) Delete(ctx context.Context, versionID RegistryProviderVersionID) error { + if err := versionID.valid(); err != nil { + return err + } + + u := fmt.Sprintf( + "organizations/%s/registry-providers/%s/%s/%s/versions/%s", + url.QueryEscape(versionID.OrganizationName), + url.QueryEscape(string(versionID.RegistryName)), + url.QueryEscape(versionID.Namespace), + url.QueryEscape(versionID.Name), + url.QueryEscape(versionID.Version), + ) + req, err := r.client.newRequest("DELETE", u, nil) + if err != nil { + return err + } + + return r.client.do(ctx, req, nil) +} + +// ShasumsUploadURL returns the upload URL to upload shasums if one is available +func (v *RegistryProviderVersion) ShasumsUploadURL() (string, error) { + uploadURL, ok := v.Links["shasums-upload"].(string) + if !ok { + return uploadURL, fmt.Errorf("the Registry Provider Version does not contain a shasums upload link") + } + if uploadURL == "" { + return uploadURL, fmt.Errorf("the Registry Provider Version shasums upload URL is empty") + } + return uploadURL, nil +} + +// ShasumsSigUploadURL returns the URL to upload a shasums sig +func (v *RegistryProviderVersion) ShasumsSigUploadURL() (string, error) { + uploadURL, ok := v.Links["shasums-sig-upload"].(string) + if !ok { + return uploadURL, fmt.Errorf("the Registry Provider Version does not contain a shasums sig upload link") + } + if uploadURL == "" { + return uploadURL, fmt.Errorf("the Registry Provider Version shasums sig upload URL is empty") + } + return uploadURL, nil +} + +// ShasumsDownloadURL returns the URL to download the shasums for the registry version +func (v *RegistryProviderVersion) ShasumsDownloadURL() (string, error) { + downloadURL, ok := v.Links["shasums-download"].(string) + if !ok { + return downloadURL, fmt.Errorf("the Registry Provider Version does not contain a shasums download link") + } + if downloadURL == "" { + return downloadURL, fmt.Errorf("the Registry Provider Version shasums download URL is empty") + } + return downloadURL, nil +} + +// ShasumsSigDownloadURL returns the URL to download the shasums sig for the registry version +func (v *RegistryProviderVersion) ShasumsSigDownloadURL() (string, error) { + downloadURL, ok := v.Links["shasums-sig-download"].(string) + if !ok { + return downloadURL, fmt.Errorf("the Registry Provider Version does not contain a shasums sig download link") + } + if downloadURL == "" { + return downloadURL, fmt.Errorf("the Registry Provider Version shasums sig download URL is empty") + } + return downloadURL, nil +} + +func (id RegistryProviderVersionID) valid() error { + if !validStringID(&id.Version) { + return ErrInvalidVersion + } + if id.RegistryName != PrivateRegistry { + return ErrRequiredPrivateRegistry + } + if err := id.RegistryProviderID.valid(); err != nil { + return err + } + return nil +} + +func (o *RegistryProviderVersionListOptions) valid() error { + return nil +} + +func (o RegistryProviderVersionCreateOptions) valid() error { + if !validStringID(&o.Version) { + return ErrInvalidVersion + } + if !validStringID(&o.KeyID) { + return ErrInvalidKeyID + } + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/run.go b/vendor/github.com/hashicorp/go-tfe/run.go index c7a21760..5c1cd85f 100644 --- a/vendor/github.com/hashicorp/go-tfe/run.go +++ b/vendor/github.com/hashicorp/go-tfe/run.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" "time" @@ -17,7 +16,7 @@ var _ Runs = (*runs)(nil) // TFE API docs: https://www.terraform.io/docs/cloud/api/run.html type Runs interface { // List all the runs of the given workspace. - List(ctx context.Context, workspaceID string, options RunListOptions) (*RunList, error) + List(ctx context.Context, workspaceID string, options *RunListOptions) (*RunList, error) // Create a new run with the given options. Create(ctx context.Context, options RunCreateOptions) (*Run, error) @@ -49,7 +48,7 @@ type runs struct { // RunStatus represents a run state. type RunStatus string -//List all available run statuses. +// List all available run statuses. const ( RunApplied RunStatus = "applied" RunApplyQueued RunStatus = "apply_queued" @@ -60,6 +59,7 @@ const ( RunCostEstimating RunStatus = "cost_estimating" RunDiscarded RunStatus = "discarded" RunErrored RunStatus = "errored" + RunFetching RunStatus = "fetching" RunPending RunStatus = "pending" RunPlanQueued RunStatus = "plan_queued" RunPlanned RunStatus = "planned" @@ -69,6 +69,8 @@ const ( RunPolicyChecking RunStatus = "policy_checking" RunPolicyOverride RunStatus = "policy_override" RunPolicySoftFailed RunStatus = "policy_soft_failed" + RunPostPlanRunning RunStatus = "post_plan_running" + RunPostPlanCompleted RunStatus = "post_plan_completed" ) // RunSource represents a source type of a run. @@ -115,7 +117,9 @@ type Run struct { CreatedBy *User `jsonapi:"relation,created-by"` Plan *Plan `jsonapi:"relation,plan"` PolicyChecks []*PolicyCheck `jsonapi:"relation,policy-checks"` + TaskStages []*TaskStage `jsonapi:"relation,task-stages,omitempty"` Workspace *Workspace `jsonapi:"relation,workspace"` + Comments []*Comment `jsonapi:"relation,comments"` } // RunActions represents the run actions. @@ -146,6 +150,8 @@ type RunStatusTimestamps struct { CostEstimatingAt time.Time `jsonapi:"attr,cost-estimating-at,rfc3339"` DiscardedAt time.Time `jsonapi:"attr,discarded-at,rfc3339"` ErroredAt time.Time `jsonapi:"attr,errored-at,rfc3339"` + FetchedAt time.Time `jsonapi:"attr,fetched-at,rfc3339"` + FetchingAt time.Time `jsonapi:"attr,fetching-at,rfc3339"` ForceCanceledAt time.Time `jsonapi:"attr,force-canceled-at,rfc3339"` PlanQueueableAt time.Time `jsonapi:"attr,plan-queueable-at,rfc3339"` PlanQueuedAt time.Time `jsonapi:"attr,plan-queued-at,rfc3339"` @@ -156,42 +162,34 @@ type RunStatusTimestamps struct { PolicySoftFailedAt time.Time `jsonapi:"attr,policy-soft-failed-at,rfc3339"` } +// RunIncludeOpt represents the available options for include query params. +// https://www.terraform.io/docs/cloud/api/run.html#available-related-resources +type RunIncludeOpt string + +const ( + RunPlan RunIncludeOpt = "plan" + RunApply RunIncludeOpt = "apply" + RunCreatedBy RunIncludeOpt = "created_by" + RunCostEstimate RunIncludeOpt = "cost_estimate" + RunConfigVer RunIncludeOpt = "configuration_version" + RunConfigVerIngress RunIncludeOpt = "configuration_version.ingress_attributes" + RunWorkspace RunIncludeOpt = "workspace" + RunTaskStages RunIncludeOpt = "task_stages" +) + // RunListOptions represents the options for listing runs. type RunListOptions struct { ListOptions - - // A list of relations to include. See available resources: + // Optional: A list of relations to include. See available resources: // https://www.terraform.io/docs/cloud/api/run.html#available-related-resources - Include *string `url:"include"` + Include []RunIncludeOpt `url:"include,omitempty"` } -// RunVariable represents a variable that can be applied to a run. All values must be expressed as an HCL literal -// in the same syntax you would use when writing terraform code. See https://www.terraform.io/docs/language/expressions/types.html#types -// for more details. -type RunVariable struct { - Key string `jsonapi:"attr,key"` - Value string `jsonapi:"attr,value"` -} - -// List all the runs of the given workspace. -func (s *runs) List(ctx context.Context, workspaceID string, options RunListOptions) (*RunList, error) { - if !validStringID(&workspaceID) { - return nil, ErrInvalidWorkspaceID - } - - u := fmt.Sprintf("workspaces/%s/runs", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - rl := &RunList{} - err = s.client.do(ctx, req, rl) - if err != nil { - return nil, err - } - - return rl, nil +// RunReadOptions represents the options for reading a run. +type RunReadOptions struct { + // Optional: A list of relations to include. See available resources: + // https://www.terraform.io/docs/cloud/api/run.html#available-related-resources + Include []RunIncludeOpt `url:"include,omitempty"` } // RunCreateOptions represents the options for creating a new run. @@ -252,11 +250,60 @@ type RunCreateOptions struct { Variables []*RunVariable `jsonapi:"attr,variables,omitempty"` } -func (o RunCreateOptions) valid() error { - if o.Workspace == nil { - return errors.New("workspace is required") +// RunApplyOptions represents the options for applying a run. +type RunApplyOptions struct { + // An optional comment about the run. + Comment *string `json:"comment,omitempty"` +} + +// RunCancelOptions represents the options for canceling a run. +type RunCancelOptions struct { + // An optional explanation for why the run was canceled. + Comment *string `json:"comment,omitempty"` +} + +// RunVariable represents a variable that can be applied to a run. All values must be expressed as an HCL literal +// in the same syntax you would use when writing terraform code. See https://www.terraform.io/docs/language/expressions/types.html#types +// for more details. +type RunVariable struct { + Key string `jsonapi:"attr,key"` + Value string `jsonapi:"attr,value"` +} + +// RunForceCancelOptions represents the options for force-canceling a run. +type RunForceCancelOptions struct { + // An optional comment explaining the reason for the force-cancel. + Comment *string `json:"comment,omitempty"` +} + +// RunDiscardOptions represents the options for discarding a run. +type RunDiscardOptions struct { + // An optional explanation for why the run was discarded. + Comment *string `json:"comment,omitempty"` +} + +// List all the runs of the given workspace. +func (s *runs) List(ctx context.Context, workspaceID string, options *RunListOptions) (*RunList, error) { + if !validStringID(&workspaceID) { + return nil, ErrInvalidWorkspaceID } - return nil + if err := options.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf("workspaces/%s/runs", url.QueryEscape(workspaceID)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + rl := &RunList{} + err = s.client.do(ctx, req, rl) + if err != nil { + return nil, err + } + + return rl, nil } // Create a new run with the given options. @@ -284,16 +331,14 @@ func (s *runs) Read(ctx context.Context, runID string) (*Run, error) { return s.ReadWithOptions(ctx, runID, nil) } -// RunReadOptions represents the options for reading a run. -type RunReadOptions struct { - Include string `url:"include"` -} - // Read a run by its ID with the given options. func (s *runs) ReadWithOptions(ctx context.Context, runID string, options *RunReadOptions) (*Run, error) { if !validStringID(&runID) { return nil, ErrInvalidRunID } + if err := options.valid(); err != nil { + return nil, err + } u := fmt.Sprintf("runs/%s", url.QueryEscape(runID)) req, err := s.client.newRequest("GET", u, options) @@ -310,12 +355,6 @@ func (s *runs) ReadWithOptions(ctx context.Context, runID string, options *RunRe return r, nil } -// RunApplyOptions represents the options for applying a run. -type RunApplyOptions struct { - // An optional comment about the run. - Comment *string `jsonapi:"attr,comment,omitempty"` -} - // Apply a run by its ID. func (s *runs) Apply(ctx context.Context, runID string, options RunApplyOptions) error { if !validStringID(&runID) { @@ -331,12 +370,6 @@ func (s *runs) Apply(ctx context.Context, runID string, options RunApplyOptions) return s.client.do(ctx, req, nil) } -// RunCancelOptions represents the options for canceling a run. -type RunCancelOptions struct { - // An optional explanation for why the run was canceled. - Comment *string `jsonapi:"attr,comment,omitempty"` -} - // Cancel a run by its ID. func (s *runs) Cancel(ctx context.Context, runID string, options RunCancelOptions) error { if !validStringID(&runID) { @@ -352,12 +385,6 @@ func (s *runs) Cancel(ctx context.Context, runID string, options RunCancelOption return s.client.do(ctx, req, nil) } -// RunForceCancelOptions represents the options for force-canceling a run. -type RunForceCancelOptions struct { - // An optional comment explaining the reason for the force-cancel. - Comment *string `jsonapi:"attr,comment,omitempty"` -} - // ForceCancel is used to forcefully cancel a run by its ID. func (s *runs) ForceCancel(ctx context.Context, runID string, options RunForceCancelOptions) error { if !validStringID(&runID) { @@ -373,12 +400,6 @@ func (s *runs) ForceCancel(ctx context.Context, runID string, options RunForceCa return s.client.do(ctx, req, nil) } -// RunDiscardOptions represents the options for discarding a run. -type RunDiscardOptions struct { - // An optional explanation for why the run was discarded. - Comment *string `jsonapi:"attr,comment,omitempty"` -} - // Discard a run by its ID. func (s *runs) Discard(ctx context.Context, runID string, options RunDiscardOptions) error { if !validStringID(&runID) { @@ -393,3 +414,45 @@ func (s *runs) Discard(ctx context.Context, runID string, options RunDiscardOpti return s.client.do(ctx, req, nil) } + +func (o RunCreateOptions) valid() error { + if o.Workspace == nil { + return ErrRequiredWorkspace + } + return nil +} + +func (o *RunReadOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateRunIncludeParam(o.Include); err != nil { + return err + } + return nil +} + +func (o *RunListOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateRunIncludeParam(o.Include); err != nil { + return err + } + return nil +} + +func validateRunIncludeParam(params []RunIncludeOpt) error { + for _, p := range params { + switch p { + case RunPlan, RunApply, RunCreatedBy, RunCostEstimate, RunConfigVer, RunConfigVerIngress, RunWorkspace, RunTaskStages: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/run_task.go b/vendor/github.com/hashicorp/go-tfe/run_task.go new file mode 100644 index 00000000..ffbaf0d1 --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/run_task.go @@ -0,0 +1,328 @@ +package tfe + +import ( + "context" + "fmt" + "net/url" +) + +// Compile-time proof of interface implementation +var _ RunTasks = (*runTasks)(nil) + +// RunTasks represents all the run task related methods in the context of an organization +// that the Terraform Cloud/Enterprise API supports. +// **Note: This API is still in BETA and subject to change.** +// https://www.terraform.io/cloud-docs/api-docs/run-tasks#run-tasks-api +type RunTasks interface { + // Create a run task for an organization + Create(ctx context.Context, organization string, options RunTaskCreateOptions) (*RunTask, error) + + // List all run tasks for an organization + List(ctx context.Context, organization string, options *RunTaskListOptions) (*RunTaskList, error) + + // Read an organization's run task by ID + Read(ctx context.Context, runTaskID string) (*RunTask, error) + + // Read an organization's run task by ID with given options + ReadWithOptions(ctx context.Context, runTaskID string, options *RunTaskReadOptions) (*RunTask, error) + + // Update a run task for an organization + Update(ctx context.Context, runTaskID string, options RunTaskUpdateOptions) (*RunTask, error) + + // Delete an organization's run task + Delete(ctx context.Context, runTaskID string) error + + // Attach a run task to an organization's workspace + AttachToWorkspace(ctx context.Context, workspaceID string, runTaskID string, enforcementLevel TaskEnforcementLevel) (*WorkspaceRunTask, error) +} + +// runTasks implements RunTasks +type runTasks struct { + client *Client +} + +// RunTask represents a TFC/E run task +type RunTask struct { + ID string `jsonapi:"primary,tasks"` + Name string `jsonapi:"attr,name"` + URL string `jsonapi:"attr,url"` + Category string `jsonapi:"attr,category"` + HMACKey *string `jsonapi:"attr,hmac-key,omitempty"` + Enabled bool `jsonapi:"attr,enabled"` + + Organization *Organization `jsonapi:"relation,organization"` + WorkspaceRunTasks []*WorkspaceRunTask `jsonapi:"relation,workspace-tasks"` +} + +// RunTaskList represents a list of run tasks +type RunTaskList struct { + *Pagination + Items []*RunTask +} + +// RunTaskIncludeOpt represents the available options for include query params. +// https://www.terraform.io/cloud-docs/api-docs/run-tasks#list-run-tasks +type RunTaskIncludeOpt string + +const ( + RunTaskWorkspaceTasks RunTaskIncludeOpt = "workspace_tasks" + RunTaskWorkspace RunTaskIncludeOpt = "workspace_tasks.workspace" +) + +// RunTaskListOptions represents the set of options for listing run tasks +type RunTaskListOptions struct { + ListOptions + // Optional: A list of relations to include with a run task. See available resources: + // https://www.terraform.io/cloud-docs/api-docs/run-tasks#list-run-tasks + Include []RunTaskIncludeOpt `url:"include,omitempty"` +} + +// RunTaskReadOptions represents the set of options for reading a run task +type RunTaskReadOptions struct { + // Optional: A list of relations to include with a run task. See available resources: + // https://www.terraform.io/cloud-docs/api-docs/run-tasks#list-run-tasks + Include []RunTaskIncludeOpt `url:"include,omitempty"` +} + +// RunTaskCreateOptions represents the set of options for creating a run task +type RunTaskCreateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,tasks"` + + // Required: The name of the run task + Name string `jsonapi:"attr,name"` + + // Required: The URL to send a run task payload + URL string `jsonapi:"attr,url"` + + // Required: Must be "task" + Category string `jsonapi:"attr,category"` + + // Optional: An HMAC key to verify the run task + HMACKey *string `jsonapi:"attr,hmac-key,omitempty"` + + // Optional: Whether the task should be enabled + Enabled *bool `jsonapi:"attr,enabled,omitempty"` +} + +// RunTaskUpdateOptions represents the set of options for updating an organization's run task +type RunTaskUpdateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,tasks"` + + // Optional: The name of the run task, defaults to previous value + Name *string `jsonapi:"attr,name,omitempty"` + + // Optional: The URL to send a run task payload, defaults to previous value + URL *string `jsonapi:"attr,url,omitempty"` + + // Optional: Must be "task", defaults to "task" + Category *string `jsonapi:"attr,category,omitempty"` + + // Optional: An HMAC key to verify the run task + HMACKey *string `jsonapi:"attr,hmac-key,omitempty"` + + // Optional: Whether the task should be enabled + Enabled *bool `jsonapi:"attr,enabled,omitempty"` +} + +// Create is used to create a new run task for an organization +func (s *runTasks) Create(ctx context.Context, organization string, options RunTaskCreateOptions) (*RunTask, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg + } + + if err := options.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf("organizations/%s/tasks", url.QueryEscape(organization)) + req, err := s.client.newRequest("POST", u, &options) + if err != nil { + return nil, err + } + + r := &RunTask{} + err = s.client.do(ctx, req, r) + if err != nil { + return nil, err + } + + return r, nil +} + +// List all the run tasks for an organization +func (s *runTasks) List(ctx context.Context, organization string, options *RunTaskListOptions) (*RunTaskList, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg + } + if err := options.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf("organizations/%s/tasks", url.QueryEscape(organization)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + rl := &RunTaskList{} + err = s.client.do(ctx, req, rl) + if err != nil { + return nil, err + } + + return rl, nil +} + +// Read is used to read an organization's run task by ID +func (s *runTasks) Read(ctx context.Context, runTaskID string) (*RunTask, error) { + return s.ReadWithOptions(ctx, runTaskID, nil) +} + +// Read is used to read an organization's run task by ID with options +func (s *runTasks) ReadWithOptions(ctx context.Context, runTaskID string, options *RunTaskReadOptions) (*RunTask, error) { + if !validStringID(&runTaskID) { + return nil, ErrInvalidRunTaskID + } + if err := options.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf("tasks/%s", url.QueryEscape(runTaskID)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + r := &RunTask{} + err = s.client.do(ctx, req, r) + if err != nil { + return nil, err + } + + return r, nil +} + +// Update an existing run task for an organization by ID +func (s *runTasks) Update(ctx context.Context, runTaskID string, options RunTaskUpdateOptions) (*RunTask, error) { + if !validStringID(&runTaskID) { + return nil, ErrInvalidRunTaskID + } + + if err := options.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf("tasks/%s", url.QueryEscape(runTaskID)) + req, err := s.client.newRequest("PATCH", u, &options) + if err != nil { + return nil, err + } + + r := &RunTask{} + err = s.client.do(ctx, req, r) + if err != nil { + return nil, err + } + + return r, nil +} + +// Delete an existing run task for an organization by ID +func (s *runTasks) Delete(ctx context.Context, runTaskID string) error { + if !validStringID(&runTaskID) { + return ErrInvalidRunTaskID + } + + u := fmt.Sprintf("tasks/%s", runTaskID) + req, err := s.client.newRequest("DELETE", u, nil) + if err != nil { + return err + } + + return s.client.do(ctx, req, nil) +} + +// AttachToWorkspace is a convenient method to attach a run task to a workspace. See: WorkspaceRunTasks.Create() +func (s *runTasks) AttachToWorkspace(ctx context.Context, workspaceID, runTaskID string, enforcement TaskEnforcementLevel) (*WorkspaceRunTask, error) { + return s.client.WorkspaceRunTasks.Create(ctx, workspaceID, WorkspaceRunTaskCreateOptions{ + EnforcementLevel: enforcement, + RunTask: &RunTask{ID: runTaskID}, + }) +} + +func (o *RunTaskCreateOptions) valid() error { + if !validString(&o.Name) { + return ErrRequiredName + } + + if !validString(&o.URL) { + return ErrInvalidRunTaskURL + } + + if o.Category != "task" { + return ErrInvalidRunTaskCategory + } + + return nil +} + +func (o *RunTaskUpdateOptions) valid() error { + if o.Name != nil && !validString(o.Name) { + return ErrRequiredName + } + + if o.URL != nil && !validString(o.URL) { + return ErrInvalidRunTaskURL + } + + if o.Category != nil && *o.Category != "task" { + return ErrInvalidRunTaskCategory + } + + return nil +} + +func (o *RunTaskListOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateRunTaskIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func (o *RunTaskReadOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateRunTaskIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func validateRunTaskIncludeParams(params []RunTaskIncludeOpt) error { + for _, p := range params { + switch p { + case RunTaskWorkspaceTasks, RunTaskWorkspace: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/run_trigger.go b/vendor/github.com/hashicorp/go-tfe/run_trigger.go index eb8005a1..f0c77730 100644 --- a/vendor/github.com/hashicorp/go-tfe/run_trigger.go +++ b/vendor/github.com/hashicorp/go-tfe/run_trigger.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" "time" @@ -18,7 +17,7 @@ var _ RunTriggers = (*runTriggers)(nil) // https://www.terraform.io/docs/cloud/api/run-triggers.html type RunTriggers interface { // List all the run triggers within a workspace. - List(ctx context.Context, workspaceID string, options RunTriggerListOptions) (*RunTriggerList, error) + List(ctx context.Context, workspaceID string, options *RunTriggerListOptions) (*RunTriggerList, error) // Create a new run trigger with the given options. Create(ctx context.Context, workspaceID string, options RunTriggerCreateOptions) (*RunTrigger, error) @@ -54,29 +53,49 @@ type RunTrigger struct { Workspace *Workspace `jsonapi:"relation,workspace"` } +// https://www.terraform.io/cloud-docs/api-docs/run-triggers#query-parameters +type RunTriggerFilterOp string + +const ( + RunTriggerOutbound RunTriggerFilterOp = "outbound" // create runs in other workspaces. + RunTriggerInbound RunTriggerFilterOp = "inbound" // create runs in the specified workspace +) + +// A list of relations to include +// https://www.terraform.io/cloud-docs/api-docs/run-triggers#available-related-resources +type RunTriggerIncludeOpt string + +const ( + RunTriggerWorkspace RunTriggerIncludeOpt = "workspace" + RunTriggerSourceable RunTriggerIncludeOpt = "sourceable" +) + // RunTriggerListOptions represents the options for listing // run triggers. type RunTriggerListOptions struct { ListOptions - RunTriggerType *string `url:"filter[run-trigger][type]"` + RunTriggerType RunTriggerFilterOp `url:"filter[run-trigger][type]"` // Required + Include []RunTriggerIncludeOpt `url:"include,omitempty"` // optional } -func (o RunTriggerListOptions) valid() error { - if !validString(o.RunTriggerType) { - return errors.New("run-trigger type is required") - } - if *o.RunTriggerType != "inbound" && *o.RunTriggerType != "outbound" { - return errors.New("invalid value for run-trigger type") - } - return nil +// RunTriggerCreateOptions represents the options for +// creating a new run trigger. +type RunTriggerCreateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,run-triggers"` + + // The source workspace + Sourceable *Workspace `jsonapi:"relation,sourceable"` } // List all the run triggers associated with a workspace. -func (s *runTriggers) List(ctx context.Context, workspaceID string, options RunTriggerListOptions) (*RunTriggerList, error) { +func (s *runTriggers) List(ctx context.Context, workspaceID string, options *RunTriggerListOptions) (*RunTriggerList, error) { if !validStringID(&workspaceID) { return nil, ErrInvalidWorkspaceID } - if err := options.valid(); err != nil { return nil, err } @@ -96,27 +115,7 @@ func (s *runTriggers) List(ctx context.Context, workspaceID string, options RunT return rtl, nil } -// RunTriggerCreateOptions represents the options for -// creating a new run trigger. -type RunTriggerCreateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,run-triggers"` - - // The source workspace - Sourceable *Workspace `jsonapi:"relation,sourceable"` -} - -func (o RunTriggerCreateOptions) valid() error { - if o.Sourceable == nil { - return errors.New("sourceable is required") - } - return nil -} - -// Creates a run trigger with the given options. +// Create a run trigger with the given options. func (s *runTriggers) Create(ctx context.Context, workspaceID string, options RunTriggerCreateOptions) (*RunTrigger, error) { if !validStringID(&workspaceID) { return nil, ErrInvalidWorkspaceID @@ -143,7 +142,7 @@ func (s *runTriggers) Create(ctx context.Context, workspaceID string, options Ru // Read a run trigger by its ID. func (s *runTriggers) Read(ctx context.Context, runTriggerID string) (*RunTrigger, error) { if !validStringID(&runTriggerID) { - return nil, errors.New("invalid value for run trigger ID") + return nil, ErrInvalidRunTriggerID } u := fmt.Sprintf("run-triggers/%s", url.QueryEscape(runTriggerID)) @@ -164,7 +163,7 @@ func (s *runTriggers) Read(ctx context.Context, runTriggerID string) (*RunTrigge // Delete a run trigger by its ID. func (s *runTriggers) Delete(ctx context.Context, runTriggerID string) error { if !validStringID(&runTriggerID) { - return errors.New("invalid value for run trigger ID") + return ErrInvalidRunTriggerID } u := fmt.Sprintf("run-triggers/%s", url.QueryEscape(runTriggerID)) @@ -175,3 +174,56 @@ func (s *runTriggers) Delete(ctx context.Context, runTriggerID string) error { return s.client.do(ctx, req, nil) } + +func (o RunTriggerCreateOptions) valid() error { + if o.Sourceable == nil { + return ErrRequiredSourceable + } + return nil +} + +func (o *RunTriggerListOptions) valid() error { + if o == nil { + return ErrRequiredRunTriggerListOps + } + + if err := validateRunTriggerFilterParam(o.RunTriggerType, o.Include); err != nil { + return err + } + + if err := validateRunTriggerIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func validateRunTriggerFilterParam(filterParam RunTriggerFilterOp, includeParams []RunTriggerIncludeOpt) error { + switch filterParam { + case RunTriggerOutbound, RunTriggerInbound: + // Do nothing + default: + return ErrInvalidRunTriggerType // return an error even if string is empty because this a required field + } + + if len(includeParams) > 0 { + if filterParam != RunTriggerInbound { + return ErrUnsupportedRunTriggerType // if user passes RunTriggerOutbound the platform will not return any "include" data + } + } + + return nil +} + +func validateRunTriggerIncludeParams(params []RunTriggerIncludeOpt) error { + for _, p := range params { + switch p { + case RunTriggerWorkspace, RunTriggerSourceable: + // Do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/ssh_key.go b/vendor/github.com/hashicorp/go-tfe/ssh_key.go index d768c6e9..d2faea2d 100644 --- a/vendor/github.com/hashicorp/go-tfe/ssh_key.go +++ b/vendor/github.com/hashicorp/go-tfe/ssh_key.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" ) @@ -17,7 +16,7 @@ var _ SSHKeys = (*sshKeys)(nil) // https://www.terraform.io/docs/cloud/api/ssh-keys.html type SSHKeys interface { // List all the SSH keys for a given organization - List(ctx context.Context, organization string, options SSHKeyListOptions) (*SSHKeyList, error) + List(ctx context.Context, organization string, options *SSHKeyListOptions) (*SSHKeyList, error) // Create an SSH key and associate it with an organization. Create(ctx context.Context, organization string, options SSHKeyCreateOptions) (*SSHKey, error) @@ -54,27 +53,6 @@ type SSHKeyListOptions struct { ListOptions } -// List all the SSH keys for a given organization -func (s *sshKeys) List(ctx context.Context, organization string, options SSHKeyListOptions) (*SSHKeyList, error) { - if !validStringID(&organization) { - return nil, ErrInvalidOrg - } - - u := fmt.Sprintf("organizations/%s/ssh-keys", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - kl := &SSHKeyList{} - err = s.client.do(ctx, req, kl) - if err != nil { - return nil, err - } - - return kl, nil -} - // SSHKeyCreateOptions represents the options for creating an SSH key. type SSHKeyCreateOptions struct { // Type is a public field utilized by JSON:API to @@ -90,14 +68,34 @@ type SSHKeyCreateOptions struct { Value *string `jsonapi:"attr,value"` } -func (o SSHKeyCreateOptions) valid() error { - if !validString(o.Name) { - return ErrRequiredName +// SSHKeyUpdateOptions represents the options for updating an SSH key. +type SSHKeyUpdateOptions struct { + // For internal use only! + ID string `jsonapi:"primary,ssh-keys"` + + // Optional: A new name to identify the SSH key. + Name *string `jsonapi:"attr,name,omitempty"` +} + +// List all the SSH keys for a given organization +func (s *sshKeys) List(ctx context.Context, organization string, options *SSHKeyListOptions) (*SSHKeyList, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg } - if !validString(o.Value) { - return errors.New("value is required") + + u := fmt.Sprintf("organizations/%s/ssh-keys", url.QueryEscape(organization)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err } - return nil + + kl := &SSHKeyList{} + err = s.client.do(ctx, req, kl) + if err != nil { + return nil, err + } + + return kl, nil } // Create an SSH key and associate it with an organization. @@ -128,7 +126,7 @@ func (s *sshKeys) Create(ctx context.Context, organization string, options SSHKe // Read an SSH key by its ID. func (s *sshKeys) Read(ctx context.Context, sshKeyID string) (*SSHKey, error) { if !validStringID(&sshKeyID) { - return nil, errors.New("invalid value for SSH key ID") + return nil, ErrInvalidSHHKeyID } u := fmt.Sprintf("ssh-keys/%s", url.QueryEscape(sshKeyID)) @@ -146,27 +144,12 @@ func (s *sshKeys) Read(ctx context.Context, sshKeyID string) (*SSHKey, error) { return k, nil } -// SSHKeyUpdateOptions represents the options for updating an SSH key. -type SSHKeyUpdateOptions struct { - // For internal use only! - ID string `jsonapi:"primary,ssh-keys"` - - // A new name to identify the SSH key. - Name *string `jsonapi:"attr,name,omitempty"` - - // Updated content of the SSH private key. - Value *string `jsonapi:"attr,value,omitempty"` -} - // Update an SSH key by its ID. func (s *sshKeys) Update(ctx context.Context, sshKeyID string, options SSHKeyUpdateOptions) (*SSHKey, error) { if !validStringID(&sshKeyID) { - return nil, errors.New("invalid value for SSH key ID") + return nil, ErrInvalidSHHKeyID } - // Make sure we don't send a user provided ID. - options.ID = "" - u := fmt.Sprintf("ssh-keys/%s", url.QueryEscape(sshKeyID)) req, err := s.client.newRequest("PATCH", u, &options) if err != nil { @@ -185,7 +168,7 @@ func (s *sshKeys) Update(ctx context.Context, sshKeyID string, options SSHKeyUpd // Delete an SSH key by its ID. func (s *sshKeys) Delete(ctx context.Context, sshKeyID string) error { if !validStringID(&sshKeyID) { - return errors.New("invalid value for SSH key ID") + return ErrInvalidSHHKeyID } u := fmt.Sprintf("ssh-keys/%s", url.QueryEscape(sshKeyID)) @@ -196,3 +179,13 @@ func (s *sshKeys) Delete(ctx context.Context, sshKeyID string) error { return s.client.do(ctx, req, nil) } + +func (o SSHKeyCreateOptions) valid() error { + if !validString(o.Name) { + return ErrRequiredName + } + if !validString(o.Value) { + return ErrRequiredValue + } + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/state_version.go b/vendor/github.com/hashicorp/go-tfe/state_version.go index 0637c867..a2aaf0aa 100644 --- a/vendor/github.com/hashicorp/go-tfe/state_version.go +++ b/vendor/github.com/hashicorp/go-tfe/state_version.go @@ -3,7 +3,7 @@ package tfe import ( "bytes" "context" - "errors" + "encoding/json" "fmt" "net/url" "time" @@ -19,7 +19,7 @@ var _ StateVersions = (*stateVersions)(nil) // https://www.terraform.io/docs/cloud/api/state-versions.html type StateVersions interface { // List all the state versions for a given workspace. - List(ctx context.Context, options StateVersionListOptions) (*StateVersionList, error) + List(ctx context.Context, options *StateVersionListOptions) (*StateVersionList, error) // Create a new state version for the given workspace. Create(ctx context.Context, workspaceID string, options StateVersionCreateOptions) (*StateVersion, error) @@ -30,17 +30,17 @@ type StateVersions interface { // ReadWithOptions reads a state version by its ID using the options supplied ReadWithOptions(ctx context.Context, svID string, options *StateVersionReadOptions) (*StateVersion, error) - // Current reads the latest available state from the given workspace. - Current(ctx context.Context, workspaceID string) (*StateVersion, error) + // ReadCurrent reads the latest available state from the given workspace. + ReadCurrent(ctx context.Context, workspaceID string) (*StateVersion, error) - // CurrentWithOptions reads the latest available state from the given workspace using the options supplied - CurrentWithOptions(ctx context.Context, workspaceID string, options *StateVersionCurrentOptions) (*StateVersion, error) + // ReadCurrentWithOptions reads the latest available state from the given workspace using the options supplied + ReadCurrentWithOptions(ctx context.Context, workspaceID string, options *StateVersionCurrentOptions) (*StateVersion, error) // Download retrieves the actual stored state of a state version Download(ctx context.Context, url string) ([]byte, error) - // Outputs retrieves all the outputs of a state version by its ID. - Outputs(ctx context.Context, svID string, options StateVersionOutputsListOptions) ([]*StateVersionOutput, error) + // ListOutputs retrieves all the outputs of a state version by its ID. + ListOutputs(ctx context.Context, svID string, options *StateVersionOutputsListOptions) (*StateVersionOutputsList, error) } // stateVersions implements StateVersions. @@ -68,41 +68,49 @@ type StateVersion struct { Outputs []*StateVersionOutput `jsonapi:"relation,outputs"` } +// StateVersionOutputsList represents a list of StateVersionOutput items. +type StateVersionOutputsList struct { + *Pagination + Items []*StateVersionOutput +} + // StateVersionListOptions represents the options for listing state versions. type StateVersionListOptions struct { ListOptions - Organization *string `url:"filter[organization][name]"` - Workspace *string `url:"filter[workspace][name]"` + Organization string `url:"filter[organization][name]"` + Workspace string `url:"filter[workspace][name]"` } -func (o StateVersionListOptions) valid() error { - if !validString(o.Organization) { - return errors.New("organization is required") - } - if !validString(o.Workspace) { - return errors.New("workspace is required") - } - return nil -} +// StateVersionIncludeOpt represents the available options for include query params. +// https://www.terraform.io/cloud-docs/api-docs/state-versions#available-related-resources +type StateVersionIncludeOpt string -// List all the state versions for a given workspace. -func (s *stateVersions) List(ctx context.Context, options StateVersionListOptions) (*StateVersionList, error) { - if err := options.valid(); err != nil { - return nil, err - } +const ( + SVcreatedby StateVersionIncludeOpt = "created_by" + SVrun StateVersionIncludeOpt = "run" + SVrunCreatedBy StateVersionIncludeOpt = "run.created_by" + SVrunConfigurationVersion StateVersionIncludeOpt = "run.configuration_version" + SVoutputs StateVersionIncludeOpt = "outputs" +) - req, err := s.client.newRequest("GET", "state-versions", &options) - if err != nil { - return nil, err - } +// StateVersionReadOptions represents the options for reading state version. +type StateVersionReadOptions struct { + // Optional: A list of relations to include. See available resources: + // https://www.terraform.io/cloud-docs/api-docs/state-versions#available-related-resources + Include []StateVersionIncludeOpt `url:"include,omitempty"` +} - svl := &StateVersionList{} - err = s.client.do(ctx, req, svl) - if err != nil { - return nil, err - } +// StateVersionOutputsListOptions represents the options for listing state +// version outputs. +type StateVersionOutputsListOptions struct { + ListOptions +} - return svl, nil +// StateVersionCurrentOptions represents the options for reading the current state version. +type StateVersionCurrentOptions struct { + // Optional: A list of relations to include. See available resources: + // https://www.terraform.io/cloud-docs/api-docs/state-versions#available-related-resources + Include []StateVersionIncludeOpt `url:"include,omitempty"` } // StateVersionCreateOptions represents the options for creating a state version. @@ -113,37 +121,52 @@ type StateVersionCreateOptions struct { // https://jsonapi.org/format/#crud-creating Type string `jsonapi:"primary,state-versions"` - // The lineage of the state. + // Optional: The lineage of the state. Lineage *string `jsonapi:"attr,lineage,omitempty"` - // The MD5 hash of the state version. + // Required: The MD5 hash of the state version. MD5 *string `jsonapi:"attr,md5"` - // The serial of the state. + // Required: The serial of the state. Serial *int64 `jsonapi:"attr,serial"` - // The base64 encoded state. + // Required: The base64 encoded state. State *string `jsonapi:"attr,state"` - // Force can be set to skip certain validations. Wrong use + // Optional: Force can be set to skip certain validations. Wrong use // of this flag can cause data loss, so USE WITH CAUTION! - Force *bool `jsonapi:"attr,force"` + Force *bool `jsonapi:"attr,force,omitempty"` - // Specifies the run to associate the state with. + // Optional: Specifies the run to associate the state with. Run *Run `jsonapi:"relation,run,omitempty"` + + // Optional: The external, json representation of state data. + // https://www.terraform.io/internals/json-format#state-representation + // Supplying this state representation can provide more details to the platform + // about the current terraform state. + // + // **Note**: This field is in BETA, subject to change and not widely available yet. + ExtState json.RawMessage `jsonapi:"attr,ext-state,omitempty"` } -func (o StateVersionCreateOptions) valid() error { - if !validString(o.MD5) { - return errors.New("MD5 is required") +// List all the state versions for a given workspace. +func (s *stateVersions) List(ctx context.Context, options *StateVersionListOptions) (*StateVersionList, error) { + if err := options.valid(); err != nil { + return nil, err } - if o.Serial == nil { - return errors.New("serial is required") + + req, err := s.client.newRequest("GET", "state-versions", options) + if err != nil { + return nil, err } - if !validString(o.State) { - return errors.New("state is required") + + svl := &StateVersionList{} + err = s.client.do(ctx, req, svl) + if err != nil { + return nil, err } - return nil + + return svl, nil } // Create a new state version for the given workspace. @@ -170,15 +193,13 @@ func (s *stateVersions) Create(ctx context.Context, workspaceID string, options return sv, nil } -// StateVersionReadOptions represents the options for reading state version. -type StateVersionReadOptions struct { - Include string `url:"include"` -} - // Read a state version by its ID. func (s *stateVersions) ReadWithOptions(ctx context.Context, svID string, options *StateVersionReadOptions) (*StateVersion, error) { if !validStringID(&svID) { - return nil, errors.New("invalid value for state version ID") + return nil, ErrInvalidStateVerID + } + if err := options.valid(); err != nil { + return nil, err } u := fmt.Sprintf("state-versions/%s", url.QueryEscape(svID)) @@ -201,16 +222,14 @@ func (s *stateVersions) Read(ctx context.Context, svID string) (*StateVersion, e return s.ReadWithOptions(ctx, svID, nil) } -// StateVersionCurrentOptions represents the options for reading the current state version. -type StateVersionCurrentOptions struct { - Include string `url:"include"` -} - -// CurrentWithOptions reads the latest available state from the given workspace using the options supplied. -func (s *stateVersions) CurrentWithOptions(ctx context.Context, workspaceID string, options *StateVersionCurrentOptions) (*StateVersion, error) { +// ReadCurrentWithOptions reads the latest available state from the given workspace using the options supplied. +func (s *stateVersions) ReadCurrentWithOptions(ctx context.Context, workspaceID string, options *StateVersionCurrentOptions) (*StateVersion, error) { if !validStringID(&workspaceID) { return nil, ErrInvalidWorkspaceID } + if err := options.valid(); err != nil { + return nil, err + } u := fmt.Sprintf("workspaces/%s/current-state-version", url.QueryEscape(workspaceID)) req, err := s.client.newRequest("GET", u, options) @@ -227,14 +246,14 @@ func (s *stateVersions) CurrentWithOptions(ctx context.Context, workspaceID stri return sv, nil } -// Current reads the latest available state from the given workspace. -func (s *stateVersions) Current(ctx context.Context, workspaceID string) (*StateVersion, error) { - return s.CurrentWithOptions(ctx, workspaceID, nil) +// ReadCurrent reads the latest available state from the given workspace. +func (s *stateVersions) ReadCurrent(ctx context.Context, workspaceID string) (*StateVersion, error) { + return s.ReadCurrentWithOptions(ctx, workspaceID, nil) } // Download retrieves the actual stored state of a state version -func (s *stateVersions) Download(ctx context.Context, url string) ([]byte, error) { - req, err := s.client.newRequest("GET", url, nil) +func (s *stateVersions) Download(ctx context.Context, u string) ([]byte, error) { + req, err := s.client.newRequest("GET", u, nil) if err != nil { return nil, err } @@ -249,22 +268,10 @@ func (s *stateVersions) Download(ctx context.Context, url string) ([]byte, error return buf.Bytes(), nil } -// StateVersionOutputsList represents a list of StateVersionOutput items. -type StateVersionOutputsList struct { - *Pagination - Items []*StateVersionOutput -} - -// StateVersionOutputsListOptions represents the options for listing state -// version outputs. -type StateVersionOutputsListOptions struct { - ListOptions -} - -// Outputs retrieves all the outputs of a state version by its ID. -func (s *stateVersions) Outputs(ctx context.Context, svID string, options StateVersionOutputsListOptions) ([]*StateVersionOutput, error) { +// ListOutputs retrieves all the outputs of a state version by its ID. +func (s *stateVersions) ListOutputs(ctx context.Context, svID string, options *StateVersionOutputsListOptions) (*StateVersionOutputsList, error) { if !validStringID(&svID) { - return nil, errors.New("invalid value for state version ID") + return nil, ErrInvalidStateVerID } u := fmt.Sprintf("state-versions/%s/outputs", url.QueryEscape(svID)) @@ -279,5 +286,66 @@ func (s *stateVersions) Outputs(ctx context.Context, svID string, options StateV return nil, err } - return sv.Items, nil + return sv, nil +} + +// check that StateVersionListOptions fields had valid values +func (o *StateVersionListOptions) valid() error { + if o == nil { + return ErrRequiredStateVerListOps + } + if !validString(&o.Organization) { + return ErrRequiredOrg + } + if !validString(&o.Workspace) { + return ErrRequiredWorkspace + } + return nil +} + +func (o StateVersionCreateOptions) valid() error { + if !validString(o.MD5) { + return ErrRequiredM5 + } + if o.Serial == nil { + return ErrRequiredSerial + } + if !validString(o.State) { + return ErrRequiredState + } + return nil +} + +func (o *StateVersionReadOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateStateVerIncludeParams(o.Include); err != nil { + return err + } + return nil +} +func (o *StateVersionCurrentOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateStateVerIncludeParams(o.Include); err != nil { + return err + } + return nil +} + +func validateStateVerIncludeParams(params []StateVersionIncludeOpt) error { + for _, p := range params { + switch p { + case SVcreatedby, SVrun, SVrunCreatedBy, SVrunConfigurationVersion, SVoutputs: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil } diff --git a/vendor/github.com/hashicorp/go-tfe/state_version_output.go b/vendor/github.com/hashicorp/go-tfe/state_version_output.go index b88c05a7..e6a86cbe 100644 --- a/vendor/github.com/hashicorp/go-tfe/state_version_output.go +++ b/vendor/github.com/hashicorp/go-tfe/state_version_output.go @@ -9,19 +9,22 @@ import ( // Compile-time proof of interface implementation. var _ StateVersionOutputs = (*stateVersionOutputs)(nil) -//State version outputs are the output values from a Terraform state file. -//They include the name and value of the output, as well as a sensitive boolean -//if the value should be hidden by default in UIs. +// State version outputs are the output values from a Terraform state file. +// They include the name and value of the output, as well as a sensitive boolean +// if the value should be hidden by default in UIs. // // TFE API docs: https://www.terraform.io/docs/cloud/api/state-version-outputs.html type StateVersionOutputs interface { Read(ctx context.Context, outputID string) (*StateVersionOutput, error) + ReadCurrent(ctx context.Context, workspaceID string) (*StateVersionOutputsList, error) } +// stateVersionOutputs implements StateVersionOutputs. type stateVersionOutputs struct { client *Client } +// StateVersionOutput represents a State Version Outputs type StateVersionOutput struct { ID string `jsonapi:"primary,state-version-outputs"` Name string `jsonapi:"attr,name"` @@ -30,9 +33,31 @@ type StateVersionOutput struct { Value interface{} `jsonapi:"attr,value"` } +// ReadCurrent reads the current state version outputs for the specified workspace +func (s *stateVersionOutputs) ReadCurrent(ctx context.Context, workspaceID string) (*StateVersionOutputsList, error) { + if !validStringID(&workspaceID) { + return nil, ErrInvalidWorkspaceID + } + + u := fmt.Sprintf("workspaces/%s/current-state-version-outputs", url.QueryEscape(workspaceID)) + req, err := s.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + so := &StateVersionOutputsList{} + err = s.client.do(ctx, req, so) + if err != nil { + return nil, err + } + + return so, nil +} + +// Read a State Version Output func (s *stateVersionOutputs) Read(ctx context.Context, outputID string) (*StateVersionOutput, error) { if !validStringID(&outputID) { - return nil, ErrInvalidRunID + return nil, ErrInvalidOutputID } u := fmt.Sprintf("state-version-outputs/%s", url.QueryEscape(outputID)) diff --git a/vendor/github.com/hashicorp/go-tfe/task_result.go b/vendor/github.com/hashicorp/go-tfe/task_result.go new file mode 100644 index 00000000..4abc3670 --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/task_result.go @@ -0,0 +1,90 @@ +package tfe + +import ( + "context" + "fmt" + "time" +) + +// Compile-time proof of interface implementation +var _ TaskResults = (*taskResults)(nil) + +// TaskResults describes all the task result related methods that the TFC/E API supports. +// **Note**: This API is still in BETA and is subject to change +type TaskResults interface { + // Read a task result by ID + Read(ctx context.Context, taskResultID string) (*TaskResult, error) +} + +// taskResults implements TaskResults +type taskResults struct { + client *Client +} + +// TaskResultStatus is an enum that represents all possible statuses for a task result +type TaskResultStatus string + +const ( + TaskPassed TaskResultStatus = "passed" + TaskFailed TaskResultStatus = "failed" + TaskPending TaskResultStatus = "pending" + TaskRunning TaskResultStatus = "running" + TaskUnreachable TaskResultStatus = "unreachable" +) + +// TaskEnforcementLevel is an enum that describes the enforcement levels for a run task +type TaskEnforcementLevel string + +const ( + Advisory TaskEnforcementLevel = "advisory" + Mandatory TaskEnforcementLevel = "mandatory" +) + +// TaskResultStatusTimestamps represents the set of timestamps recorded for a task result +type TaskResultStatusTimestamps struct { + ErroredAt time.Time `jsonapi:"attr,errored-at,rfc3339"` + RunningAt time.Time `jsonapi:"attr,running-at,rfc3339"` + CanceledAt time.Time `jsonapi:"attr,canceled-at,rfc3339"` + FailedAt time.Time `jsonapi:"attr,failed-at,rfc3339"` + PassedAt time.Time `jsonapi:"attr,passed-at,rfc3339"` +} + +// TaskResult represents the result of a TFC/E run task +type TaskResult struct { + ID string `jsonapi:"primary,task-results"` + Status TaskResultStatus `jsonapi:"attr,status"` + Message string `jsonapi:"attr,message"` + StatusTimestamps TaskResultStatusTimestamps `jsonapi:"attr,status-timestamps"` + URL string `jsonapi:"attr,url"` + CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` + UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"` + TaskID string `jsonapi:"attr,task-id"` + TaskName string `jsonapi:"attr,task-name"` + TaskURL string `jsonapi:"attr,task-url"` + WorkspaceTaskID string `jsonapi:"attr,workspace-task-id"` + WorkspaceTaskEnforcementLevel TaskEnforcementLevel `jsonapi:"attr,workspace-task-enforcement-level"` + + // The task stage this result belongs to + TaskStage *TaskStage `jsonapi:"relation,task_stage"` +} + +// Read a task result by ID +func (t *taskResults) Read(ctx context.Context, taskResultID string) (*TaskResult, error) { + if !validStringID(&taskResultID) { + return nil, ErrInvalidTaskResultID + } + + u := fmt.Sprintf("task-results/%s", taskResultID) + req, err := t.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + r := &TaskResult{} + err = t.client.do(ctx, req, r) + if err != nil { + return nil, err + } + + return r, nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/task_stages.go b/vendor/github.com/hashicorp/go-tfe/task_stages.go new file mode 100644 index 00000000..d6699a45 --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/task_stages.go @@ -0,0 +1,147 @@ +package tfe + +import ( + "context" + "fmt" + "time" +) + +// Compile-time proof of interface implementation +var _ TaskStages = (*taskStages)(nil) + +// TaskStages describes all the task stage related methods that the TFC/E API +// supports. +// **Note: This API is still in BETA and is subject to change.** +type TaskStages interface { + // Read a task stage by ID + Read(ctx context.Context, taskStageID string, options *TaskStageReadOptions) (*TaskStage, error) + + // List all task stages for a given rrun + List(ctx context.Context, runID string, options *TaskStageListOptions) (*TaskStageList, error) +} + +// taskStages implements TaskStages +type taskStages struct { + client *Client +} + +// Stage is an enum that represents the possible run stages for run tasks +type Stage string + +const ( + PostPlan Stage = "post_plan" +) + +// TaskStage represents a TFC/E run's stage where run tasks can occur +type TaskStage struct { + ID string `jsonapi:"primary,task-stages"` + Stage Stage `jsonapi:"attr,stage"` + StatusTimestamps TaskStageStatusTimestamps `jsonapi:"attr,status-timestamps"` + CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` + UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"` + + Run *Run `jsonapi:"relation,run"` + TaskResults []*TaskResult `jsonapi:"relation,task-results"` +} + +// TaskStageList represents a list of task stages +type TaskStageList struct { + *Pagination + Items []*TaskStage +} + +// TaskStageStatusTimestamps represents the set of timestamps recorded for a task stage +type TaskStageStatusTimestamps struct { + ErroredAt time.Time `jsonapi:"attr,errored-at,rfc3339"` + RunningAt time.Time `jsonapi:"attr,running-at,rfc3339"` + CanceledAt time.Time `jsonapi:"attr,canceled-at,rfc3339"` + FailedAt time.Time `jsonapi:"attr,failed-at,rfc3339"` + PassedAt time.Time `jsonapi:"attr,passed-at,rfc3339"` +} + +// TaskStageIncludeOpt represents the available options for include query params. +type TaskStageIncludeOpt string + +const TaskStageTaskResults TaskStageIncludeOpt = "task_results" + +// TaskStageReadOptions represents the set of options when reading a task stage +type TaskStageReadOptions struct { + // Optional: A list of relations to include. + Include []TaskStageIncludeOpt `url:"include,omitempty"` +} + +// TaskStageListOptions represents the options for listing task stages for a run +type TaskStageListOptions struct { + ListOptions +} + +// Read a task stage by ID +func (s *taskStages) Read(ctx context.Context, taskStageID string, options *TaskStageReadOptions) (*TaskStage, error) { + if !validStringID(&taskStageID) { + return nil, ErrInvalidTaskStageID + } + if err := options.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf("task-stages/%s", taskStageID) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + t := &TaskStage{} + err = s.client.do(ctx, req, t) + if err != nil { + return nil, err + } + + return t, nil +} + +// List task stages for a run +func (s *taskStages) List(ctx context.Context, runID string, options *TaskStageListOptions) (*TaskStageList, error) { + if !validStringID(&runID) { + return nil, ErrInvalidRunID + } + + u := fmt.Sprintf("runs/%s/task-stages", runID) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + tlist := &TaskStageList{} + + err = s.client.do(ctx, req, tlist) + if err != nil { + return nil, err + } + + return tlist, nil +} + +func (o *TaskStageReadOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateTaskStageIncludeParams(o.Include); err != nil { + return err + } + + return nil +} + +func validateTaskStageIncludeParams(params []TaskStageIncludeOpt) error { + for _, p := range params { + switch p { + case TaskStageTaskResults: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/team.go b/vendor/github.com/hashicorp/go-tfe/team.go index 9fbecbea..a9c08244 100644 --- a/vendor/github.com/hashicorp/go-tfe/team.go +++ b/vendor/github.com/hashicorp/go-tfe/team.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" ) @@ -16,7 +15,7 @@ var _ Teams = (*teams)(nil) // TFE API docs: https://www.terraform.io/docs/cloud/api/teams.html type Teams interface { // List all the teams of the given organization. - List(ctx context.Context, organization string, options TeamListOptions) (*TeamList, error) + List(ctx context.Context, organization string, options *TeamListOptions) (*TeamList, error) // Create a new team with the given options. Create(ctx context.Context, organization string, options TeamCreateOptions) (*Team, error) @@ -50,6 +49,7 @@ type Team struct { Visibility string `jsonapi:"attr,visibility"` Permissions *TeamPermissions `jsonapi:"attr,permissions"` UserCount int `jsonapi:"attr,users-count"` + SSOTeamID string `jsonapi:"attr,sso-team-id"` // Relations Users []*User `jsonapi:"relation,users"` @@ -62,6 +62,9 @@ type OrganizationAccess struct { ManagePolicyOverrides bool `jsonapi:"attr,manage-policy-overrides"` ManageWorkspaces bool `jsonapi:"attr,manage-workspaces"` ManageVCSSettings bool `jsonapi:"attr,manage-vcs-settings"` + ManageProviders bool `jsonapi:"attr,manage-providers"` + ManageModules bool `jsonapi:"attr,manage-modules"` + ManageRunTasks bool `jsonapi:"attr,manage-run-tasks"` } // TeamPermissions represents the current user's permissions on the team. @@ -70,32 +73,24 @@ type TeamPermissions struct { CanUpdateMembership bool `jsonapi:"attr,can-update-membership"` } +// TeamIncludeOpt represents the available options for include query params. +// https://www.terraform.io/docs/cloud/api/teams.html#available-related-resources +type TeamIncludeOpt string + +const ( + TeamUsers TeamIncludeOpt = "users" + TeamOrganizationMemberships TeamIncludeOpt = "organization-memberships" +) + // TeamListOptions represents the options for listing teams. type TeamListOptions struct { ListOptions + // Optional: A list of relations to include. + // https://www.terraform.io/docs/cloud/api/teams.html#available-related-resources + Include []TeamIncludeOpt `url:"include,omitempty"` - Include string `url:"include"` -} - -// List all the teams of the given organization. -func (s *teams) List(ctx context.Context, organization string, options TeamListOptions) (*TeamList, error) { - if !validStringID(&organization) { - return nil, ErrInvalidOrg - } - - u := fmt.Sprintf("organizations/%s/teams", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - tl := &TeamList{} - err = s.client.do(ctx, req, tl) - if err != nil { - return nil, err - } - - return tl, nil + // Optional: A list of team names to filter by. + Names []string `url:"filter[names],omitempty"` } // TeamCreateOptions represents the options for creating a team. @@ -109,6 +104,9 @@ type TeamCreateOptions struct { // Name of the team. Name *string `jsonapi:"attr,name"` + // Optional: Unique Identifier to control team membership via SAML + SSOTeamID *string `jsonapi:"attr,sso-team-id,omitempty"` + // The team's organization access OrganizationAccess *OrganizationAccessOptions `jsonapi:"attr,organization-access,omitempty"` @@ -116,19 +114,59 @@ type TeamCreateOptions struct { Visibility *string `jsonapi:"attr,visibility,omitempty"` } +// TeamUpdateOptions represents the options for updating a team. +type TeamUpdateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,teams"` + + // Optional: New name for the team + Name *string `jsonapi:"attr,name,omitempty"` + + // Optional: Unique Identifier to control team membership via SAML + SSOTeamID *string `jsonapi:"attr,sso-team-id,omitempty"` + + // Optional: The team's organization access + OrganizationAccess *OrganizationAccessOptions `jsonapi:"attr,organization-access,omitempty"` + + // Optional: The team's visibility ("secret", "organization") + Visibility *string `jsonapi:"attr,visibility,omitempty"` +} + // OrganizationAccessOptions represents the organization access options of a team. type OrganizationAccessOptions struct { ManagePolicies *bool `json:"manage-policies,omitempty"` ManagePolicyOverrides *bool `json:"manage-policy-overrides,omitempty"` ManageWorkspaces *bool `json:"manage-workspaces,omitempty"` ManageVCSSettings *bool `json:"manage-vcs-settings,omitempty"` + ManageProviders *bool `json:"manage-providers,omitempty"` + ManageModules *bool `json:"manage-modules,omitempty"` + ManageRunTasks *bool `json:"manage-run-tasks,omitempty"` } -func (o TeamCreateOptions) valid() error { - if !validString(o.Name) { - return ErrRequiredName +// List all the teams of the given organization. +func (s *teams) List(ctx context.Context, organization string, options *TeamListOptions) (*TeamList, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg } - return nil + if err := options.valid(); err != nil { + return nil, err + } + u := fmt.Sprintf("organizations/%s/teams", url.QueryEscape(organization)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + tl := &TeamList{} + err = s.client.do(ctx, req, tl) + if err != nil { + return nil, err + } + + return tl, nil } // Create a new team with the given options. @@ -158,7 +196,7 @@ func (s *teams) Create(ctx context.Context, organization string, options TeamCre // Read a single team by its ID. func (s *teams) Read(ctx context.Context, teamID string) (*Team, error) { if !validStringID(&teamID) { - return nil, errors.New("invalid value for team ID") + return nil, ErrInvalidTeamID } u := fmt.Sprintf("teams/%s", url.QueryEscape(teamID)) @@ -176,28 +214,10 @@ func (s *teams) Read(ctx context.Context, teamID string) (*Team, error) { return t, nil } -// TeamUpdateOptions represents the options for updating a team. -type TeamUpdateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,teams"` - - // New name for the team - Name *string `jsonapi:"attr,name,omitempty"` - - // The team's organization access - OrganizationAccess *OrganizationAccessOptions `jsonapi:"attr,organization-access,omitempty"` - - // The team's visibility ("secret", "organization") - Visibility *string `jsonapi:"attr,visibility,omitempty"` -} - // Update a team by its ID. func (s *teams) Update(ctx context.Context, teamID string, options TeamUpdateOptions) (*Team, error) { if !validStringID(&teamID) { - return nil, errors.New("invalid value for team ID") + return nil, ErrInvalidTeamID } u := fmt.Sprintf("teams/%s", url.QueryEscape(teamID)) @@ -218,7 +238,7 @@ func (s *teams) Update(ctx context.Context, teamID string, options TeamUpdateOpt // Delete a team by its ID. func (s *teams) Delete(ctx context.Context, teamID string) error { if !validStringID(&teamID) { - return errors.New("invalid value for team ID") + return ErrInvalidTeamID } u := fmt.Sprintf("teams/%s", url.QueryEscape(teamID)) @@ -229,3 +249,49 @@ func (s *teams) Delete(ctx context.Context, teamID string) error { return s.client.do(ctx, req, nil) } + +func (o TeamCreateOptions) valid() error { + if !validString(o.Name) { + return ErrRequiredName + } + return nil +} + +func (o *TeamListOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateTeamIncludeParams(o.Include); err != nil { + return err + } + + if err := validateTeamNames(o.Names); err != nil { + return err + } + + return nil +} + +func validateTeamIncludeParams(params []TeamIncludeOpt) error { + for _, p := range params { + switch p { + case TeamUsers, TeamOrganizationMemberships: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil +} + +func validateTeamNames(names []string) error { + for _, name := range names { + if name == "" { + return ErrEmptyTeamName + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/team_access.go b/vendor/github.com/hashicorp/go-tfe/team_access.go index 60319808..0f56b3eb 100644 --- a/vendor/github.com/hashicorp/go-tfe/team_access.go +++ b/vendor/github.com/hashicorp/go-tfe/team_access.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" ) @@ -17,7 +16,7 @@ var _ TeamAccesses = (*teamAccesses)(nil) // https://www.terraform.io/docs/cloud/api/team-access.html type TeamAccesses interface { // List all the team accesses for a given workspace. - List(ctx context.Context, options TeamAccessListOptions) (*TeamAccessList, error) + List(ctx context.Context, options *TeamAccessListOptions) (*TeamAccessList, error) // Add team access for a workspace. Add(ctx context.Context, options TeamAccessAddOptions) (*TeamAccess, error) @@ -40,42 +39,46 @@ type teamAccesses struct { // AccessType represents a team access type. type AccessType string -// RunsPermissionType represents the permissiontype to a workspace's runs. -type RunsPermissionType string - -// VariablesPermissionType represents the permissiontype to a workspace's variables. -type VariablesPermissionType string - -// StateVersionsPermissionType represents the permissiontype to a workspace's state versions. -type StateVersionsPermissionType string - -// SentinelMocksPermissionType represents the permissiontype to a workspace's Sentinel mocks. -type SentinelMocksPermissionType string - -// WorkspaceLockingPermissionType represents the permissiontype to lock or unlock a workspace. -type WorkspaceLockingPermissionType bool - -// List all available team access types and permissions. const ( AccessAdmin AccessType = "admin" AccessPlan AccessType = "plan" AccessRead AccessType = "read" AccessWrite AccessType = "write" AccessCustom AccessType = "custom" +) + +// RunsPermissionType represents the permissiontype to a workspace's runs. +type RunsPermissionType string +const ( RunsPermissionRead RunsPermissionType = "read" RunsPermissionPlan RunsPermissionType = "plan" RunsPermissionApply RunsPermissionType = "apply" +) + +// VariablesPermissionType represents the permissiontype to a workspace's variables. +type VariablesPermissionType string +const ( VariablesPermissionNone VariablesPermissionType = "none" VariablesPermissionRead VariablesPermissionType = "read" VariablesPermissionWrite VariablesPermissionType = "write" +) +// StateVersionsPermissionType represents the permissiontype to a workspace's state versions. +type StateVersionsPermissionType string + +const ( StateVersionsPermissionNone StateVersionsPermissionType = "none" StateVersionsPermissionReadOutputs StateVersionsPermissionType = "read-outputs" StateVersionsPermissionRead StateVersionsPermissionType = "read" StateVersionsPermissionWrite StateVersionsPermissionType = "write" +) + +// SentinelMocksPermissionType represents the permissiontype to a workspace's Sentinel mocks. +type SentinelMocksPermissionType string +const ( SentinelMocksPermissionNone SentinelMocksPermissionType = "none" SentinelMocksPermissionRead SentinelMocksPermissionType = "read" ) @@ -95,6 +98,7 @@ type TeamAccess struct { StateVersions StateVersionsPermissionType `jsonapi:"attr,state-versions"` SentinelMocks SentinelMocksPermissionType `jsonapi:"attr,sentinel-mocks"` WorkspaceLocking bool `jsonapi:"attr,workspace-locking"` + RunTasks bool `jsonapi:"attr,run-tasks"` // Relations Team *Team `jsonapi:"relation,team"` @@ -104,37 +108,7 @@ type TeamAccess struct { // TeamAccessListOptions represents the options for listing team accesses. type TeamAccessListOptions struct { ListOptions - WorkspaceID *string `url:"filter[workspace][id],omitempty"` -} - -func (o TeamAccessListOptions) valid() error { - if !validString(o.WorkspaceID) { - return errors.New("workspace ID is required") - } - if !validStringID(o.WorkspaceID) { - return ErrInvalidWorkspaceID - } - return nil -} - -// List all the team accesses for a given workspace. -func (s *teamAccesses) List(ctx context.Context, options TeamAccessListOptions) (*TeamAccessList, error) { - if err := options.valid(); err != nil { - return nil, err - } - - req, err := s.client.newRequest("GET", "team-workspaces", &options) - if err != nil { - return nil, err - } - - tal := &TeamAccessList{} - err = s.client.do(ctx, req, tal) - if err != nil { - return nil, err - } - - return tal, nil + WorkspaceID string `url:"filter[workspace][id]"` } // TeamAccessAddOptions represents the options for adding team access. @@ -155,6 +129,7 @@ type TeamAccessAddOptions struct { StateVersions *StateVersionsPermissionType `jsonapi:"attr,state-versions,omitempty"` SentinelMocks *SentinelMocksPermissionType `jsonapi:"attr,sentinel-mocks,omitempty"` WorkspaceLocking *bool `jsonapi:"attr,workspace-locking,omitempty"` + RunTasks *bool `jsonapi:"attr,run-tasks,omitempty"` // The team to add to the workspace Team *Team `jsonapi:"relation,team"` @@ -163,17 +138,45 @@ type TeamAccessAddOptions struct { Workspace *Workspace `jsonapi:"relation,workspace"` } -func (o TeamAccessAddOptions) valid() error { - if o.Access == nil { - return errors.New("access is required") +// TeamAccessUpdateOptions represents the options for updating team access. +type TeamAccessUpdateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,team-workspaces"` + + // The type of access to grant. + Access *AccessType `jsonapi:"attr,access,omitempty"` + + // Custom workspace access permissions. These can only be edited when Access is 'custom'; otherwise, they are + // read-only and reflect the Access level's implicit permissions. + Runs *RunsPermissionType `jsonapi:"attr,runs,omitempty"` + Variables *VariablesPermissionType `jsonapi:"attr,variables,omitempty"` + StateVersions *StateVersionsPermissionType `jsonapi:"attr,state-versions,omitempty"` + SentinelMocks *SentinelMocksPermissionType `jsonapi:"attr,sentinel-mocks,omitempty"` + WorkspaceLocking *bool `jsonapi:"attr,workspace-locking,omitempty"` + RunTasks *bool `jsonapi:"attr,run-tasks,omitempty"` +} + +// List all the team accesses for a given workspace. +func (s *teamAccesses) List(ctx context.Context, options *TeamAccessListOptions) (*TeamAccessList, error) { + if err := options.valid(); err != nil { + return nil, err } - if o.Team == nil { - return errors.New("team is required") + + req, err := s.client.newRequest("GET", "team-workspaces", options) + if err != nil { + return nil, err } - if o.Workspace == nil { - return errors.New("workspace is required") + + tal := &TeamAccessList{} + err = s.client.do(ctx, req, tal) + if err != nil { + return nil, err } - return nil + + return tal, nil } // Add team access for a workspace. @@ -199,7 +202,7 @@ func (s *teamAccesses) Add(ctx context.Context, options TeamAccessAddOptions) (* // Read a team access by its ID. func (s *teamAccesses) Read(ctx context.Context, teamAccessID string) (*TeamAccess, error) { if !validStringID(&teamAccessID) { - return nil, errors.New("invalid value for team access ID") + return nil, ErrInvalidAccessTeamID } u := fmt.Sprintf("team-workspaces/%s", url.QueryEscape(teamAccessID)) @@ -217,30 +220,10 @@ func (s *teamAccesses) Read(ctx context.Context, teamAccessID string) (*TeamAcce return ta, nil } -// TeamAccessUpdateOptions represents the options for updating team access. -type TeamAccessUpdateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,team-workspaces"` - - // The type of access to grant. - Access *AccessType `jsonapi:"attr,access,omitempty"` - - // Custom workspace access permissions. These can only be edited when Access is 'custom'; otherwise, they are - // read-only and reflect the Access level's implicit permissions. - Runs *RunsPermissionType `jsonapi:"attr,runs,omitempty"` - Variables *VariablesPermissionType `jsonapi:"attr,variables,omitempty"` - StateVersions *StateVersionsPermissionType `jsonapi:"attr,state-versions,omitempty"` - SentinelMocks *SentinelMocksPermissionType `jsonapi:"attr,sentinel-mocks,omitempty"` - WorkspaceLocking *bool `jsonapi:"attr,workspace-locking,omitempty"` -} - // Update team access for a workspace func (s *teamAccesses) Update(ctx context.Context, teamAccessID string, options TeamAccessUpdateOptions) (*TeamAccess, error) { if !validStringID(&teamAccessID) { - return nil, errors.New("invalid value for team access ID") + return nil, ErrInvalidAccessTeamID } u := fmt.Sprintf("team-workspaces/%s", url.QueryEscape(teamAccessID)) @@ -261,7 +244,7 @@ func (s *teamAccesses) Update(ctx context.Context, teamAccessID string, options // Remove team access from a workspace. func (s *teamAccesses) Remove(ctx context.Context, teamAccessID string) error { if !validStringID(&teamAccessID) { - return errors.New("invalid value for team access ID") + return ErrInvalidAccessTeamID } u := fmt.Sprintf("team-workspaces/%s", url.QueryEscape(teamAccessID)) @@ -272,3 +255,30 @@ func (s *teamAccesses) Remove(ctx context.Context, teamAccessID string) error { return s.client.do(ctx, req, nil) } + +func (o *TeamAccessListOptions) valid() error { + if o == nil { + return ErrRequiredTeamAccessListOps + } + if !validString(&o.WorkspaceID) { + return ErrRequiredWorkspaceID + } + if !validStringID(&o.WorkspaceID) { + return ErrInvalidWorkspaceID + } + + return nil +} + +func (o TeamAccessAddOptions) valid() error { + if o.Access == nil { + return ErrRequiredAccess + } + if o.Team == nil { + return ErrRequiredTeam + } + if o.Workspace == nil { + return ErrRequiredWorkspace + } + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/team_member.go b/vendor/github.com/hashicorp/go-tfe/team_member.go index c04f55fb..c52c6016 100644 --- a/vendor/github.com/hashicorp/go-tfe/team_member.go +++ b/vendor/github.com/hashicorp/go-tfe/team_member.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" @@ -48,6 +47,20 @@ type teamMemberOrgMembership struct { ID string `jsonapi:"primary,organization-memberships"` } +// TeamMemberAddOptions represents the options for +// adding or removing team members. +type TeamMemberAddOptions struct { + Usernames []string + OrganizationMembershipIDs []string +} + +// TeamMemberRemoveOptions represents the options for +// adding or removing team members. +type TeamMemberRemoveOptions struct { + Usernames []string + OrganizationMembershipIDs []string +} + // List returns all Users of a team calling ListUsers // See ListOrganizationMemberships for fetching memberships func (s *teamMembers) List(ctx context.Context, teamID string) ([]*User, error) { @@ -57,13 +70,13 @@ func (s *teamMembers) List(ctx context.Context, teamID string) ([]*User, error) // ListUsers returns the Users of this team. func (s *teamMembers) ListUsers(ctx context.Context, teamID string) ([]*User, error) { if !validStringID(&teamID) { - return nil, errors.New("invalid value for team ID") + return nil, ErrInvalidTeamID } options := struct { - Include string `url:"include"` + Include []TeamIncludeOpt `url:"include,omitempty"` }{ - Include: "users", + Include: []TeamIncludeOpt{TeamUsers}, } u := fmt.Sprintf("teams/%s", url.QueryEscape(teamID)) @@ -84,13 +97,13 @@ func (s *teamMembers) ListUsers(ctx context.Context, teamID string) ([]*User, er // ListOrganizationMemberships returns the OrganizationMemberships of this team. func (s *teamMembers) ListOrganizationMemberships(ctx context.Context, teamID string) ([]*OrganizationMembership, error) { if !validStringID(&teamID) { - return nil, errors.New("invalid value for team ID") + return nil, ErrInvalidTeamID } options := struct { - Include string `url:"include"` + Include []TeamIncludeOpt `url:"include,omitempty"` }{ - Include: "organization-memberships", + Include: []TeamIncludeOpt{TeamOrganizationMemberships}, } u := fmt.Sprintf("teams/%s", url.QueryEscape(teamID)) @@ -108,49 +121,17 @@ func (s *teamMembers) ListOrganizationMemberships(ctx context.Context, teamID st return t.OrganizationMemberships, nil } -// TeamMemberAddOptions represents the options for -// adding or removing team members. -type TeamMemberAddOptions struct { - Usernames []string - OrganizationMembershipIDs []string -} - -func (o *TeamMemberAddOptions) valid() error { - if o.Usernames == nil && o.OrganizationMembershipIDs == nil { - return errors.New("usernames or organization membership ids are required") - } - if o.Usernames != nil && o.OrganizationMembershipIDs != nil { - return errors.New("only one of usernames or organization membership ids can be provided") - } - if o.Usernames != nil && len(o.Usernames) == 0 { - return errors.New("invalid value for usernames") - } - if o.OrganizationMembershipIDs != nil && len(o.OrganizationMembershipIDs) == 0 { - return errors.New("invalid value for organization membership ids") - } - return nil -} - -// kind returns "users" or "organization-memberships" -// depending on which is defined -func (o *TeamMemberAddOptions) kind() string { - if o.Usernames != nil && len(o.Usernames) != 0 { - return "users" - } - return "organization-memberships" -} - // Add multiple users to a team. func (s *teamMembers) Add(ctx context.Context, teamID string, options TeamMemberAddOptions) error { if !validStringID(&teamID) { - return errors.New("invalid value for team ID") + return ErrInvalidTeamID } if err := options.valid(); err != nil { return err } usersOrMemberships := options.kind() - URL := fmt.Sprintf("teams/%s/relationships/%s", url.QueryEscape(teamID), usersOrMemberships) + u := fmt.Sprintf("teams/%s/relationships/%s", url.QueryEscape(teamID), usersOrMemberships) var req *retryablehttp.Request @@ -160,7 +141,7 @@ func (s *teamMembers) Add(ctx context.Context, teamID string, options TeamMember for _, name := range options.Usernames { members = append(members, &teamMemberUser{Username: name}) } - req, err = s.client.newRequest("POST", URL, members) + req, err = s.client.newRequest("POST", u, members) if err != nil { return err } @@ -170,7 +151,7 @@ func (s *teamMembers) Add(ctx context.Context, teamID string, options TeamMember for _, ID := range options.OrganizationMembershipIDs { members = append(members, &teamMemberOrgMembership{ID: ID}) } - req, err = s.client.newRequest("POST", URL, members) + req, err = s.client.newRequest("POST", u, members) if err != nil { return err } @@ -179,49 +160,17 @@ func (s *teamMembers) Add(ctx context.Context, teamID string, options TeamMember return s.client.do(ctx, req, nil) } -// TeamMemberRemoveOptions represents the options for -// adding or removing team members. -type TeamMemberRemoveOptions struct { - Usernames []string - OrganizationMembershipIDs []string -} - -func (o *TeamMemberRemoveOptions) valid() error { - if o.Usernames == nil && o.OrganizationMembershipIDs == nil { - return errors.New("usernames or organization membership ids are required") - } - if o.Usernames != nil && o.OrganizationMembershipIDs != nil { - return errors.New("only one of usernames or organization membership ids can be provided") - } - if o.Usernames != nil && len(o.Usernames) == 0 { - return errors.New("invalid value for usernames") - } - if o.OrganizationMembershipIDs != nil && len(o.OrganizationMembershipIDs) == 0 { - return errors.New("invalid value for organization membership ids") - } - return nil -} - -// kind returns "users" or "organization-memberships" -// depending on which is defined -func (o *TeamMemberRemoveOptions) kind() string { - if o.Usernames != nil && len(o.Usernames) != 0 { - return "users" - } - return "organization-memberships" -} - // Remove multiple users from a team. func (s *teamMembers) Remove(ctx context.Context, teamID string, options TeamMemberRemoveOptions) error { if !validStringID(&teamID) { - return errors.New("invalid value for team ID") + return ErrInvalidTeamID } if err := options.valid(); err != nil { return err } usersOrMemberships := options.kind() - URL := fmt.Sprintf("teams/%s/relationships/%s", url.QueryEscape(teamID), usersOrMemberships) + u := fmt.Sprintf("teams/%s/relationships/%s", url.QueryEscape(teamID), usersOrMemberships) var req *retryablehttp.Request @@ -231,7 +180,7 @@ func (s *teamMembers) Remove(ctx context.Context, teamID string, options TeamMem for _, name := range options.Usernames { members = append(members, &teamMemberUser{Username: name}) } - req, err = s.client.newRequest("DELETE", URL, members) + req, err = s.client.newRequest("DELETE", u, members) if err != nil { return err } @@ -241,7 +190,7 @@ func (s *teamMembers) Remove(ctx context.Context, teamID string, options TeamMem for _, ID := range options.OrganizationMembershipIDs { members = append(members, &teamMemberOrgMembership{ID: ID}) } - req, err = s.client.newRequest("DELETE", URL, members) + req, err = s.client.newRequest("DELETE", u, members) if err != nil { return err } @@ -249,3 +198,53 @@ func (s *teamMembers) Remove(ctx context.Context, teamID string, options TeamMem return s.client.do(ctx, req, nil) } + +// kind returns "users" or "organization-memberships" +// depending on which is defined +func (o *TeamMemberAddOptions) kind() string { + if o.Usernames != nil && len(o.Usernames) != 0 { + return "users" + } + return "organization-memberships" +} + +// kind returns "users" or "organization-memberships" +// depending on which is defined +func (o *TeamMemberRemoveOptions) kind() string { + if o.Usernames != nil && len(o.Usernames) != 0 { + return "users" + } + return "organization-memberships" +} + +func (o *TeamMemberAddOptions) valid() error { + if o.Usernames == nil && o.OrganizationMembershipIDs == nil { + return ErrRequiredUsernameOrMembershipIds + } + if o.Usernames != nil && o.OrganizationMembershipIDs != nil { + return ErrRequiredOnlyOneField + } + if o.Usernames != nil && len(o.Usernames) == 0 { + return ErrInvalidUsernames + } + if o.OrganizationMembershipIDs != nil && len(o.OrganizationMembershipIDs) == 0 { + return ErrInvalidMembershipIDs + } + return nil +} + +func (o *TeamMemberRemoveOptions) valid() error { + if o.Usernames == nil && o.OrganizationMembershipIDs == nil { + return ErrRequiredUsernameOrMembershipIds + } + if o.Usernames != nil && o.OrganizationMembershipIDs != nil { + return ErrRequiredOnlyOneField + } + if o.Usernames != nil && len(o.Usernames) == 0 { + return ErrInvalidUsernames + } + if o.OrganizationMembershipIDs != nil && len(o.OrganizationMembershipIDs) == 0 { + return ErrInvalidMembershipIDs + } + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/team_token.go b/vendor/github.com/hashicorp/go-tfe/team_token.go index ac412c6b..d299f93c 100644 --- a/vendor/github.com/hashicorp/go-tfe/team_token.go +++ b/vendor/github.com/hashicorp/go-tfe/team_token.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" "time" @@ -17,8 +16,8 @@ var _ TeamTokens = (*teamTokens)(nil) // TFE API docs: // https://www.terraform.io/docs/cloud/api/team-tokens.html type TeamTokens interface { - // Generate a new team token, replacing any existing token. - Generate(ctx context.Context, teamID string) (*TeamToken, error) + // Create a new team token, replacing any existing token. + Create(ctx context.Context, teamID string) (*TeamToken, error) // Read a team token by its ID. Read(ctx context.Context, teamID string) (*TeamToken, error) @@ -41,10 +40,10 @@ type TeamToken struct { Token string `jsonapi:"attr,token"` } -// Generate a new team token, replacing any existing token. -func (s *teamTokens) Generate(ctx context.Context, teamID string) (*TeamToken, error) { +// Create a new team token, replacing any existing token. +func (s *teamTokens) Create(ctx context.Context, teamID string) (*TeamToken, error) { if !validStringID(&teamID) { - return nil, errors.New("invalid value for team ID") + return nil, ErrInvalidTeamID } u := fmt.Sprintf("teams/%s/authentication-token", url.QueryEscape(teamID)) @@ -65,7 +64,7 @@ func (s *teamTokens) Generate(ctx context.Context, teamID string) (*TeamToken, e // Read a team token by its ID. func (s *teamTokens) Read(ctx context.Context, teamID string) (*TeamToken, error) { if !validStringID(&teamID) { - return nil, errors.New("invalid value for team ID") + return nil, ErrInvalidTeamID } u := fmt.Sprintf("teams/%s/authentication-token", url.QueryEscape(teamID)) @@ -86,7 +85,7 @@ func (s *teamTokens) Read(ctx context.Context, teamID string) (*TeamToken, error // Delete a team token by its ID. func (s *teamTokens) Delete(ctx context.Context, teamID string) error { if !validStringID(&teamID) { - return errors.New("invalid value for team ID") + return ErrInvalidTeamID } u := fmt.Sprintf("teams/%s/authentication-token", url.QueryEscape(teamID)) diff --git a/vendor/github.com/hashicorp/go-tfe/tfe.go b/vendor/github.com/hashicorp/go-tfe/tfe.go index 51bdc072..5de21331 100644 --- a/vendor/github.com/hashicorp/go-tfe/tfe.go +++ b/vendor/github.com/hashicorp/go-tfe/tfe.go @@ -1,12 +1,14 @@ package tfe import ( + "errors" + "io/fs" "log" + "sort" "bytes" "context" "encoding/json" - "errors" "fmt" "io" "math/rand" @@ -19,7 +21,7 @@ import ( "time" "github.com/google/go-querystring/query" - "github.com/hashicorp/go-cleanhttp" + cleanhttp "github.com/hashicorp/go-cleanhttp" retryablehttp "github.com/hashicorp/go-retryablehttp" "github.com/hashicorp/jsonapi" "golang.org/x/time/rate" @@ -28,23 +30,24 @@ import ( ) const ( - userAgent = "go-tfe" - headerRateLimit = "X-RateLimit-Limit" - headerRateReset = "X-RateLimit-Reset" - headerAPIVersion = "TFP-API-Version" - - // DefaultAddress of Terraform Enterprise. - DefaultAddress = "https://app.terraform.io" - // DefaultBasePath on which the API is served. + _userAgent = "go-tfe" + _headerRateLimit = "X-RateLimit-Limit" + _headerRateReset = "X-RateLimit-Reset" + _headerAPIVersion = "TFP-API-Version" + _includeQueryParam = "include" + + DefaultAddress = "https://app.terraform.io" DefaultBasePath = "/api/v2/" // PingEndpoint is a no-op API endpoint used to configure the rate limiter PingEndpoint = "ping" ) // RetryLogHook allows a function to run before each retry. + type RetryLogHook func(attemptNum int, resp *http.Response) // Config provides configuration details to the API client. + type Config struct { // The address of the Terraform Enterprise API. Address string @@ -66,6 +69,7 @@ type Config struct { } // DefaultConfig returns a default config structure. + func DefaultConfig() *Config { config := &Config{ Address: os.Getenv("TFE_ADDRESS"), @@ -77,17 +81,21 @@ func DefaultConfig() *Config { // Set the default address if none is given. if config.Address == "" { - config.Address = DefaultAddress + if host := os.Getenv("TFE_HOSTNAME"); host != "" { + config.Address = fmt.Sprintf("https://%s", host) + } else { + config.Address = DefaultAddress + } } // Set the default user agent. - config.Headers.Set("User-Agent", userAgent) + config.Headers.Set("User-Agent", _userAgent) return config } // Client is the Terraform Enterprise API client. It provides the basic -// connectivity and configuration for accessing the TFE API. +// connectivity and configuration for accessing the TFE API type Client struct { baseURL *url.URL token string @@ -102,6 +110,8 @@ type Client struct { AgentPools AgentPools AgentTokens AgentTokens Applies Applies + AuditTrails AuditTrails + Comments Comments ConfigurationVersions ConfigurationVersions CostEstimates CostEstimates NotificationConfigurations NotificationConfigurations @@ -119,11 +129,17 @@ type Client struct { PolicySetVersions PolicySetVersions PolicySets PolicySets RegistryModules RegistryModules + RegistryProviders RegistryProviders + RegistryProviderPlatforms RegistryProviderPlatforms + RegistryProviderVersions RegistryProviderVersions Runs Runs + RunTasks RunTasks RunTriggers RunTriggers SSHKeys SSHKeys StateVersionOutputs StateVersionOutputs StateVersions StateVersions + TaskResults TaskResults + TaskStages TaskStages Teams Teams TeamAccess TeamAccesses TeamMembers TeamMembers @@ -131,14 +147,17 @@ type Client struct { Users Users UserTokens UserTokens Variables Variables + VariableSets VariableSets + VariableSetVariables VariableSetVariables Workspaces Workspaces + WorkspaceRunTasks WorkspaceRunTasks Meta Meta } // Admin is the the Terraform Enterprise Admin API. It provides access to site // wide admin settings. These are only available for Terraform Enterprise and -// do not function against Terraform Cloud. +// do not function against Terraform Cloud type Admin struct { Organizations AdminOrganizations Workspaces AdminWorkspaces @@ -182,7 +201,7 @@ func NewClient(cfg *Config) (*Client, error) { // Parse the address to make sure its a valid URL. baseURL, err := url.Parse(config.Address) if err != nil { - return nil, fmt.Errorf("invalid address: %v", err) + return nil, fmt.Errorf("invalid address: %w", err) } baseURL.Path = config.BasePath @@ -239,6 +258,8 @@ func NewClient(cfg *Config) (*Client, error) { client.AgentPools = &agentPools{client: client} client.AgentTokens = &agentTokens{client: client} client.Applies = &applies{client: client} + client.AuditTrails = &auditTrails{client: client} + client.Comments = &comments{client: client} client.ConfigurationVersions = &configurationVersions{client: client} client.CostEstimates = &costEstimates{client: client} client.NotificationConfigurations = ¬ificationConfigurations{client: client} @@ -256,11 +277,16 @@ func NewClient(cfg *Config) (*Client, error) { client.PolicySetVersions = &policySetVersions{client: client} client.PolicySets = &policySets{client: client} client.RegistryModules = ®istryModules{client: client} + client.RegistryProviders = ®istryProviders{client: client} + client.RegistryProviderPlatforms = ®istryProviderPlatforms{client: client} + client.RegistryProviderVersions = ®istryProviderVersions{client: client} client.Runs = &runs{client: client} + client.RunTasks = &runTasks{client: client} client.RunTriggers = &runTriggers{client: client} client.SSHKeys = &sshKeys{client: client} client.StateVersionOutputs = &stateVersionOutputs{client: client} client.StateVersions = &stateVersions{client: client} + client.TaskStages = &taskStages{client: client} client.Teams = &teams{client: client} client.TeamAccess = &teamAccesses{client: client} client.TeamMembers = &teamMembers{client: client} @@ -268,7 +294,10 @@ func NewClient(cfg *Config) (*Client, error) { client.Users = &users{client: client} client.UserTokens = &userTokens{client: client} client.Variables = &variables{client: client} + client.VariableSets = &variableSets{client: client} + client.VariableSetVariables = &variableSetVariables{client: client} client.Workspaces = &workspaces{client: client} + client.WorkspaceRunTasks = &workspaceRunTasks{client: client} client.Meta = Meta{ IPRanges: &ipRanges{client: client}, @@ -302,18 +331,21 @@ func (c *Client) RemoteAPIVersion() string { // // This is intended for use in tests, when you may want to configure your TFE client to // return something different than the actual API version in order to test error handling. + func (c *Client) SetFakeRemoteAPIVersion(fakeAPIVersion string) { c.remoteAPIVersion = fakeAPIVersion } // RetryServerErrors configures the retry HTTP check to also retry // unexpected errors or requests that failed with a server error. + func (c *Client) RetryServerErrors(retry bool) { c.retryServerErrors = retry } // retryHTTPCheck provides a callback for Client.CheckRetry which // will retry both rate limit (429) and server (>= 500) errors. + func (c *Client) retryHTTPCheck(ctx context.Context, resp *http.Response, err error) (bool, error) { if ctx.Err() != nil { return false, ctx.Err() @@ -329,6 +361,7 @@ func (c *Client) retryHTTPCheck(ctx context.Context, resp *http.Response, err er // retryHTTPBackoff provides a generic callback for Client.Backoff which // will pass through all calls based on the status code of the response. + func (c *Client) retryHTTPBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration { if c.retryLogHook != nil { c.retryLogHook(attemptNum, resp) @@ -336,7 +369,7 @@ func (c *Client) retryHTTPBackoff(min, max time.Duration, attemptNum int, resp * // Use the rate limit backoff function when we are rate limited. if resp != nil && resp.StatusCode == 429 { - return rateLimitBackoff(min, max, attemptNum, resp) + return rateLimitBackoff(min, max, resp) } // Set custom duration's when we experience a service interruption. @@ -353,15 +386,16 @@ func (c *Client) retryHTTPBackoff(min, max time.Duration, attemptNum int, resp * // min and max are mainly used for bounding the jitter that will be added to // the reset time retrieved from the headers. But if the final wait time is // less then min, min will be used instead. -func rateLimitBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration { + +func rateLimitBackoff(min, max time.Duration, resp *http.Response) time.Duration { // rnd is used to generate pseudo-random numbers. rnd := rand.New(rand.NewSource(time.Now().UnixNano())) // First create some jitter bounded by the min and max durations. jitter := time.Duration(rnd.Float64() * float64(max-min)) - if resp != nil && resp.Header.Get(headerRateReset) != "" { - v := resp.Header.Get(headerRateReset) + if resp != nil && resp.Header.Get(_headerRateReset) != "" { + v := resp.Header.Get(_headerRateReset) reset, err := strconv.ParseFloat(v, 64) if err != nil { log.Fatal(err) @@ -414,13 +448,14 @@ func (c *Client) getRawAPIMetadata() (rawAPIMetadata, error) { } resp.Body.Close() - meta.APIVersion = resp.Header.Get(headerAPIVersion) - meta.RateLimit = resp.Header.Get(headerRateLimit) + meta.APIVersion = resp.Header.Get(_headerAPIVersion) + meta.RateLimit = resp.Header.Get(_headerRateLimit) return meta, nil } // configureLimiter configures the rate limiter. + func (c *Client) configureLimiter(rawLimit string) { // Set default values for when rate limiting is disabled. limit := rate.Inf @@ -474,7 +509,7 @@ func (c *Client) newRequest(method, path string, v interface{}) (*retryablehttp. if err != nil { return nil, err } - u.RawQuery = q.Encode() + u.RawQuery = encodeQueryParams(q) } case "DELETE", "PATCH", "POST": reqHeaders.Set("Accept", "application/vnd.api+json") @@ -509,31 +544,65 @@ func (c *Client) newRequest(method, path string, v interface{}) (*retryablehttp. return req, nil } +// Encode encodes the values into ``URL encoded'' form +// ("bar=baz&foo=quux") sorted by key. + +func encodeQueryParams(v url.Values) string { + if v == nil { + return "" + } + var buf strings.Builder + keys := make([]string, 0, len(v)) + for k := range v { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + vs := v[k] + if len(vs) > 1 && validSliceKey(k) { + val := strings.Join(vs, ",") + vs = vs[:0] + vs = append(vs, val) + } + keyEscaped := url.QueryEscape(k) + + for _, v := range vs { + if buf.Len() > 0 { + buf.WriteByte('&') + } + buf.WriteString(keyEscaped) + buf.WriteByte('=') + buf.WriteString(url.QueryEscape(v)) + } + } + return buf.String() +} + // Helper method that serializes the given ptr or ptr slice into a JSON // request. It automatically uses jsonapi or json serialization, depending // on the body type's tags. + func serializeRequestBody(v interface{}) (interface{}, error) { // The body can be a slice of pointers or a pointer. In either // case we want to choose the serialization type based on the // individual record type. To determine that type, we need // to either follow the pointer or examine the slice element type. - // There are other theoretical possiblities (e. g. maps, + // There are other theoretical possibilities (e. g. maps, // non-pointers) but they wouldn't work anyway because the // json-api library doesn't support serializing other things. var modelType reflect.Type bodyType := reflect.TypeOf(v) - invalidBodyError := errors.New("go-tfe bug: DELETE/PATCH/POST body must be nil, ptr, or ptr slice") switch bodyType.Kind() { case reflect.Slice: sliceElem := bodyType.Elem() if sliceElem.Kind() != reflect.Ptr { - return nil, invalidBodyError + return nil, ErrInvalidRequestBody } modelType = sliceElem.Elem() case reflect.Ptr: modelType = reflect.ValueOf(v).Elem().Type() default: - return nil, invalidBodyError + return nil, ErrInvalidRequestBody } // Infer whether the request uses jsonapi or regular json @@ -554,7 +623,7 @@ func serializeRequestBody(v interface{}) (interface{}, error) { // make sense, because a struct can only be serialized // as one or another. If this does happen, it's a bug // in the library that should be fixed at development time - return nil, errors.New("go-tfe bug: struct can't use both json and jsonapi attributes") + return nil, ErrInvalidStructFormat } if jsonFields > 0 { @@ -576,6 +645,7 @@ func serializeRequestBody(v interface{}) (interface{}, error) { // // The provided ctx must be non-nil. If it is canceled or times out, ctx.Err() // will be returned. + func (c *Client) do(ctx context.Context, req *retryablehttp.Request, v interface{}) error { // Wait will block until the limiter can obtain a new token // or returns an error if the given context is canceled. @@ -619,6 +689,46 @@ func (c *Client) do(ctx context.Context, req *retryablehttp.Request, v interface return unmarshalResponse(resp.Body, v) } +// customDo is similar to func (c *Client) do(ctx context.Context, req *retryablehttp.Request, v interface{}) error. Except that The IP ranges API is not returning jsonapi like every other endpoint +// which means we need to handle it differently. + +func (i *ipRanges) customDo(ctx context.Context, req *retryablehttp.Request, ir *IPRange) error { + // Wait will block until the limiter can obtain a new token + // or returns an error if the given context is canceled. + if err := i.client.limiter.Wait(ctx); err != nil { + return err + } + + // Add the context to the request. + req = req.WithContext(ctx) + + // Execute the request and check the response. + resp, err := i.client.http.Do(req) + if err != nil { + // If we got an error, and the context has been canceled, + // the context's error is probably more useful. + select { + case <-ctx.Done(): + return ctx.Err() + default: + return err + } + } + defer resp.Body.Close() + + if resp.StatusCode < 200 && resp.StatusCode >= 400 { + return fmt.Errorf("error HTTP response while retrieving IP ranges: %d", resp.StatusCode) + } else if resp.StatusCode == 304 { + return nil + } + + err = json.NewDecoder(resp.Body).Decode(ir) + if err != nil { + return err + } + return nil +} + func unmarshalResponse(responseBody io.Reader, model interface{}) error { // Get the value of model so we can test if it's a struct. dst := reflect.Indirect(reflect.ValueOf(model)) @@ -632,22 +742,22 @@ func unmarshalResponse(responseBody io.Reader, model interface{}) error { items := dst.FieldByName("Items") pagination := dst.FieldByName("Pagination") - // Unmarshal a single value if v does not contain the + // Unmarshal a single value if model does not contain the // Items and Pagination struct fields. if !items.IsValid() || !pagination.IsValid() { return jsonapi.UnmarshalPayload(responseBody, model) } - // Return an error if v.Items is not a slice. + // Return an error if model.Items is not a slice. if items.Type().Kind() != reflect.Slice { - return fmt.Errorf("v.Items must be a slice") + return ErrItemsMustBeSlice } // Create a temporary buffer and copy all the read data into it. body := bytes.NewBuffer(nil) reader := io.TeeReader(responseBody, body) - // Unmarshal as a list of values as v.Items is a slice. + // Unmarshal as a list of values as model.Items is a slice. raw, err := jsonapi.UnmarshalManyPayload(reader, items.Type().Elem()) if err != nil { return err @@ -713,11 +823,15 @@ func parsePagination(body io.Reader) (*Pagination, error) { } // checkResponseCode can be used to check the status code of an HTTP request. + func checkResponseCode(r *http.Response) error { if r.StatusCode >= 200 && r.StatusCode <= 299 { return nil } + var errs []string + var err error + switch r.StatusCode { case 401: return ErrUnauthorized @@ -728,21 +842,39 @@ func checkResponseCode(r *http.Response) error { case strings.HasSuffix(r.Request.URL.Path, "actions/lock"): return ErrWorkspaceLocked case strings.HasSuffix(r.Request.URL.Path, "actions/unlock"): + errs, err = decodeErrorPayload(r) + if err != nil { + return err + } + + if errorPayloadContains(errs, "is locked by Run") { + return ErrWorkspaceLockedByRun + } + return ErrWorkspaceNotLocked case strings.HasSuffix(r.Request.URL.Path, "actions/force-unlock"): return ErrWorkspaceNotLocked } } + errs, err = decodeErrorPayload(r) + if err != nil { + return err + } + + return errors.New(strings.Join(errs, "\n")) +} + +func decodeErrorPayload(r *http.Response) ([]string, error) { // Decode the error payload. + var errs []string errPayload := &jsonapi.ErrorsPayload{} err := json.NewDecoder(r.Body).Decode(errPayload) if err != nil || len(errPayload.Errors) == 0 { - return fmt.Errorf(r.Status) + return errs, errors.New(r.Status) } // Parse and format the errors. - var errs []string for _, e := range errPayload.Errors { if e.Detail == "" { errs = append(errs, e.Title) @@ -751,7 +883,16 @@ func checkResponseCode(r *http.Response) error { } } - return fmt.Errorf(strings.Join(errs, "\n")) + return errs, nil +} + +func errorPayloadContains(payloadErrors []string, match string) bool { + for _, e := range payloadErrors { + if strings.Contains(e, match) { + return true + } + } + return false } func packContents(path string) (*bytes.Buffer, error) { @@ -759,8 +900,12 @@ func packContents(path string) (*bytes.Buffer, error) { file, err := os.Stat(path) if err != nil { - return body, err + if errors.Is(err, fs.ErrNotExist) { + return body, fmt.Errorf(`failed to find files under the path "%v": %w`, path, err) + } + return body, fmt.Errorf(`unable to upload files from the path "%v": %w`, path, err) } + if !file.Mode().IsDir() { return body, ErrMissingDirectory } @@ -772,3 +917,7 @@ func packContents(path string) (*bytes.Buffer, error) { return body, nil } + +func validSliceKey(key string) bool { + return key == _includeQueryParam || strings.Contains(key, "filter[") +} diff --git a/vendor/github.com/hashicorp/go-tfe/user.go b/vendor/github.com/hashicorp/go-tfe/user.go index 76cb25dc..0795f503 100644 --- a/vendor/github.com/hashicorp/go-tfe/user.go +++ b/vendor/github.com/hashicorp/go-tfe/user.go @@ -16,7 +16,7 @@ type Users interface { ReadCurrent(ctx context.Context) (*User, error) // Update attributes of the currently authenticated user. - Update(ctx context.Context, options UserUpdateOptions) (*User, error) + UpdateCurrent(ctx context.Context, options UserUpdateOptions) (*User, error) } // users implements Users. @@ -45,6 +45,21 @@ type TwoFactor struct { Verified bool `jsonapi:"attr,verified"` } +// UserUpdateOptions represents the options for updating a user. +type UserUpdateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,users"` + + // Optional: New username. + Username *string `jsonapi:"attr,username,omitempty"` + + // Optional: New email address (must be consumed afterwards to take effect). + Email *string `jsonapi:"attr,email,omitempty"` +} + // ReadCurrent reads the details of the currently authenticated user. func (s *users) ReadCurrent(ctx context.Context) (*User, error) { req, err := s.client.newRequest("GET", "account/details", nil) @@ -61,23 +76,8 @@ func (s *users) ReadCurrent(ctx context.Context) (*User, error) { return u, nil } -// UserUpdateOptions represents the options for updating a user. -type UserUpdateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,users"` - - // New username. - Username *string `jsonapi:"attr,username,omitempty"` - - // New email address (must be consumed afterwards to take effect). - Email *string `jsonapi:"attr,email,omitempty"` -} - -// Update attributes of the currently authenticated user. -func (s *users) Update(ctx context.Context, options UserUpdateOptions) (*User, error) { +// UpdateCurrent updates attributes of the currently authenticated user. +func (s *users) UpdateCurrent(ctx context.Context, options UserUpdateOptions) (*User, error) { req, err := s.client.newRequest("PATCH", "account/update", &options) if err != nil { return nil, err diff --git a/vendor/github.com/hashicorp/go-tfe/user_token.go b/vendor/github.com/hashicorp/go-tfe/user_token.go index 6a0ad748..56de5b85 100644 --- a/vendor/github.com/hashicorp/go-tfe/user_token.go +++ b/vendor/github.com/hashicorp/go-tfe/user_token.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" "time" @@ -20,8 +19,8 @@ type UserTokens interface { // List all the tokens of the given user ID. List(ctx context.Context, userID string) (*UserTokenList, error) - // Generate a new user token - Generate(ctx context.Context, userID string, options UserTokenGenerateOptions) (*UserToken, error) + // Create a new user token + Create(ctx context.Context, userID string, options UserTokenCreateOptions) (*UserToken, error) // Read a user token by its ID. Read(ctx context.Context, tokenID string) (*UserToken, error) @@ -50,16 +49,16 @@ type UserToken struct { Token string `jsonapi:"attr,token"` } -// UserTokenGenerateOptions the options for creating a user token. -type UserTokenGenerateOptions struct { - // Description of the token +// UserTokenCreateOptions the options for creating a user token. +type UserTokenCreateOptions struct { + // Optional: Description of the token Description string `jsonapi:"attr,description,omitempty"` } -// Generate a new user token -func (s *userTokens) Generate(ctx context.Context, userID string, options UserTokenGenerateOptions) (*UserToken, error) { +// Create a new user token +func (s *userTokens) Create(ctx context.Context, userID string, options UserTokenCreateOptions) (*UserToken, error) { if !validStringID(&userID) { - return nil, errors.New("invalid value for user ID") + return nil, ErrInvalidUserID } u := fmt.Sprintf("users/%s/authentication-tokens", url.QueryEscape(userID)) @@ -80,7 +79,7 @@ func (s *userTokens) Generate(ctx context.Context, userID string, options UserTo // List shows existing user tokens func (s *userTokens) List(ctx context.Context, userID string) (*UserTokenList, error) { if !validStringID(&userID) { - return nil, errors.New("invalid value for user ID") + return nil, ErrInvalidUserID } u := fmt.Sprintf("users/%s/authentication-tokens", url.QueryEscape(userID)) @@ -101,7 +100,7 @@ func (s *userTokens) List(ctx context.Context, userID string) (*UserTokenList, e // Read a user token by its ID. func (s *userTokens) Read(ctx context.Context, tokenID string) (*UserToken, error) { if !validStringID(&tokenID) { - return nil, errors.New("invalid value for token ID") + return nil, ErrInvalidTokenID } u := fmt.Sprintf("authentication-tokens/%s", url.QueryEscape(tokenID)) @@ -122,7 +121,7 @@ func (s *userTokens) Read(ctx context.Context, tokenID string) (*UserToken, erro // Delete a user token by its ID. func (s *userTokens) Delete(ctx context.Context, tokenID string) error { if !validStringID(&tokenID) { - return errors.New("invalid value for token ID") + return ErrInvalidTokenID } u := fmt.Sprintf("authentication-tokens/%s", url.QueryEscape(tokenID)) diff --git a/vendor/github.com/hashicorp/go-tfe/validations.go b/vendor/github.com/hashicorp/go-tfe/validations.go index 38d95a68..2ca32293 100644 --- a/vendor/github.com/hashicorp/go-tfe/validations.go +++ b/vendor/github.com/hashicorp/go-tfe/validations.go @@ -1,19 +1,29 @@ package tfe import ( + "net/mail" "regexp" ) // A regular expression used to validate common string ID patterns. -var reStringID = regexp.MustCompile(`^[a-zA-Z0-9\-\._]+$`) + +var reStringID = regexp.MustCompile(`^[a-zA-Z0-9\-._]+$`) // validString checks if the given input is present and non-empty. + func validString(v *string) bool { return v != nil && *v != "" } // validStringID checks if the given string pointer is non-nil and // contains a typical string identifier. + func validStringID(v *string) bool { return v != nil && reStringID.MatchString(*v) } + +// validEmail checks if the given input is a correct email +func validEmail(v string) bool { + _, err := mail.ParseAddress(v) + return err == nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/variable.go b/vendor/github.com/hashicorp/go-tfe/variable.go index 9d11e4b9..53ea1745 100644 --- a/vendor/github.com/hashicorp/go-tfe/variable.go +++ b/vendor/github.com/hashicorp/go-tfe/variable.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "net/url" ) @@ -16,7 +15,7 @@ var _ Variables = (*variables)(nil) // TFE API docs: https://www.terraform.io/docs/cloud/api/workspace-variables.html type Variables interface { // List all the variables associated with the given workspace. - List(ctx context.Context, workspaceID string, options VariableListOptions) (*VariableList, error) + List(ctx context.Context, workspaceID string, options *VariableListOptions) (*VariableList, error) // Create is used to create a new variable. Create(ctx context.Context, workspaceID string, options VariableCreateOptions) (*Variable, error) @@ -39,7 +38,7 @@ type variables struct { // CategoryType represents a category type. type CategoryType string -//List all available categories. +// List all available categories. const ( CategoryEnv CategoryType = "env" CategoryPolicySet CategoryType = "policy-set" @@ -71,29 +70,35 @@ type VariableListOptions struct { ListOptions } -// List all the variables associated with the given workspace. -func (s *variables) List(ctx context.Context, workspaceID string, options VariableListOptions) (*VariableList, error) { - if !validStringID(&workspaceID) { - return nil, ErrInvalidWorkspaceID - } +// VariableCreateOptions represents the options for creating a new variable. +type VariableCreateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,vars"` - u := fmt.Sprintf("workspaces/%s/vars", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } + // Required: The name of the variable. + Key *string `jsonapi:"attr,key"` - vl := &VariableList{} - err = s.client.do(ctx, req, vl) - if err != nil { - return nil, err - } + // Optional: The value of the variable. + Value *string `jsonapi:"attr,value,omitempty"` - return vl, nil + // Optional: The description of the variable. + Description *string `jsonapi:"attr,description,omitempty"` + + // Required: Whether this is a Terraform or environment variable. + Category *CategoryType `jsonapi:"attr,category"` + + // Optional: Whether to evaluate the value of the variable as a string of HCL code. + HCL *bool `jsonapi:"attr,hcl,omitempty"` + + // Optional: Whether the value is sensitive. + Sensitive *bool `jsonapi:"attr,sensitive,omitempty"` } -// VariableCreateOptions represents the options for creating a new variable. -type VariableCreateOptions struct { +// VariableUpdateOptions represents the options for updating a variable. +type VariableUpdateOptions struct { // Type is a public field utilized by JSON:API to // set the resource type via the field tag. // It is not a user-defined value and does not need to be set. @@ -101,7 +106,7 @@ type VariableCreateOptions struct { Type string `jsonapi:"primary,vars"` // The name of the variable. - Key *string `jsonapi:"attr,key"` + Key *string `jsonapi:"attr,key,omitempty"` // The value of the variable. Value *string `jsonapi:"attr,value,omitempty"` @@ -110,7 +115,7 @@ type VariableCreateOptions struct { Description *string `jsonapi:"attr,description,omitempty"` // Whether this is a Terraform or environment variable. - Category *CategoryType `jsonapi:"attr,category"` + Category *CategoryType `jsonapi:"attr,category,omitempty"` // Whether to evaluate the value of the variable as a string of HCL code. HCL *bool `jsonapi:"attr,hcl,omitempty"` @@ -119,14 +124,25 @@ type VariableCreateOptions struct { Sensitive *bool `jsonapi:"attr,sensitive,omitempty"` } -func (o VariableCreateOptions) valid() error { - if !validString(o.Key) { - return errors.New("key is required") +// List all the variables associated with the given workspace. +func (s *variables) List(ctx context.Context, workspaceID string, options *VariableListOptions) (*VariableList, error) { + if !validStringID(&workspaceID) { + return nil, ErrInvalidWorkspaceID } - if o.Category == nil { - return errors.New("category is required") + + u := fmt.Sprintf("workspaces/%s/vars", url.QueryEscape(workspaceID)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err } - return nil + + vl := &VariableList{} + err = s.client.do(ctx, req, vl) + if err != nil { + return nil, err + } + + return vl, nil } // Create is used to create a new variable. @@ -154,12 +170,12 @@ func (s *variables) Create(ctx context.Context, workspaceID string, options Vari } // Read a variable by its ID. -func (s *variables) Read(ctx context.Context, workspaceID string, variableID string) (*Variable, error) { +func (s *variables) Read(ctx context.Context, workspaceID, variableID string) (*Variable, error) { if !validStringID(&workspaceID) { return nil, ErrInvalidWorkspaceID } if !validStringID(&variableID) { - return nil, errors.New("invalid value for variable ID") + return nil, ErrInvalidVariableID } u := fmt.Sprintf("workspaces/%s/vars/%s", url.QueryEscape(workspaceID), url.QueryEscape(variableID)) @@ -177,37 +193,13 @@ func (s *variables) Read(ctx context.Context, workspaceID string, variableID str return v, err } -// VariableUpdateOptions represents the options for updating a variable. -type VariableUpdateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,vars"` - - // The name of the variable. - Key *string `jsonapi:"attr,key,omitempty"` - - // The value of the variable. - Value *string `jsonapi:"attr,value,omitempty"` - - // The description of the variable. - Description *string `jsonapi:"attr,description,omitempty"` - - // Whether to evaluate the value of the variable as a string of HCL code. - HCL *bool `jsonapi:"attr,hcl,omitempty"` - - // Whether the value is sensitive. - Sensitive *bool `jsonapi:"attr,sensitive,omitempty"` -} - // Update values of an existing variable. -func (s *variables) Update(ctx context.Context, workspaceID string, variableID string, options VariableUpdateOptions) (*Variable, error) { +func (s *variables) Update(ctx context.Context, workspaceID, variableID string, options VariableUpdateOptions) (*Variable, error) { if !validStringID(&workspaceID) { return nil, ErrInvalidWorkspaceID } if !validStringID(&variableID) { - return nil, errors.New("invalid value for variable ID") + return nil, ErrInvalidVariableID } u := fmt.Sprintf("workspaces/%s/vars/%s", url.QueryEscape(workspaceID), url.QueryEscape(variableID)) @@ -226,12 +218,12 @@ func (s *variables) Update(ctx context.Context, workspaceID string, variableID s } // Delete a variable by its ID. -func (s *variables) Delete(ctx context.Context, workspaceID string, variableID string) error { +func (s *variables) Delete(ctx context.Context, workspaceID, variableID string) error { if !validStringID(&workspaceID) { return ErrInvalidWorkspaceID } if !validStringID(&variableID) { - return errors.New("invalid value for variable ID") + return ErrInvalidVariableID } u := fmt.Sprintf("workspaces/%s/vars/%s", url.QueryEscape(workspaceID), url.QueryEscape(variableID)) @@ -242,3 +234,13 @@ func (s *variables) Delete(ctx context.Context, workspaceID string, variableID s return s.client.do(ctx, req, nil) } + +func (o VariableCreateOptions) valid() error { + if !validString(o.Key) { + return ErrRequiredKey + } + if o.Category == nil { + return ErrRequiredCategory + } + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/variable_set.go b/vendor/github.com/hashicorp/go-tfe/variable_set.go new file mode 100644 index 00000000..d666dae6 --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/variable_set.go @@ -0,0 +1,369 @@ +package tfe + +import ( + "context" + "fmt" + "net/url" +) + +// Compile-time proof of interface implementation. +var _ VariableSets = (*variableSets)(nil) + +// VariableSets describes all the Variable Set related methods that the +// Terraform Enterprise API supports. +// +// TFE API docs: https://www.terraform.io/cloud-docs/api-docs/variable-sets +type VariableSets interface { + // List all the variable sets within an organization. + List(ctx context.Context, organization string, options *VariableSetListOptions) (*VariableSetList, error) + + // Create is used to create a new variable set. + Create(ctx context.Context, organization string, options *VariableSetCreateOptions) (*VariableSet, error) + + // Read a variable set by its ID. + Read(ctx context.Context, variableSetID string, options *VariableSetReadOptions) (*VariableSet, error) + + // Update an existing variable set. + Update(ctx context.Context, variableSetID string, options *VariableSetUpdateOptions) (*VariableSet, error) + + // Delete a variable set by ID. + Delete(ctx context.Context, variableSetID string) error + + // Apply variable set to workspaces in the supplied list. + ApplyToWorkspaces(ctx context.Context, variableSetID string, options *VariableSetApplyToWorkspacesOptions) error + + // Remove variable set from workspaces in the supplied list. + RemoveFromWorkspaces(ctx context.Context, variableSetID string, options *VariableSetRemoveFromWorkspacesOptions) error + + // Update list of workspaces to which the variable set is applied to match the supplied list. + UpdateWorkspaces(ctx context.Context, variableSetID string, options *VariableSetUpdateWorkspacesOptions) (*VariableSet, error) +} + +// variableSets implements VariableSets. +type variableSets struct { + client *Client +} + +// VariableSetList represents a list of variable sets. +type VariableSetList struct { + *Pagination + Items []*VariableSet +} + +// VariableSet represents a Terraform Enterprise variable set. +type VariableSet struct { + ID string `jsonapi:"primary,varsets"` + Name string `jsonapi:"attr,name"` + Description string `jsonapi:"attr,description"` + Global bool `jsonapi:"attr,global"` + + // Relations + Organization *Organization `jsonapi:"relation,organization"` + Workspaces []*Workspace `jsonapi:"relation,workspaces,omitempty"` + Variables []*VariableSetVariable `jsonapi:"relation,vars,omitempty"` +} + +// A list of relations to include. See available resources +// https://www.terraform.io/docs/cloud/api/admin/organizations.html#available-related-resources +type VariableSetIncludeOpt string + +const ( + VariableSetWorkspaces VariableSetIncludeOpt = "workspaces" + VariableSetVars VariableSetIncludeOpt = "vars" +) + +// VariableSetListOptions represents the options for listing variable sets. +type VariableSetListOptions struct { + ListOptions + Include string `url:"include"` +} + +// VariableSetCreateOptions represents the options for creating a new variable set within in a organization. +type VariableSetCreateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,varsets"` + + // The name of the variable set. + // Affects variable precedence when there are conflicts between Variable Sets + // https://www.terraform.io/cloud-docs/api-docs/variable-sets#apply-variable-set-to-workspaces + Name *string `jsonapi:"attr,name"` + + // A description to provide context for the variable set. + Description *string `jsonapi:"attr,description,omitempty"` + + // If true the variable set is considered in all runs in the organization. + Global *bool `jsonapi:"attr,global,omitempty"` +} + +// VariableSetReadOptions represents the options for reading variable sets. +type VariableSetReadOptions struct { + Include *[]VariableSetIncludeOpt `url:"include:omitempty"` +} + +// VariableSetUpdateOptions represents the options for updating a variable set. +type VariableSetUpdateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,varsets"` + + // The name of the variable set. + // Affects variable precedence when there are conflicts between Variable Sets + // https://www.terraform.io/cloud-docs/api-docs/variable-sets#apply-variable-set-to-workspaces + Name *string `jsonapi:"attr,name,omitempty"` + + // A description to provide context for the variable set. + Description *string `jsonapi:"attr,description,omitempty"` + + // If true the variable set is considered in all runs in the organization. + Global *bool `jsonapi:"attr,global,omitempty"` +} + +// VariableSetApplyToWorkspacesOptions represents the options for applying variable sets to workspaces. +type VariableSetApplyToWorkspacesOptions struct { + // The workspaces to apply the variable set to (additive). + Workspaces []*Workspace +} + +// VariableSetRemoveFromWorkspacesOptions represents the options for removing variable sets from workspaces. +type VariableSetRemoveFromWorkspacesOptions struct { + // The workspaces to remove the variable set from. + Workspaces []*Workspace +} + +// VariableSetUpdateWorkspacesOptions represents a subset of update options specifically for applying variable sets to workspaces +type VariableSetUpdateWorkspacesOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,varsets"` + + // The workspaces to be applied to. An empty set means remove all applied + Workspaces []*Workspace `jsonapi:"relation,workspaces"` +} + +type privateVariableSetUpdateWorkspacesOptions struct { + Type string `jsonapi:"primary,varsets"` + Global bool `jsonapi:"attr,global"` + Workspaces []*Workspace `jsonapi:"relation,workspaces"` +} + +// List all Variable Sets in the organization +func (s *variableSets) List(ctx context.Context, organization string, options *VariableSetListOptions) (*VariableSetList, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg + } + if options != nil { + if err := options.valid(); err != nil { + return nil, err + } + } + + u := fmt.Sprintf("organizations/%s/varsets", url.QueryEscape(organization)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + vl := &VariableSetList{} + err = s.client.do(ctx, req, vl) + if err != nil { + return nil, err + } + + return vl, nil +} + +// Create is used to create a new variable set. +func (s *variableSets) Create(ctx context.Context, organization string, options *VariableSetCreateOptions) (*VariableSet, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg + } + if err := options.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf("organizations/%s/varsets", url.QueryEscape(organization)) + req, err := s.client.newRequest("POST", u, options) + if err != nil { + return nil, err + } + + vl := &VariableSet{} + err = s.client.do(ctx, req, vl) + if err != nil { + return nil, err + } + + return vl, nil +} + +// Read is used to inspect a given variable set based on ID +func (s *variableSets) Read(ctx context.Context, variableSetID string, options *VariableSetReadOptions) (*VariableSet, error) { + if !validStringID(&variableSetID) { + return nil, ErrInvalidVariableSetID + } + + u := fmt.Sprintf("varsets/%s", url.QueryEscape(variableSetID)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + vs := &VariableSet{} + err = s.client.do(ctx, req, vs) + if err != nil { + return nil, err + } + + return vs, err +} + +// Update an existing variable set. +func (s *variableSets) Update(ctx context.Context, variableSetID string, options *VariableSetUpdateOptions) (*VariableSet, error) { + if !validStringID(&variableSetID) { + return nil, ErrInvalidVariableSetID + } + + u := fmt.Sprintf("varsets/%s", url.QueryEscape(variableSetID)) + req, err := s.client.newRequest("PATCH", u, options) + if err != nil { + return nil, err + } + + v := &VariableSet{} + err = s.client.do(ctx, req, v) + if err != nil { + return nil, err + } + + return v, nil +} + +// Delete a variable set by its ID. +func (s *variableSets) Delete(ctx context.Context, variableSetID string) error { + if !validStringID(&variableSetID) { + return ErrInvalidVariableSetID + } + + u := fmt.Sprintf("varsets/%s", url.QueryEscape(variableSetID)) + req, err := s.client.newRequest("DELETE", u, nil) + if err != nil { + return err + } + + return s.client.do(ctx, req, nil) +} + +// Apply variable set to workspaces in the supplied list. +// Note: this method will return an error if the variable set has global = true. +func (s *variableSets) ApplyToWorkspaces(ctx context.Context, variableSetID string, options *VariableSetApplyToWorkspacesOptions) error { + if !validStringID(&variableSetID) { + return ErrInvalidVariableSetID + } + if err := options.valid(); err != nil { + return err + } + + u := fmt.Sprintf("varsets/%s/relationships/workspaces", url.QueryEscape(variableSetID)) + req, err := s.client.newRequest("POST", u, options.Workspaces) + if err != nil { + return err + } + + return s.client.do(ctx, req, nil) +} + +// Remove variable set from workspaces in the supplied list. +// Note: this method will return an error if the variable set has global = true. +func (s *variableSets) RemoveFromWorkspaces(ctx context.Context, variableSetID string, options *VariableSetRemoveFromWorkspacesOptions) error { + if !validStringID(&variableSetID) { + return ErrInvalidVariableSetID + } + if err := options.valid(); err != nil { + return err + } + + u := fmt.Sprintf("varsets/%s/relationships/workspaces", url.QueryEscape(variableSetID)) + req, err := s.client.newRequest("DELETE", u, options.Workspaces) + if err != nil { + return err + } + + return s.client.do(ctx, req, nil) +} + +// Update variable set to be applied to only the workspaces in the supplied list. +func (s *variableSets) UpdateWorkspaces(ctx context.Context, variableSetID string, options *VariableSetUpdateWorkspacesOptions) (*VariableSet, error) { + if err := options.valid(); err != nil { + return nil, err + } + + // Use private struct to ensure global is set to false when applying to workspaces + o := privateVariableSetUpdateWorkspacesOptions{ + Global: bool(false), + Workspaces: options.Workspaces, + } + + // We force inclusion of workspaces as that is the primary data for which we are concerned with confirming changes. + u := fmt.Sprintf("varsets/%s?include=%s", url.QueryEscape(variableSetID), VariableSetWorkspaces) + req, err := s.client.newRequest("PATCH", u, &o) + if err != nil { + return nil, err + } + + v := &VariableSet{} + err = s.client.do(ctx, req, v) + if err != nil { + return nil, err + } + + return v, nil +} + +func (o *VariableSetListOptions) valid() error { + return nil +} + +func (o *VariableSetCreateOptions) valid() error { + if o == nil { + return nil + } + if !validString(o.Name) { + return ErrRequiredName + } + if o.Global == nil { + return ErrRequiredGlobalFlag + } + return nil +} + +func (o *VariableSetApplyToWorkspacesOptions) valid() error { + for _, s := range o.Workspaces { + if !validStringID(&s.ID) { + return ErrRequiredWorkspaceID + } + } + return nil +} + +func (o *VariableSetRemoveFromWorkspacesOptions) valid() error { + for _, s := range o.Workspaces { + if !validStringID(&s.ID) { + return ErrRequiredWorkspaceID + } + } + return nil +} + +func (o *VariableSetUpdateWorkspacesOptions) valid() error { + if o == nil || o.Workspaces == nil { + return ErrRequiredWorkspacesList + } + return nil +} diff --git a/vendor/github.com/hashicorp/go-tfe/variable_set_variable.go b/vendor/github.com/hashicorp/go-tfe/variable_set_variable.go new file mode 100644 index 00000000..dea5835f --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/variable_set_variable.go @@ -0,0 +1,241 @@ +package tfe + +import ( + "context" + "fmt" + "net/url" +) + +// Compile-time proof of interface implementation. +var _ VariableSetVariables = (*variableSetVariables)(nil) + +// VariableSetVariables describes all variable variable related methods within the scope of +// Variable Sets that the Terraform Enterprise API supports +// +// TFE API docs: https://www.terraform.io/cloud-docs/api-docs/variable-sets#variable-relationships +type VariableSetVariables interface { + // List all variables in the variable set. + List(ctx context.Context, variableSetID string, options *VariableSetVariableListOptions) (*VariableSetVariableList, error) + + // Create is used to create a new variable within a given variable set + Create(ctx context.Context, variableSetID string, options *VariableSetVariableCreateOptions) (*VariableSetVariable, error) + + // Read a variable by its ID + Read(ctx context.Context, variableSetID string, variableID string) (*VariableSetVariable, error) + + // Update valuse of an existing variable + Update(ctx context.Context, variableSetID string, variableID string, options *VariableSetVariableUpdateOptions) (*VariableSetVariable, error) + + // Delete a variable by its ID + Delete(ctx context.Context, variableSetID string, variableID string) error +} + +type variableSetVariables struct { + client *Client +} + +type VariableSetVariableList struct { + *Pagination + Items []*VariableSetVariable +} + +type VariableSetVariable struct { + ID string `jsonapi:"primary,vars"` + Key string `jsonapi:"attr,key"` + Value string `jsonapi:"attr,value"` + Description string `jsonapi:"attr,description"` + Category CategoryType `jsonapi:"attr,category"` + HCL bool `jsonapi:"attr,hcl"` + Sensitive bool `jsonapi:"attr,sensitive"` + + // Relations + VariableSet *VariableSet `jsonapi:"relation,configurable"` +} + +type VariableSetVariableListOptions struct { + ListOptions +} + +func (o VariableSetVariableListOptions) valid() error { + return nil +} + +// List all variables associated with the given variable set. +func (s *variableSetVariables) List(ctx context.Context, variableSetID string, options *VariableSetVariableListOptions) (*VariableSetVariableList, error) { + if !validStringID(&variableSetID) { + return nil, ErrInvalidVariableSetID + } + if options != nil { + if err := options.valid(); err != nil { + return nil, err + } + } + + u := fmt.Sprintf("varsets/%s/relationships/vars", url.QueryEscape(variableSetID)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + vl := &VariableSetVariableList{} + err = s.client.do(ctx, req, vl) + if err != nil { + return nil, err + } + + return vl, nil +} + +// VariableSetVariableCreatOptions represents the options for creating a new variable within a variable set +type VariableSetVariableCreateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,vars"` + + // The name of the variable. + Key *string `jsonapi:"attr,key"` + + // The value of the variable. + Value *string `jsonapi:"attr,value,omitempty"` + + // The description of the variable. + Description *string `jsonapi:"attr,description,omitempty"` + + // Whether this is a Terraform or environment variable. + Category *CategoryType `jsonapi:"attr,category"` + + // Whether to evaluate the value of the variable as a string of HCL code. + HCL *bool `jsonapi:"attr,hcl,omitempty"` + + // Whether the value is sensitive. + Sensitive *bool `jsonapi:"attr,sensitive,omitempty"` +} + +func (o VariableSetVariableCreateOptions) valid() error { + if !validString(o.Key) { + return ErrRequiredKey + } + if o.Category == nil { + return ErrRequiredCategory + } + return nil +} + +// Create is used to create a new variable. +func (s *variableSetVariables) Create(ctx context.Context, variableSetID string, options *VariableSetVariableCreateOptions) (*VariableSetVariable, error) { + if !validStringID(&variableSetID) { + return nil, ErrInvalidVariableSetID + } + if options != nil { + if err := options.valid(); err != nil { + return nil, err + } + } + + u := fmt.Sprintf("varsets/%s/relationships/vars", url.QueryEscape(variableSetID)) + req, err := s.client.newRequest("POST", u, options) + if err != nil { + return nil, err + } + + v := &VariableSetVariable{} + err = s.client.do(ctx, req, v) + if err != nil { + return nil, err + } + + return v, nil +} + +// Read a variable by its ID. +func (s *variableSetVariables) Read(ctx context.Context, variableSetID, variableID string) (*VariableSetVariable, error) { + if !validStringID(&variableSetID) { + return nil, ErrInvalidVariableSetID + } + if !validStringID(&variableID) { + return nil, ErrInvalidVariableID + } + + u := fmt.Sprintf("varsets/%s/relationships/vars/%s", url.QueryEscape(variableSetID), url.QueryEscape(variableID)) + req, err := s.client.newRequest("GET", u, nil) + + if err != nil { + return nil, err + } + + v := &VariableSetVariable{} + err = s.client.do(ctx, req, v) + if err != nil { + return nil, err + } + + return v, err +} + +// VariableSetVariableUpdateOptions represents the options for updating a variable. +type VariableSetVariableUpdateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,vars"` + + // The name of the variable. + Key *string `jsonapi:"attr,key,omitempty"` + + // The value of the variable. + Value *string `jsonapi:"attr,value,omitempty"` + + // The description of the variable. + Description *string `jsonapi:"attr,description,omitempty"` + + // Whether to evaluate the value of the variable as a string of HCL code. + HCL *bool `jsonapi:"attr,hcl,omitempty"` + + // Whether the value is sensitive. + Sensitive *bool `jsonapi:"attr,sensitive,omitempty"` +} + +// Update values of an existing variable. +func (s *variableSetVariables) Update(ctx context.Context, variableSetID, variableID string, options *VariableSetVariableUpdateOptions) (*VariableSetVariable, error) { + if !validStringID(&variableSetID) { + return nil, ErrInvalidVariableSetID + } + if !validStringID(&variableID) { + return nil, ErrInvalidVariableID + } + + u := fmt.Sprintf("varsets/%s/relationships/vars/%s", url.QueryEscape(variableSetID), url.QueryEscape(variableID)) + req, err := s.client.newRequest("PATCH", u, options) + if err != nil { + return nil, err + } + + v := &VariableSetVariable{} + err = s.client.do(ctx, req, v) + if err != nil { + return nil, err + } + + return v, nil +} + +// Delete a variable by its ID. +func (s *variableSetVariables) Delete(ctx context.Context, variableSetID, variableID string) error { + if !validStringID(&variableSetID) { + return ErrInvalidVariableSetID + } + if !validStringID(&variableID) { + return ErrInvalidVariableID + } + + u := fmt.Sprintf("varsets/%s/relationships/vars/%s", url.QueryEscape(variableSetID), url.QueryEscape(variableID)) + req, err := s.client.newRequest("DELETE", u, nil) + if err != nil { + return err + } + + return s.client.do(ctx, req, nil) +} diff --git a/vendor/github.com/hashicorp/go-tfe/workspace.go b/vendor/github.com/hashicorp/go-tfe/workspace.go index 187f3952..df627b81 100644 --- a/vendor/github.com/hashicorp/go-tfe/workspace.go +++ b/vendor/github.com/hashicorp/go-tfe/workspace.go @@ -2,7 +2,6 @@ package tfe import ( "context" - "errors" "fmt" "io" "net/url" @@ -19,7 +18,7 @@ var _ Workspaces = (*workspaces)(nil) // TFE API docs: https://www.terraform.io/docs/cloud/api/workspaces.html type Workspaces interface { // List all the workspaces within an organization. - List(ctx context.Context, organization string, options WorkspaceListOptions) (*WorkspaceList, error) + List(ctx context.Context, organization string, options *WorkspaceListOptions) (*WorkspaceList, error) // Create is used to create a new workspace. Create(ctx context.Context, organization string, options WorkspaceCreateOptions) (*Workspace, error) @@ -72,8 +71,8 @@ type Workspaces interface { // UnassignSSHKey from a workspace. UnassignSSHKey(ctx context.Context, workspaceID string) (*Workspace, error) - // RemoteStateConsumers reads the remote state consumers for a workspace. - RemoteStateConsumers(ctx context.Context, workspaceID string, options *RemoteStateConsumersListOptions) (*WorkspaceList, error) + // ListRemoteStateConsumers reads the remote state consumers for a workspace. + ListRemoteStateConsumers(ctx context.Context, workspaceID string, options *RemoteStateConsumersListOptions) (*WorkspaceList, error) // AddRemoteStateConsumers adds remote state consumers to a workspace. AddRemoteStateConsumers(ctx context.Context, workspaceID string, options WorkspaceAddRemoteStateConsumersOptions) error @@ -85,8 +84,8 @@ type Workspaces interface { // to match the workspaces in the update options. UpdateRemoteStateConsumers(ctx context.Context, workspaceID string, options WorkspaceUpdateRemoteStateConsumersOptions) error - // Tags reads the tags for a workspace. - Tags(ctx context.Context, workspaceID string, options WorkspaceTagListOptions) (*TagList, error) + // ListTags reads the tags for a workspace. + ListTags(ctx context.Context, workspaceID string, options *WorkspaceTagListOptions) (*TagList, error) // AddTags appends tags to a workspace AddTags(ctx context.Context, workspaceID string, options WorkspaceAddTagsOptions) error @@ -132,6 +131,7 @@ type Workspace struct { StructuredRunOutputEnabled bool `jsonapi:"attr,structured-run-output-enabled"` TerraformVersion string `jsonapi:"attr,terraform-version"` TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes"` + TriggerPatterns []string `jsonapi:"attr,trigger-patterns"` VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"` WorkingDirectory string `jsonapi:"attr,working-directory"` UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"` @@ -144,12 +144,13 @@ type Workspace struct { TagNames []string `jsonapi:"attr,tag-names"` // Relations - AgentPool *AgentPool `jsonapi:"relation,agent-pool"` - CurrentRun *Run `jsonapi:"relation,current-run"` - Organization *Organization `jsonapi:"relation,organization"` - SSHKey *SSHKey `jsonapi:"relation,ssh-key"` - Outputs []*WorkspaceOutputs `jsonapi:"relation,outputs"` - Tags []*Tag `jsonapi:"relation,tags"` + AgentPool *AgentPool `jsonapi:"relation,agent-pool"` + CurrentRun *Run `jsonapi:"relation,current-run"` + CurrentStateVersion *StateVersion `jsonapi:"relation,current-state-version"` + Organization *Organization `jsonapi:"relation,organization"` + SSHKey *SSHKey `jsonapi:"relation,ssh-key"` + Outputs []*WorkspaceOutputs `jsonapi:"relation,outputs"` + Tags []*Tag `jsonapi:"relation,tags"` } type WorkspaceOutputs struct { @@ -181,6 +182,7 @@ type VCSRepo struct { OAuthTokenID string `jsonapi:"attr,oauth-token-id"` RepositoryHTTPURL string `jsonapi:"attr,repository-http-url"` ServiceProvider string `jsonapi:"attr,service-provider"` + WebhookURL string `jsonapi:"attr,webhook-url"` } // WorkspaceActions represents the workspace actions. @@ -193,6 +195,7 @@ type WorkspacePermissions struct { CanDestroy bool `jsonapi:"attr,can-destroy"` CanForceUnlock bool `jsonapi:"attr,can-force-unlock"` CanLock bool `jsonapi:"attr,can-lock"` + CanManageRunTasks bool `jsonapi:"attr,can-manage-run-tasks"` CanQueueApply bool `jsonapi:"attr,can-queue-apply"` CanQueueDestroy bool `jsonapi:"attr,can-queue-destroy"` CanQueueRun bool `jsonapi:"attr,can-queue-run"` @@ -202,44 +205,43 @@ type WorkspacePermissions struct { CanUpdateVariable bool `jsonapi:"attr,can-update-variable"` } +// WSIncludeOpt represents the available options for include query params. +// https://www.terraform.io/docs/cloud/api/workspaces.html#available-related-resources +type WSIncludeOpt string + +const ( + WSOrganization WSIncludeOpt = "organization" + WSCurrentConfigVer WSIncludeOpt = "current_configuration_version" + WSCurrentConfigVerIngress WSIncludeOpt = "current_configuration_version.ingress_attributes" + WSCurrentRun WSIncludeOpt = "current_run" + WSCurrentRunPlan WSIncludeOpt = "current_run.plan" + WSCurrentRunConfigVer WSIncludeOpt = "current_run.configuration_version" + WSCurrentrunConfigVerIngress WSIncludeOpt = "current_run.configuration_version.ingress_attributes" + WSLockedBy WSIncludeOpt = "locked_by" + WSReadme WSIncludeOpt = "readme" + WSOutputs WSIncludeOpt = "outputs" + WSCurrentStateVer WSIncludeOpt = "current-state-version" +) + // WorkspaceReadOptions represents the options for reading a workspace. type WorkspaceReadOptions struct { - Include string `url:"include"` + // Optional: A list of relations to include. + // https://www.terraform.io/docs/cloud/api/workspaces.html#available-related-resources + Include []WSIncludeOpt `url:"include,omitempty"` } // WorkspaceListOptions represents the options for listing workspaces. type WorkspaceListOptions struct { ListOptions - // A search string (partial workspace name) used to filter the results. - Search *string `url:"search[name],omitempty"` + // Optional: A search string (partial workspace name) used to filter the results. + Search string `url:"search[name],omitempty"` - // A search string (comma-separated tag names) used to filter the results. - Tags *string `url:"search[tags],omitempty"` + // Optional: A search string (comma-separated tag names) used to filter the results. + Tags string `url:"search[tags],omitempty"` - // A list of relations to include. See available resources https://www.terraform.io/docs/cloud/api/workspaces.html#available-related-resources - Include *string `url:"include,omitempty"` -} - -// List all the workspaces within an organization. -func (s *workspaces) List(ctx context.Context, organization string, options WorkspaceListOptions) (*WorkspaceList, error) { - if !validStringID(&organization) { - return nil, ErrInvalidOrg - } - - u := fmt.Sprintf("organizations/%s/workspaces", url.QueryEscape(organization)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - wl := &WorkspaceList{} - err = s.client.do(ctx, req, wl) - if err != nil { - return nil, err - } - - return wl, nil + // Optional: A list of relations to include. See available resources https://www.terraform.io/docs/cloud/api/workspaces.html#available-related-resources + Include []WSIncludeOpt `url:"include,omitempty"` } // WorkspaceCreateOptions represents the options for creating a new workspace. @@ -250,27 +252,27 @@ type WorkspaceCreateOptions struct { // https://jsonapi.org/format/#crud-creating Type string `jsonapi:"primary,workspaces"` - // Required when execution-mode is set to agent. The ID of the agent pool + // Required when: execution-mode is set to agent. The ID of the agent pool // belonging to the workspace's organization. This value must not be specified // if execution-mode is set to remote or local or if operations is set to true. AgentPoolID *string `jsonapi:"attr,agent-pool-id,omitempty"` - // Whether destroy plans can be queued on the workspace. + // Optional: Whether destroy plans can be queued on the workspace. AllowDestroyPlan *bool `jsonapi:"attr,allow-destroy-plan,omitempty"` - // Whether to automatically apply changes when a Terraform plan is successful. + // Optional: Whether to automatically apply changes when a Terraform plan is successful. AutoApply *bool `jsonapi:"attr,auto-apply,omitempty"` - // A description for the workspace. + // Optional: A description for the workspace. Description *string `jsonapi:"attr,description,omitempty"` - // Which execution mode to use. Valid values are remote, local, and agent. + // Optional: Which execution mode to use. Valid values are remote, local, and agent. // When set to local, the workspace will be used for state storage only. // This value must not be specified if operations is specified. // 'agent' execution mode is not available in Terraform Enterprise. ExecutionMode *string `jsonapi:"attr,execution-mode,omitempty"` - // Whether to filter runs based on the changed files in a VCS push. If + // Optional: Whether to filter runs based on the changed files in a VCS push. If // enabled, the working directory and trigger prefixes describe a set of // paths which must contain changes for a VCS push to trigger a run. If // disabled, any push will trigger a run. @@ -278,7 +280,7 @@ type WorkspaceCreateOptions struct { GlobalRemoteState *bool `jsonapi:"attr,global-remote-state,omitempty"` - // The legacy TFE environment to use as the source of the migration, in the + // Optional: The legacy TFE environment to use as the source of the migration, in the // form organization/environment. Omit this unless you are migrating a legacy // environment. MigrationEnvironment *string `jsonapi:"attr,migration-environment,omitempty"` @@ -326,6 +328,10 @@ type WorkspaceCreateOptions struct { // tracked for changes. See FileTriggersEnabled above for more details. TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes,omitempty"` + // Optional: List of patterns used to match against changed files in order + // to decide whether to trigger a run or not. + TriggerPatterns []string `jsonapi:"attr,trigger-patterns,omitempty"` + // Settings for the workspace's VCS repository. If omitted, the workspace is // created without a VCS repo. If included, you must specify at least the // oauth-token-id and identifier keys below. @@ -350,24 +356,195 @@ type VCSRepoOptions struct { OAuthTokenID *string `json:"oauth-token-id,omitempty"` } -func (o WorkspaceCreateOptions) valid() error { - if !validString(o.Name) { - return ErrRequiredName - } - if !validStringID(o.Name) { - return ErrInvalidName +// WorkspaceUpdateOptions represents the options for updating a workspace. +type WorkspaceUpdateOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,workspaces"` + + // Required when: execution-mode is set to agent. The ID of the agent pool + // belonging to the workspace's organization. This value must not be specified + // if execution-mode is set to remote or local or if operations is set to true. + AgentPoolID *string `jsonapi:"attr,agent-pool-id,omitempty"` + + // Optional: Whether destroy plans can be queued on the workspace. + AllowDestroyPlan *bool `jsonapi:"attr,allow-destroy-plan,omitempty"` + + // Optional: Whether to automatically apply changes when a Terraform plan is successful. + AutoApply *bool `jsonapi:"attr,auto-apply,omitempty"` + + // Optional: A new name for the workspace, which can only include letters, numbers, -, + // and _. This will be used as an identifier and must be unique in the + // organization. Warning: Changing a workspace's name changes its URL in the + // API and UI. + Name *string `jsonapi:"attr,name,omitempty"` + + // Optional: A description for the workspace. + Description *string `jsonapi:"attr,description,omitempty"` + + // Optional: Which execution mode to use. Valid values are remote, local, and agent. + // When set to local, the workspace will be used for state storage only. + // This value must not be specified if operations is specified. + // 'agent' execution mode is not available in Terraform Enterprise. + ExecutionMode *string `jsonapi:"attr,execution-mode,omitempty"` + + // Optional: Whether to filter runs based on the changed files in a VCS push. If + // enabled, the working directory and trigger prefixes describe a set of + // paths which must contain changes for a VCS push to trigger a run. If + // disabled, any push will trigger a run. + FileTriggersEnabled *bool `jsonapi:"attr,file-triggers-enabled,omitempty"` + + // Optional: + GlobalRemoteState *bool `jsonapi:"attr,global-remote-state,omitempty"` + + // DEPRECATED. Whether the workspace will use remote or local execution mode. + // Use ExecutionMode instead. + Operations *bool `jsonapi:"attr,operations,omitempty"` + + // Optional: Whether to queue all runs. Unless this is set to true, runs triggered by + // a webhook will not be queued until at least one run is manually queued. + QueueAllRuns *bool `jsonapi:"attr,queue-all-runs,omitempty"` + + // Optional: Whether this workspace allows speculative plans. Setting this to false + // prevents Terraform Cloud or the Terraform Enterprise instance from + // running plans on pull requests, which can improve security if the VCS + // repository is public or includes untrusted contributors. + SpeculativeEnabled *bool `jsonapi:"attr,speculative-enabled,omitempty"` + + // BETA. Enable the experimental advanced run user interface. + // This only applies to runs using Terraform version 0.15.2 or newer, + // and runs executed using older versions will see the classic experience + // regardless of this setting. + StructuredRunOutputEnabled *bool `jsonapi:"attr,structured-run-output-enabled,omitempty"` + + // Optional: The version of Terraform to use for this workspace. + TerraformVersion *string `jsonapi:"attr,terraform-version,omitempty"` + + // Optional: List of repository-root-relative paths which list all locations to be + // tracked for changes. See FileTriggersEnabled above for more details. + TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes,omitempty"` + + // Optional: List of patterns used to match against changed files in order + // to decide whether to trigger a run or not. + TriggerPatterns []string `jsonapi:"attr,trigger-patterns,omitempty"` + + // Optional: To delete a workspace's existing VCS repo, specify null instead of an + // object. To modify a workspace's existing VCS repo, include whichever of + // the keys below you wish to modify. To add a new VCS repo to a workspace + // that didn't previously have one, include at least the oauth-token-id and + // identifier keys. + VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"` + + // Optional: A relative path that Terraform will execute within. This defaults to the + // root of your repository and is typically set to a subdirectory matching + // the environment when multiple environments exist within the same + // repository. + WorkingDirectory *string `jsonapi:"attr,working-directory,omitempty"` +} + +// WorkspaceLockOptions represents the options for locking a workspace. +type WorkspaceLockOptions struct { + // Specifies the reason for locking the workspace. + Reason *string `jsonapi:"attr,reason,omitempty"` +} + +// workspaceRemoveVCSConnectionOptions +type workspaceRemoveVCSConnectionOptions struct { + ID string `jsonapi:"primary,workspaces"` + VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo"` +} + +// WorkspaceAssignSSHKeyOptions represents the options to assign an SSH key to +// a workspace. +type WorkspaceAssignSSHKeyOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,workspaces"` + + // The SSH key ID to assign. + SSHKeyID *string `jsonapi:"attr,id"` +} + +// workspaceUnassignSSHKeyOptions represents the options to unassign an SSH key +// to a workspace. +type workspaceUnassignSSHKeyOptions struct { + // Type is a public field utilized by JSON:API to + // set the resource type via the field tag. + // It is not a user-defined value and does not need to be set. + // https://jsonapi.org/format/#crud-creating + Type string `jsonapi:"primary,workspaces"` + + // Must be nil to unset the currently assigned SSH key. + SSHKeyID *string `jsonapi:"attr,id"` +} + +type RemoteStateConsumersListOptions struct { + ListOptions +} + +// WorkspaceAddRemoteStateConsumersOptions represents the options for adding remote state consumers +// to a workspace. +type WorkspaceAddRemoteStateConsumersOptions struct { + // The workspaces to add as remote state consumers to the workspace. + Workspaces []*Workspace +} + +// WorkspaceRemoveRemoteStateConsumersOptions represents the options for removing remote state +// consumers from a workspace. +type WorkspaceRemoveRemoteStateConsumersOptions struct { + // The workspaces to remove as remote state consumers from the workspace. + Workspaces []*Workspace +} + +// WorkspaceUpdateRemoteStateConsumersOptions represents the options for +// updatintg remote state consumers from a workspace. +type WorkspaceUpdateRemoteStateConsumersOptions struct { + // The workspaces to update remote state consumers for the workspace. + Workspaces []*Workspace +} + +type WorkspaceTagListOptions struct { + ListOptions + + // A query string used to filter workspace tags. + // Any workspace tag with a name partially matching this value will be returned. + Query *string `url:"name,omitempty"` +} + +type WorkspaceAddTagsOptions struct { + Tags []*Tag +} + +type WorkspaceRemoveTagsOptions struct { + Tags []*Tag +} + +// List all the workspaces within an organization. +func (s *workspaces) List(ctx context.Context, organization string, options *WorkspaceListOptions) (*WorkspaceList, error) { + if !validStringID(&organization) { + return nil, ErrInvalidOrg } - if o.Operations != nil && o.ExecutionMode != nil { - return errors.New("operations is deprecated and cannot be specified when execution mode is used") + if err := options.valid(); err != nil { + return nil, err } - if o.AgentPoolID != nil && (o.ExecutionMode == nil || *o.ExecutionMode != "agent") { - return errors.New("specifying an agent pool ID requires 'agent' execution mode") + + u := fmt.Sprintf("organizations/%s/workspaces", url.QueryEscape(organization)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err } - if o.AgentPoolID == nil && (o.ExecutionMode != nil && *o.ExecutionMode == "agent") { - return errors.New("'agent' execution mode requires an agent pool ID to be specified") + + wl := &WorkspaceList{} + err = s.client.do(ctx, req, wl) + if err != nil { + return nil, err } - return nil + return wl, nil } // Create is used to create a new workspace. @@ -400,13 +577,16 @@ func (s *workspaces) Read(ctx context.Context, organization, workspace string) ( } // ReadWithOptions reads a workspace by name and organization name with given options. -func (s *workspaces) ReadWithOptions(ctx context.Context, organization string, workspace string, options *WorkspaceReadOptions) (*Workspace, error) { +func (s *workspaces) ReadWithOptions(ctx context.Context, organization, workspace string, options *WorkspaceReadOptions) (*Workspace, error) { if !validStringID(&organization) { return nil, ErrInvalidOrg } if !validStringID(&workspace) { return nil, ErrInvalidWorkspaceValue } + if err := options.valid(); err != nil { + return nil, err + } u := fmt.Sprintf( "organizations/%s/workspaces/%s", @@ -469,117 +649,20 @@ func (s *workspaces) Readme(ctx context.Context, workspaceID string) (io.Reader, u := fmt.Sprintf("workspaces/%s?include=readme", url.QueryEscape(workspaceID)) req, err := s.client.newRequest("GET", u, nil) - if err != nil { - return nil, err - } - - r := &workspaceWithReadme{} - err = s.client.do(ctx, req, r) - if err != nil { - return nil, err - } - if r.Readme == nil { - return nil, nil - } - - return strings.NewReader(r.Readme.RawMarkdown), nil -} - -// WorkspaceUpdateOptions represents the options for updating a workspace. -type WorkspaceUpdateOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,workspaces"` - - // Required when execution-mode is set to agent. The ID of the agent pool - // belonging to the workspace's organization. This value must not be specified - // if execution-mode is set to remote or local or if operations is set to true. - AgentPoolID *string `jsonapi:"attr,agent-pool-id,omitempty"` - - // Whether destroy plans can be queued on the workspace. - AllowDestroyPlan *bool `jsonapi:"attr,allow-destroy-plan,omitempty"` - - // Whether to automatically apply changes when a Terraform plan is successful. - AutoApply *bool `jsonapi:"attr,auto-apply,omitempty"` - - // A new name for the workspace, which can only include letters, numbers, -, - // and _. This will be used as an identifier and must be unique in the - // organization. Warning: Changing a workspace's name changes its URL in the - // API and UI. - Name *string `jsonapi:"attr,name,omitempty"` - - // A description for the workspace. - Description *string `jsonapi:"attr,description,omitempty"` - - // Which execution mode to use. Valid values are remote, local, and agent. - // When set to local, the workspace will be used for state storage only. - // This value must not be specified if operations is specified. - // 'agent' execution mode is not available in Terraform Enterprise. - ExecutionMode *string `jsonapi:"attr,execution-mode,omitempty"` - - // Whether to filter runs based on the changed files in a VCS push. If - // enabled, the working directory and trigger prefixes describe a set of - // paths which must contain changes for a VCS push to trigger a run. If - // disabled, any push will trigger a run. - FileTriggersEnabled *bool `jsonapi:"attr,file-triggers-enabled,omitempty"` - - GlobalRemoteState *bool `jsonapi:"attr,global-remote-state,omitempty"` - - // DEPRECATED. Whether the workspace will use remote or local execution mode. - // Use ExecutionMode instead. - Operations *bool `jsonapi:"attr,operations,omitempty"` - - // Whether to queue all runs. Unless this is set to true, runs triggered by - // a webhook will not be queued until at least one run is manually queued. - QueueAllRuns *bool `jsonapi:"attr,queue-all-runs,omitempty"` - - // Whether this workspace allows speculative plans. Setting this to false - // prevents Terraform Cloud or the Terraform Enterprise instance from - // running plans on pull requests, which can improve security if the VCS - // repository is public or includes untrusted contributors. - SpeculativeEnabled *bool `jsonapi:"attr,speculative-enabled,omitempty"` - - // BETA. Enable the experimental advanced run user interface. - // This only applies to runs using Terraform version 0.15.2 or newer, - // and runs executed using older versions will see the classic experience - // regardless of this setting. - StructuredRunOutputEnabled *bool `jsonapi:"attr,structured-run-output-enabled,omitempty"` - - // The version of Terraform to use for this workspace. - TerraformVersion *string `jsonapi:"attr,terraform-version,omitempty"` - - // List of repository-root-relative paths which list all locations to be - // tracked for changes. See FileTriggersEnabled above for more details. - TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes,omitempty"` - - // To delete a workspace's existing VCS repo, specify null instead of an - // object. To modify a workspace's existing VCS repo, include whichever of - // the keys below you wish to modify. To add a new VCS repo to a workspace - // that didn't previously have one, include at least the oauth-token-id and - // identifier keys. - VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo,omitempty"` - - // A relative path that Terraform will execute within. This defaults to the - // root of your repository and is typically set to a subdirectory matching - // the environment when multiple environments exist within the same - // repository. - WorkingDirectory *string `jsonapi:"attr,working-directory,omitempty"` -} - -func (o WorkspaceUpdateOptions) valid() error { - if o.Name != nil && !validStringID(o.Name) { - return ErrInvalidName + if err != nil { + return nil, err } - if o.Operations != nil && o.ExecutionMode != nil { - return errors.New("operations is deprecated and cannot be specified when execution mode is used") + + r := &workspaceWithReadme{} + err = s.client.do(ctx, req, r) + if err != nil { + return nil, err } - if o.AgentPoolID == nil && (o.ExecutionMode != nil && *o.ExecutionMode == "agent") { - return errors.New("'agent' execution mode requires an agent pool ID to be specified") + if r.Readme == nil { + return nil, nil } - return nil + return strings.NewReader(r.Readme.RawMarkdown), nil } // Update settings of an existing workspace. @@ -671,12 +754,6 @@ func (s *workspaces) DeleteByID(ctx context.Context, workspaceID string) error { return s.client.do(ctx, req, nil) } -// workspaceRemoveVCSConnectionOptions -type workspaceRemoveVCSConnectionOptions struct { - ID string `jsonapi:"primary,workspaces"` - VCSRepo *VCSRepoOptions `jsonapi:"attr,vcs-repo"` -} - // RemoveVCSConnection from a workspace. func (s *workspaces) RemoveVCSConnection(ctx context.Context, organization, workspace string) (*Workspace, error) { if !validStringID(&organization) { @@ -728,12 +805,6 @@ func (s *workspaces) RemoveVCSConnectionByID(ctx context.Context, workspaceID st return w, nil } -// WorkspaceLockOptions represents the options for locking a workspace. -type WorkspaceLockOptions struct { - // Specifies the reason for locking the workspace. - Reason *string `jsonapi:"attr,reason,omitempty"` -} - // Lock a workspace by its ID. func (s *workspaces) Lock(ctx context.Context, workspaceID string, options WorkspaceLockOptions) (*Workspace, error) { if !validStringID(&workspaceID) { @@ -797,29 +868,6 @@ func (s *workspaces) ForceUnlock(ctx context.Context, workspaceID string) (*Work return w, nil } -// WorkspaceAssignSSHKeyOptions represents the options to assign an SSH key to -// a workspace. -type WorkspaceAssignSSHKeyOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,workspaces"` - - // The SSH key ID to assign. - SSHKeyID *string `jsonapi:"attr,id"` -} - -func (o WorkspaceAssignSSHKeyOptions) valid() error { - if !validString(o.SSHKeyID) { - return errors.New("SSH key ID is required") - } - if !validStringID(o.SSHKeyID) { - return errors.New("invalid value for SSH key ID") - } - return nil -} - // AssignSSHKey to a workspace. func (s *workspaces) AssignSSHKey(ctx context.Context, workspaceID string, options WorkspaceAssignSSHKeyOptions) (*Workspace, error) { if !validStringID(&workspaceID) { @@ -844,19 +892,6 @@ func (s *workspaces) AssignSSHKey(ctx context.Context, workspaceID string, optio return w, nil } -// workspaceUnassignSSHKeyOptions represents the options to unassign an SSH key -// to a workspace. -type workspaceUnassignSSHKeyOptions struct { - // Type is a public field utilized by JSON:API to - // set the resource type via the field tag. - // It is not a user-defined value and does not need to be set. - // https://jsonapi.org/format/#crud-creating - Type string `jsonapi:"primary,workspaces"` - - // Must be nil to unset the currently assigned SSH key. - SSHKeyID *string `jsonapi:"attr,id"` -} - // UnassignSSHKey from a workspace. func (s *workspaces) UnassignSSHKey(ctx context.Context, workspaceID string) (*Workspace, error) { if !validStringID(&workspaceID) { @@ -878,19 +913,15 @@ func (s *workspaces) UnassignSSHKey(ctx context.Context, workspaceID string) (*W return w, nil } -type RemoteStateConsumersListOptions struct { - ListOptions -} - // RemoteStateConsumers returns the remote state consumers for a given workspace. -func (s *workspaces) RemoteStateConsumers(ctx context.Context, workspaceID string, options *RemoteStateConsumersListOptions) (*WorkspaceList, error) { +func (s *workspaces) ListRemoteStateConsumers(ctx context.Context, workspaceID string, options *RemoteStateConsumersListOptions) (*WorkspaceList, error) { if !validStringID(&workspaceID) { return nil, ErrInvalidWorkspaceID } u := fmt.Sprintf("workspaces/%s/relationships/remote-state-consumers", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("GET", u, &options) + req, err := s.client.newRequest("GET", u, options) if err != nil { return nil, err } @@ -904,23 +935,6 @@ func (s *workspaces) RemoteStateConsumers(ctx context.Context, workspaceID strin return wl, nil } -// WorkspaceAddRemoteStateConsumersOptions represents the options for adding remote state consumers -// to a workspace. -type WorkspaceAddRemoteStateConsumersOptions struct { - /// The workspaces to add as remote state consumers to the workspace. - Workspaces []*Workspace -} - -func (o WorkspaceAddRemoteStateConsumersOptions) valid() error { - if o.Workspaces == nil { - return ErrWorkspacesRequired - } - if len(o.Workspaces) == 0 { - return ErrWorkspaceMinLimit - } - return nil -} - // AddRemoteStateConsumere adds the remote state consumers to a given workspace. func (s *workspaces) AddRemoteStateConsumers(ctx context.Context, workspaceID string, options WorkspaceAddRemoteStateConsumersOptions) error { if !validStringID(&workspaceID) { @@ -939,23 +953,6 @@ func (s *workspaces) AddRemoteStateConsumers(ctx context.Context, workspaceID st return s.client.do(ctx, req, nil) } -// WorkspaceRemoveRemoteStateConsumersOptions represents the options for removing remote state -// consumers from a workspace. -type WorkspaceRemoveRemoteStateConsumersOptions struct { - /// The workspaces to remove as remote state consumers from the workspace. - Workspaces []*Workspace -} - -func (o WorkspaceRemoveRemoteStateConsumersOptions) valid() error { - if o.Workspaces == nil { - return ErrWorkspacesRequired - } - if len(o.Workspaces) == 0 { - return ErrWorkspaceMinLimit - } - return nil -} - // RemoveRemoteStateConsumers removes the remote state consumers for a given workspace. func (s *workspaces) RemoveRemoteStateConsumers(ctx context.Context, workspaceID string, options WorkspaceRemoveRemoteStateConsumersOptions) error { if !validStringID(&workspaceID) { @@ -974,23 +971,6 @@ func (s *workspaces) RemoveRemoteStateConsumers(ctx context.Context, workspaceID return s.client.do(ctx, req, nil) } -// WorkspaceUpdateRemoteStateConsumersOptions represents the options for -// updatintg remote state consumers from a workspace. -type WorkspaceUpdateRemoteStateConsumersOptions struct { - /// The workspaces to update remote state consumers for the workspace. - Workspaces []*Workspace -} - -func (o WorkspaceUpdateRemoteStateConsumersOptions) valid() error { - if o.Workspaces == nil { - return ErrWorkspacesRequired - } - if len(o.Workspaces) == 0 { - return ErrWorkspaceMinLimit - } - return nil -} - // UpdateRemoteStateConsumers removes the remote state consumers for a given workspace. func (s *workspaces) UpdateRemoteStateConsumers(ctx context.Context, workspaceID string, options WorkspaceUpdateRemoteStateConsumersOptions) error { if !validStringID(&workspaceID) { @@ -1009,23 +989,15 @@ func (s *workspaces) UpdateRemoteStateConsumers(ctx context.Context, workspaceID return s.client.do(ctx, req, nil) } -type WorkspaceTagListOptions struct { - ListOptions - - // A query string used to filter workspace tags. - // Any workspace tag with a name partially matching this value will be returned. - Query *string `url:"name,omitempty"` -} - -// Tags returns the tags for a given workspace. -func (s *workspaces) Tags(ctx context.Context, workspaceID string, options WorkspaceTagListOptions) (*TagList, error) { +// ListTags returns the tags for a given workspace. +func (s *workspaces) ListTags(ctx context.Context, workspaceID string, options *WorkspaceTagListOptions) (*TagList, error) { if !validStringID(&workspaceID) { return nil, ErrInvalidWorkspaceID } u := fmt.Sprintf("workspaces/%s/relationships/tags", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("GET", u, &options) + req, err := s.client.newRequest("GET", u, options) if err != nil { return nil, err } @@ -1039,25 +1011,26 @@ func (s *workspaces) Tags(ctx context.Context, workspaceID string, options Works return tl, nil } -type WorkspaceAddTagsOptions struct { - Tags []*Tag -} - -func (o WorkspaceAddTagsOptions) valid() error { - if len(o.Tags) == 0 { - return ErrMissingTagIdentifier +// AddTags adds a list of tags to a workspace. +func (s *workspaces) AddTags(ctx context.Context, workspaceID string, options WorkspaceAddTagsOptions) error { + if !validStringID(&workspaceID) { + return ErrInvalidWorkspaceID } - for _, s := range o.Tags { - if s.Name == "" && s.ID == "" { - return ErrMissingTagIdentifier - } + if err := options.valid(); err != nil { + return err } - return nil + u := fmt.Sprintf("workspaces/%s/relationships/tags", url.QueryEscape(workspaceID)) + req, err := s.client.newRequest("POST", u, options.Tags) + if err != nil { + return err + } + + return s.client.do(ctx, req, nil) } -// AddTags adds a list of tags to a workspace. -func (s *workspaces) AddTags(ctx context.Context, workspaceID string, options WorkspaceAddTagsOptions) error { +// RemoveTags removes a list of tags from a workspace. +func (s *workspaces) RemoveTags(ctx context.Context, workspaceID string, options WorkspaceRemoveTagsOptions) error { if !validStringID(&workspaceID) { return ErrInvalidWorkspaceID } @@ -1066,7 +1039,7 @@ func (s *workspaces) AddTags(ctx context.Context, workspaceID string, options Wo } u := fmt.Sprintf("workspaces/%s/relationships/tags", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("POST", u, options.Tags) + req, err := s.client.newRequest("DELETE", u, options.Tags) if err != nil { return err } @@ -1074,8 +1047,99 @@ func (s *workspaces) AddTags(ctx context.Context, workspaceID string, options Wo return s.client.do(ctx, req, nil) } -type WorkspaceRemoveTagsOptions struct { - Tags []*Tag +func (o WorkspaceCreateOptions) valid() error { + if !validString(o.Name) { + return ErrRequiredName + } + if !validStringID(o.Name) { + return ErrInvalidName + } + if o.Operations != nil && o.ExecutionMode != nil { + return ErrUnsupportedOperations + } + if o.AgentPoolID != nil && (o.ExecutionMode == nil || *o.ExecutionMode != "agent") { + return ErrRequiredAgentMode + } + if o.AgentPoolID == nil && (o.ExecutionMode != nil && *o.ExecutionMode == "agent") { + return ErrRequiredAgentPoolID + } + if o.TriggerPrefixes != nil && len(o.TriggerPrefixes) > 0 && + o.TriggerPatterns != nil && len(o.TriggerPatterns) > 0 { + return ErrUnsupportedBothTriggerPatternsAndPrefixes + } + + return nil +} + +func (o WorkspaceUpdateOptions) valid() error { + if o.Name != nil && !validStringID(o.Name) { + return ErrInvalidName + } + if o.Operations != nil && o.ExecutionMode != nil { + return ErrUnsupportedOperations + } + if o.AgentPoolID == nil && (o.ExecutionMode != nil && *o.ExecutionMode == "agent") { + return ErrRequiredAgentPoolID + } + if o.TriggerPrefixes != nil && len(o.TriggerPrefixes) > 0 && + o.TriggerPatterns != nil && len(o.TriggerPatterns) > 0 { + return ErrUnsupportedBothTriggerPatternsAndPrefixes + } + + return nil +} + +func (o WorkspaceAssignSSHKeyOptions) valid() error { + if !validString(o.SSHKeyID) { + return ErrRequiredSHHKeyID + } + if !validStringID(o.SSHKeyID) { + return ErrInvalidSHHKeyID + } + return nil +} + +func (o WorkspaceAddRemoteStateConsumersOptions) valid() error { + if o.Workspaces == nil { + return ErrWorkspacesRequired + } + if len(o.Workspaces) == 0 { + return ErrWorkspaceMinLimit + } + return nil +} + +func (o WorkspaceRemoveRemoteStateConsumersOptions) valid() error { + if o.Workspaces == nil { + return ErrWorkspacesRequired + } + if len(o.Workspaces) == 0 { + return ErrWorkspaceMinLimit + } + return nil +} + +func (o WorkspaceUpdateRemoteStateConsumersOptions) valid() error { + if o.Workspaces == nil { + return ErrWorkspacesRequired + } + if len(o.Workspaces) == 0 { + return ErrWorkspaceMinLimit + } + return nil +} + +func (o WorkspaceAddTagsOptions) valid() error { + if len(o.Tags) == 0 { + return ErrMissingTagIdentifier + } + for _, s := range o.Tags { + if s.Name == "" && s.ID == "" { + return ErrMissingTagIdentifier + } + } + + return nil } func (o WorkspaceRemoveTagsOptions) valid() error { @@ -1091,20 +1155,39 @@ func (o WorkspaceRemoveTagsOptions) valid() error { return nil } -// RemoveTags removes a list of tags from a workspace. -func (s *workspaces) RemoveTags(ctx context.Context, workspaceID string, options WorkspaceRemoveTagsOptions) error { - if !validStringID(&workspaceID) { - return ErrInvalidWorkspaceID +func (o *WorkspaceListOptions) valid() error { + if o == nil { + return nil // nothing to validate } - if err := options.valid(); err != nil { + + if err := validateWorkspaceIncludeParams(o.Include); err != nil { return err } - u := fmt.Sprintf("workspaces/%s/relationships/tags", url.QueryEscape(workspaceID)) - req, err := s.client.newRequest("DELETE", u, options.Tags) - if err != nil { + return nil +} + +func (o *WorkspaceReadOptions) valid() error { + if o == nil { + return nil // nothing to validate + } + + if err := validateWorkspaceIncludeParams(o.Include); err != nil { return err } - return s.client.do(ctx, req, nil) + return nil +} + +func validateWorkspaceIncludeParams(params []WSIncludeOpt) error { + for _, p := range params { + switch p { + case WSOrganization, WSCurrentConfigVer, WSCurrentConfigVerIngress, WSCurrentRun, WSCurrentRunPlan, WSCurrentRunConfigVer, WSCurrentrunConfigVerIngress, WSLockedBy, WSReadme, WSOutputs, WSCurrentStateVer: + // do nothing + default: + return ErrInvalidIncludeValue + } + } + + return nil } diff --git a/vendor/github.com/hashicorp/go-tfe/workspace_run_task.go b/vendor/github.com/hashicorp/go-tfe/workspace_run_task.go new file mode 100644 index 00000000..cc2eb4cd --- /dev/null +++ b/vendor/github.com/hashicorp/go-tfe/workspace_run_task.go @@ -0,0 +1,204 @@ +package tfe + +import ( + "context" + "fmt" + "net/url" +) + +// Compile-time proof of interface implementation +var _ WorkspaceRunTasks = (*workspaceRunTasks)(nil) + +// WorkspaceRunTasks represent all the run task related methods in the context of a workspace that the Terraform Cloud/Enterprise API supports. +// **Note: This API is still in BETA and subject to change.** +type WorkspaceRunTasks interface { + // Add a run task to a workspace + Create(ctx context.Context, workspaceID string, options WorkspaceRunTaskCreateOptions) (*WorkspaceRunTask, error) + + // List all run tasks for a workspace + List(ctx context.Context, workspaceID string, options *WorkspaceRunTaskListOptions) (*WorkspaceRunTaskList, error) + + // Read a workspace run task by ID + Read(ctx context.Context, workspaceID string, workspaceTaskID string) (*WorkspaceRunTask, error) + + // Update a workspace run task by ID + Update(ctx context.Context, workspaceID string, workspaceTaskID string, options WorkspaceRunTaskUpdateOptions) (*WorkspaceRunTask, error) + + // Delete a workspace's run task by ID + Delete(ctx context.Context, workspaceID string, workspaceTaskID string) error +} + +// workspaceRunTasks implements WorkspaceRunTasks +type workspaceRunTasks struct { + client *Client +} + +// WorkspaceRunTask represents a TFC/E run task that belongs to a workspace +type WorkspaceRunTask struct { + ID string `jsonapi:"primary,workspace-tasks"` + EnforcementLevel TaskEnforcementLevel `jsonapi:"attr,enforcement-level"` + + RunTask *RunTask `jsonapi:"relation,task"` + Workspace *Workspace `jsonapi:"relation,workspace"` +} + +// WorkspaceRunTaskList represents a list of workspace run tasks +type WorkspaceRunTaskList struct { + *Pagination + Items []*WorkspaceRunTask +} + +// WorkspaceRunTaskListOptions represents the set of options for listing workspace run tasks +type WorkspaceRunTaskListOptions struct { + ListOptions +} + +// WorkspaceRunTaskCreateOptions represents the set of options for creating a workspace run task +type WorkspaceRunTaskCreateOptions struct { + Type string `jsonapi:"primary,workspace-tasks"` + // Required: The enforcement level for a run task + EnforcementLevel TaskEnforcementLevel `jsonapi:"attr,enforcement-level"` + // Required: The run task to attach to the workspace + RunTask *RunTask `jsonapi:"relation,task"` +} + +// WorkspaceRunTaskUpdateOptions represent the set of options for updating a workspace run task. +type WorkspaceRunTaskUpdateOptions struct { + Type string `jsonapi:"primary,workspace-tasks"` + EnforcementLevel TaskEnforcementLevel `jsonapi:"attr,enforcement-level,omitempty"` +} + +// List all run tasks attached to a workspace +func (s *workspaceRunTasks) List(ctx context.Context, workspaceID string, options *WorkspaceRunTaskListOptions) (*WorkspaceRunTaskList, error) { + if !validStringID(&workspaceID) { + return nil, ErrInvalidWorkspaceID + } + + u := fmt.Sprintf("workspaces/%s/tasks", url.QueryEscape(workspaceID)) + req, err := s.client.newRequest("GET", u, options) + if err != nil { + return nil, err + } + + rl := &WorkspaceRunTaskList{} + err = s.client.do(ctx, req, rl) + if err != nil { + return nil, err + } + + return rl, nil +} + +// Read a workspace run task by ID +func (s *workspaceRunTasks) Read(ctx context.Context, workspaceID, workspaceTaskID string) (*WorkspaceRunTask, error) { + if !validStringID(&workspaceID) { + return nil, ErrInvalidWorkspaceID + } + + if !validStringID(&workspaceTaskID) { + return nil, ErrInvalidWorkspaceRunTaskID + } + + u := fmt.Sprintf( + "workspaces/%s/tasks/%s", + url.QueryEscape(workspaceID), + url.QueryEscape(workspaceTaskID), + ) + req, err := s.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + wr := &WorkspaceRunTask{} + err = s.client.do(ctx, req, wr) + if err != nil { + return nil, err + } + + return wr, nil +} + +// Create is used to attach a run task to a workspace, or in other words: create a workspace run task. The run task must exist in the workspace's organization. +func (s *workspaceRunTasks) Create(ctx context.Context, workspaceID string, options WorkspaceRunTaskCreateOptions) (*WorkspaceRunTask, error) { + if !validStringID(&workspaceID) { + return nil, ErrInvalidWorkspaceID + } + + if err := options.valid(); err != nil { + return nil, err + } + + u := fmt.Sprintf("workspaces/%s/tasks", workspaceID) + req, err := s.client.newRequest("POST", u, &options) + if err != nil { + return nil, err + } + + wr := &WorkspaceRunTask{} + err = s.client.do(ctx, req, wr) + if err != nil { + return nil, err + } + + return wr, nil +} + +// Update an existing workspace run task by ID +func (s *workspaceRunTasks) Update(ctx context.Context, workspaceID, workspaceTaskID string, options WorkspaceRunTaskUpdateOptions) (*WorkspaceRunTask, error) { + if !validStringID(&workspaceID) { + return nil, ErrInvalidWorkspaceID + } + + if !validStringID(&workspaceTaskID) { + return nil, ErrInvalidWorkspaceRunTaskID + } + + u := fmt.Sprintf( + "workspaces/%s/tasks/%s", + url.QueryEscape(workspaceID), + url.QueryEscape(workspaceTaskID), + ) + req, err := s.client.newRequest("PATCH", u, &options) + if err != nil { + return nil, err + } + + wr := &WorkspaceRunTask{} + err = s.client.do(ctx, req, wr) + if err != nil { + return nil, err + } + + return wr, nil +} + +// Delete a workspace run task by ID +func (s *workspaceRunTasks) Delete(ctx context.Context, workspaceID, workspaceTaskID string) error { + if !validStringID(&workspaceID) { + return ErrInvalidWorkspaceID + } + + if !validStringID(&workspaceTaskID) { + return ErrInvalidWorkspaceRunTaskType + } + + u := fmt.Sprintf( + "workspaces/%s/tasks/%s", + url.QueryEscape(workspaceID), + url.QueryEscape(workspaceTaskID), + ) + req, err := s.client.newRequest("DELETE", u, nil) + if err != nil { + return err + } + + return s.client.do(ctx, req, nil) +} + +func (o *WorkspaceRunTaskCreateOptions) valid() error { + if o.RunTask.ID == "" { + return ErrInvalidRunTaskID + } + + return nil +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare.go b/vendor/github.com/stretchr/testify/assert/assertion_compare.go index 41649d26..3bb22a97 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_compare.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_compare.go @@ -3,6 +3,7 @@ package assert import ( "fmt" "reflect" + "time" ) type CompareType int @@ -30,6 +31,8 @@ var ( float64Type = reflect.TypeOf(float64(1)) stringType = reflect.TypeOf("") + + timeType = reflect.TypeOf(time.Time{}) ) func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { @@ -299,6 +302,27 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { return compareLess, true } } + // Check for known struct types we can check for compare results. + case reflect.Struct: + { + // All structs enter here. We're not interested in most types. + if !canConvert(obj1Value, timeType) { + break + } + + // time.Time can compared! + timeObj1, ok := obj1.(time.Time) + if !ok { + timeObj1 = obj1Value.Convert(timeType).Interface().(time.Time) + } + + timeObj2, ok := obj2.(time.Time) + if !ok { + timeObj2 = obj2Value.Convert(timeType).Interface().(time.Time) + } + + return compare(timeObj1.UnixNano(), timeObj2.UnixNano(), reflect.Int64) + } } return compareEqual, false @@ -310,7 +334,10 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { // assert.Greater(t, float64(2), float64(1)) // assert.Greater(t, "b", "a") func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { - return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs) + if h, ok := t.(tHelper); ok { + h.Helper() + } + return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...) } // GreaterOrEqual asserts that the first element is greater than or equal to the second @@ -320,7 +347,10 @@ func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface // assert.GreaterOrEqual(t, "b", "a") // assert.GreaterOrEqual(t, "b", "b") func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { - return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs) + if h, ok := t.(tHelper); ok { + h.Helper() + } + return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...) } // Less asserts that the first element is less than the second @@ -329,7 +359,10 @@ func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...in // assert.Less(t, float64(1), float64(2)) // assert.Less(t, "a", "b") func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { - return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs) + if h, ok := t.(tHelper); ok { + h.Helper() + } + return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...) } // LessOrEqual asserts that the first element is less than or equal to the second @@ -339,7 +372,10 @@ func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) // assert.LessOrEqual(t, "a", "b") // assert.LessOrEqual(t, "b", "b") func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { - return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs) + if h, ok := t.(tHelper); ok { + h.Helper() + } + return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...) } // Positive asserts that the specified element is positive @@ -347,8 +383,11 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter // assert.Positive(t, 1) // assert.Positive(t, 1.23) func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } zero := reflect.Zero(reflect.TypeOf(e)) - return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs) + return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs...) } // Negative asserts that the specified element is negative @@ -356,8 +395,11 @@ func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { // assert.Negative(t, -1) // assert.Negative(t, -1.23) func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } zero := reflect.Zero(reflect.TypeOf(e)) - return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs) + return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs...) } func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool { diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go b/vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go new file mode 100644 index 00000000..da867903 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go @@ -0,0 +1,16 @@ +//go:build go1.17 +// +build go1.17 + +// TODO: once support for Go 1.16 is dropped, this file can be +// merged/removed with assertion_compare_go1.17_test.go and +// assertion_compare_legacy.go + +package assert + +import "reflect" + +// Wrapper around reflect.Value.CanConvert, for compatibility +// reasons. +func canConvert(value reflect.Value, to reflect.Type) bool { + return value.CanConvert(to) +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare_legacy.go b/vendor/github.com/stretchr/testify/assert/assertion_compare_legacy.go new file mode 100644 index 00000000..1701af2a --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertion_compare_legacy.go @@ -0,0 +1,16 @@ +//go:build !go1.17 +// +build !go1.17 + +// TODO: once support for Go 1.16 is dropped, this file can be +// merged/removed with assertion_compare_go1.17_test.go and +// assertion_compare_can_convert.go + +package assert + +import "reflect" + +// Older versions of Go does not have the reflect.Value.CanConvert +// method. +func canConvert(value reflect.Value, to reflect.Type) bool { + return false +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go index 4dfd1229..27e2420e 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_format.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go @@ -123,6 +123,18 @@ func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...int return ErrorAs(t, err, target, append([]interface{}{msg}, args...)...) } +// ErrorContainsf asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted") +func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return ErrorContains(t, theError, contains, append([]interface{}{msg}, args...)...) +} + // ErrorIsf asserts that at least one of the errors in err's chain matches target. // This is a wrapper for errors.Is. func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool { diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go index 25337a6f..d9ea368d 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -222,6 +222,30 @@ func (a *Assertions) ErrorAsf(err error, target interface{}, msg string, args .. return ErrorAsf(a.t, err, target, msg, args...) } +// ErrorContains asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// a.ErrorContains(err, expectedErrorSubString) +func (a *Assertions) ErrorContains(theError error, contains string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return ErrorContains(a.t, theError, contains, msgAndArgs...) +} + +// ErrorContainsf asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// a.ErrorContainsf(err, expectedErrorSubString, "error message %s", "formatted") +func (a *Assertions) ErrorContainsf(theError error, contains string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return ErrorContainsf(a.t, theError, contains, msg, args...) +} + // ErrorIs asserts that at least one of the errors in err's chain matches target. // This is a wrapper for errors.Is. func (a *Assertions) ErrorIs(err error, target error, msgAndArgs ...interface{}) bool { diff --git a/vendor/github.com/stretchr/testify/assert/assertion_order.go b/vendor/github.com/stretchr/testify/assert/assertion_order.go index 1c3b4718..75944878 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_order.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_order.go @@ -50,7 +50,7 @@ func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareT // assert.IsIncreasing(t, []float{1, 2}) // assert.IsIncreasing(t, []string{"a", "b"}) func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs) + return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...) } // IsNonIncreasing asserts that the collection is not increasing @@ -59,7 +59,7 @@ func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) boo // assert.IsNonIncreasing(t, []float{2, 1}) // assert.IsNonIncreasing(t, []string{"b", "a"}) func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs) + return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...) } // IsDecreasing asserts that the collection is decreasing @@ -68,7 +68,7 @@ func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) // assert.IsDecreasing(t, []float{2, 1}) // assert.IsDecreasing(t, []string{"b", "a"}) func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs) + return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...) } // IsNonDecreasing asserts that the collection is not decreasing @@ -77,5 +77,5 @@ func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) boo // assert.IsNonDecreasing(t, []float{1, 2}) // assert.IsNonDecreasing(t, []string{"a", "b"}) func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { - return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs) + return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...) } diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go index bcac4401..0357b223 100644 --- a/vendor/github.com/stretchr/testify/assert/assertions.go +++ b/vendor/github.com/stretchr/testify/assert/assertions.go @@ -718,10 +718,14 @@ func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...inte // return (false, false) if impossible. // return (true, false) if element was not found. // return (true, true) if element was found. -func includeElement(list interface{}, element interface{}) (ok, found bool) { +func containsElement(list interface{}, element interface{}) (ok, found bool) { listValue := reflect.ValueOf(list) - listKind := reflect.TypeOf(list).Kind() + listType := reflect.TypeOf(list) + if listType == nil { + return false, false + } + listKind := listType.Kind() defer func() { if e := recover(); e != nil { ok = false @@ -764,7 +768,7 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo h.Helper() } - ok, found := includeElement(s, contains) + ok, found := containsElement(s, contains) if !ok { return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", s), msgAndArgs...) } @@ -787,7 +791,7 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) h.Helper() } - ok, found := includeElement(s, contains) + ok, found := containsElement(s, contains) if !ok { return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...) } @@ -831,7 +835,7 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok for i := 0; i < subsetValue.Len(); i++ { element := subsetValue.Index(i).Interface() - ok, found := includeElement(list, element) + ok, found := containsElement(list, element) if !ok { return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...) } @@ -852,7 +856,7 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) h.Helper() } if subset == nil { - return Fail(t, fmt.Sprintf("nil is the empty set which is a subset of every set"), msgAndArgs...) + return Fail(t, "nil is the empty set which is a subset of every set", msgAndArgs...) } subsetValue := reflect.ValueOf(subset) @@ -875,7 +879,7 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) for i := 0; i < subsetValue.Len(); i++ { element := subsetValue.Index(i).Interface() - ok, found := includeElement(list, element) + ok, found := containsElement(list, element) if !ok { return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...) } @@ -1000,27 +1004,21 @@ func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool { type PanicTestFunc func() // didPanic returns true if the function passed to it panics. Otherwise, it returns false. -func didPanic(f PanicTestFunc) (bool, interface{}, string) { - - didPanic := false - var message interface{} - var stack string - func() { - - defer func() { - if message = recover(); message != nil { - didPanic = true - stack = string(debug.Stack()) - } - }() - - // call the target function - f() +func didPanic(f PanicTestFunc) (didPanic bool, message interface{}, stack string) { + didPanic = true + defer func() { + message = recover() + if didPanic { + stack = string(debug.Stack()) + } }() - return didPanic, message, stack + // call the target function + f() + didPanic = false + return } // Panics asserts that the code inside the specified PanicTestFunc panics. @@ -1161,11 +1159,15 @@ func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs bf, bok := toFloat(actual) if !aok || !bok { - return Fail(t, fmt.Sprintf("Parameters must be numerical"), msgAndArgs...) + return Fail(t, "Parameters must be numerical", msgAndArgs...) + } + + if math.IsNaN(af) && math.IsNaN(bf) { + return true } if math.IsNaN(af) { - return Fail(t, fmt.Sprintf("Expected must not be NaN"), msgAndArgs...) + return Fail(t, "Expected must not be NaN", msgAndArgs...) } if math.IsNaN(bf) { @@ -1188,7 +1190,7 @@ func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAn if expected == nil || actual == nil || reflect.TypeOf(actual).Kind() != reflect.Slice || reflect.TypeOf(expected).Kind() != reflect.Slice { - return Fail(t, fmt.Sprintf("Parameters must be slice"), msgAndArgs...) + return Fail(t, "Parameters must be slice", msgAndArgs...) } actualSlice := reflect.ValueOf(actual) @@ -1250,8 +1252,12 @@ func InDeltaMapValues(t TestingT, expected, actual interface{}, delta float64, m func calcRelativeError(expected, actual interface{}) (float64, error) { af, aok := toFloat(expected) - if !aok { - return 0, fmt.Errorf("expected value %q cannot be converted to float", expected) + bf, bok := toFloat(actual) + if !aok || !bok { + return 0, fmt.Errorf("Parameters must be numerical") + } + if math.IsNaN(af) && math.IsNaN(bf) { + return 0, nil } if math.IsNaN(af) { return 0, errors.New("expected value must not be NaN") @@ -1259,10 +1265,6 @@ func calcRelativeError(expected, actual interface{}) (float64, error) { if af == 0 { return 0, fmt.Errorf("expected value must have a value other than zero to calculate the relative error") } - bf, bok := toFloat(actual) - if !bok { - return 0, fmt.Errorf("actual value %q cannot be converted to float", actual) - } if math.IsNaN(bf) { return 0, errors.New("actual value must not be NaN") } @@ -1298,7 +1300,7 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, m if expected == nil || actual == nil || reflect.TypeOf(actual).Kind() != reflect.Slice || reflect.TypeOf(expected).Kind() != reflect.Slice { - return Fail(t, fmt.Sprintf("Parameters must be slice"), msgAndArgs...) + return Fail(t, "Parameters must be slice", msgAndArgs...) } actualSlice := reflect.ValueOf(actual) @@ -1375,6 +1377,27 @@ func EqualError(t TestingT, theError error, errString string, msgAndArgs ...inte return true } +// ErrorContains asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// assert.ErrorContains(t, err, expectedErrorSubString) +func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !Error(t, theError, msgAndArgs...) { + return false + } + + actual := theError.Error() + if !strings.Contains(actual, contains) { + return Fail(t, fmt.Sprintf("Error %#v does not contain %#v", actual, contains), msgAndArgs...) + } + + return true +} + // matchRegexp return true if a specified regexp matches a string. func matchRegexp(rx interface{}, str interface{}) bool { @@ -1588,12 +1611,17 @@ func diff(expected interface{}, actual interface{}) string { } var e, a string - if et != reflect.TypeOf("") { - e = spewConfig.Sdump(expected) - a = spewConfig.Sdump(actual) - } else { + + switch et { + case reflect.TypeOf(""): e = reflect.ValueOf(expected).String() a = reflect.ValueOf(actual).String() + case reflect.TypeOf(time.Time{}): + e = spewConfigStringerEnabled.Sdump(expected) + a = spewConfigStringerEnabled.Sdump(actual) + default: + e = spewConfig.Sdump(expected) + a = spewConfig.Sdump(actual) } diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ @@ -1625,6 +1653,14 @@ var spewConfig = spew.ConfigState{ MaxDepth: 10, } +var spewConfigStringerEnabled = spew.ConfigState{ + Indent: " ", + DisablePointerAddresses: true, + DisableCapacities: true, + SortKeys: true, + MaxDepth: 10, +} + type tHelper interface { Helper() } diff --git a/vendor/gopkg.in/yaml.v3/decode.go b/vendor/gopkg.in/yaml.v3/decode.go index df36e3a3..0173b698 100644 --- a/vendor/gopkg.in/yaml.v3/decode.go +++ b/vendor/gopkg.in/yaml.v3/decode.go @@ -100,7 +100,10 @@ func (p *parser) peek() yaml_event_type_t { if p.event.typ != yaml_NO_EVENT { return p.event.typ } - if !yaml_parser_parse(&p.parser, &p.event) { + // It's curious choice from the underlying API to generally return a + // positive result on success, but on this case return true in an error + // scenario. This was the source of bugs in the past (issue #666). + if !yaml_parser_parse(&p.parser, &p.event) || p.parser.error != yaml_NO_ERROR { p.fail() } return p.event.typ @@ -320,6 +323,8 @@ type decoder struct { decodeCount int aliasCount int aliasDepth int + + mergedFields map[interface{}]bool } var ( @@ -808,6 +813,11 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) { } } + mergedFields := d.mergedFields + d.mergedFields = nil + + var mergeNode *Node + mapIsNew := false if out.IsNil() { out.Set(reflect.MakeMap(outt)) @@ -815,11 +825,18 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) { } for i := 0; i < l; i += 2 { if isMerge(n.Content[i]) { - d.merge(n.Content[i+1], out) + mergeNode = n.Content[i+1] continue } k := reflect.New(kt).Elem() if d.unmarshal(n.Content[i], k) { + if mergedFields != nil { + ki := k.Interface() + if mergedFields[ki] { + continue + } + mergedFields[ki] = true + } kkind := k.Kind() if kkind == reflect.Interface { kkind = k.Elem().Kind() @@ -833,6 +850,12 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) { } } } + + d.mergedFields = mergedFields + if mergeNode != nil { + d.merge(n, mergeNode, out) + } + d.stringMapType = stringMapType d.generalMapType = generalMapType return true @@ -844,7 +867,8 @@ func isStringMap(n *Node) bool { } l := len(n.Content) for i := 0; i < l; i += 2 { - if n.Content[i].ShortTag() != strTag { + shortTag := n.Content[i].ShortTag() + if shortTag != strTag && shortTag != mergeTag { return false } } @@ -861,7 +885,6 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) { var elemType reflect.Type if sinfo.InlineMap != -1 { inlineMap = out.Field(sinfo.InlineMap) - inlineMap.Set(reflect.New(inlineMap.Type()).Elem()) elemType = inlineMap.Type().Elem() } @@ -870,6 +893,9 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) { d.prepare(n, field) } + mergedFields := d.mergedFields + d.mergedFields = nil + var mergeNode *Node var doneFields []bool if d.uniqueKeys { doneFields = make([]bool, len(sinfo.FieldsList)) @@ -879,13 +905,20 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) { for i := 0; i < l; i += 2 { ni := n.Content[i] if isMerge(ni) { - d.merge(n.Content[i+1], out) + mergeNode = n.Content[i+1] continue } if !d.unmarshal(ni, name) { continue } - if info, ok := sinfo.FieldsMap[name.String()]; ok { + sname := name.String() + if mergedFields != nil { + if mergedFields[sname] { + continue + } + mergedFields[sname] = true + } + if info, ok := sinfo.FieldsMap[sname]; ok { if d.uniqueKeys { if doneFields[info.Id] { d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.Line, name.String(), out.Type())) @@ -911,6 +944,11 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) { d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.Line, name.String(), out.Type())) } } + + d.mergedFields = mergedFields + if mergeNode != nil { + d.merge(n, mergeNode, out) + } return true } @@ -918,19 +956,29 @@ func failWantMap() { failf("map merge requires map or sequence of maps as the value") } -func (d *decoder) merge(n *Node, out reflect.Value) { - switch n.Kind { +func (d *decoder) merge(parent *Node, merge *Node, out reflect.Value) { + mergedFields := d.mergedFields + if mergedFields == nil { + d.mergedFields = make(map[interface{}]bool) + for i := 0; i < len(parent.Content); i += 2 { + k := reflect.New(ifaceType).Elem() + if d.unmarshal(parent.Content[i], k) { + d.mergedFields[k.Interface()] = true + } + } + } + + switch merge.Kind { case MappingNode: - d.unmarshal(n, out) + d.unmarshal(merge, out) case AliasNode: - if n.Alias != nil && n.Alias.Kind != MappingNode { + if merge.Alias != nil && merge.Alias.Kind != MappingNode { failWantMap() } - d.unmarshal(n, out) + d.unmarshal(merge, out) case SequenceNode: - // Step backwards as earlier nodes take precedence. - for i := len(n.Content) - 1; i >= 0; i-- { - ni := n.Content[i] + for i := 0; i < len(merge.Content); i++ { + ni := merge.Content[i] if ni.Kind == AliasNode { if ni.Alias != nil && ni.Alias.Kind != MappingNode { failWantMap() @@ -943,6 +991,8 @@ func (d *decoder) merge(n *Node, out reflect.Value) { default: failWantMap() } + + d.mergedFields = mergedFields } func isMerge(n *Node) bool { diff --git a/vendor/gopkg.in/yaml.v3/parserc.go b/vendor/gopkg.in/yaml.v3/parserc.go index ac66fccc..268558a0 100644 --- a/vendor/gopkg.in/yaml.v3/parserc.go +++ b/vendor/gopkg.in/yaml.v3/parserc.go @@ -687,6 +687,9 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) + if token == nil { + return false + } parser.marks = append(parser.marks, token.start_mark) skip_token(parser) } @@ -786,7 +789,7 @@ func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) { } token := peek_token(parser) - if token.typ != yaml_BLOCK_SEQUENCE_START_TOKEN && token.typ != yaml_BLOCK_MAPPING_START_TOKEN { + if token == nil || token.typ != yaml_BLOCK_SEQUENCE_START_TOKEN && token.typ != yaml_BLOCK_MAPPING_START_TOKEN { return } @@ -813,6 +816,9 @@ func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) { func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) + if token == nil { + return false + } parser.marks = append(parser.marks, token.start_mark) skip_token(parser) } @@ -922,6 +928,9 @@ func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_ev func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) + if token == nil { + return false + } parser.marks = append(parser.marks, token.start_mark) skip_token(parser) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 867ea8ef..48a509ed 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -68,11 +68,11 @@ github.com/hashicorp/go-cleanhttp github.com/hashicorp/go-hclog # github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-multierror -# github.com/hashicorp/go-retryablehttp v0.7.0 +# github.com/hashicorp/go-retryablehttp v0.7.1 github.com/hashicorp/go-retryablehttp -# github.com/hashicorp/go-slug v0.7.0 +# github.com/hashicorp/go-slug v0.8.1 github.com/hashicorp/go-slug -# github.com/hashicorp/go-tfe v0.21.0 +# github.com/hashicorp/go-tfe v1.3.0 ## explicit github.com/hashicorp/go-tfe # github.com/hashicorp/go-version v1.2.1 @@ -212,7 +212,7 @@ github.com/spf13/afero github.com/spf13/afero/mem # github.com/spf13/pflag v1.0.5 github.com/spf13/pflag -# github.com/stretchr/testify v1.7.0 +# github.com/stretchr/testify v1.7.2 ## explicit github.com/stretchr/testify/assert # github.com/zclconf/go-cty v1.9.1 @@ -348,7 +348,7 @@ gopkg.in/inf.v0 gopkg.in/tomb.v1 # gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 -# gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b +# gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 # k8s.io/api v0.21.4 ## explicit diff --git a/workspacehelper/tfc_org.go b/workspacehelper/tfc_org.go index 4d3cb231..79abe742 100644 --- a/workspacehelper/tfc_org.go +++ b/workspacehelper/tfc_org.go @@ -120,7 +120,7 @@ func (t *TerraformCloudClient) listAgentPools() ([]*tfc.AgentPool, error) { ListOptions: tfc.ListOptions{PageSize: AgentPageSize}, } - agentpools, err := t.Client.AgentPools.List(context.TODO(), t.Organization, options) + agentpools, err := t.Client.AgentPools.List(context.TODO(), t.Organization, &options) if err != nil { return nil, fmt.Errorf("Problem fetching agent pools %s", err) } @@ -257,7 +257,7 @@ func (t *TerraformCloudClient) CreateWorkspace(workspace string, instance *appv1 // GetSSHKeyByNameOrID Lookup provided Key ID by name or ID, return ID. func (t *TerraformCloudClient) GetSSHKeyByNameOrID(SSHKeyID string) (string, error) { - sshKeys, err := t.Client.SSHKeys.List(context.TODO(), t.Organization, tfc.SSHKeyListOptions{}) + sshKeys, err := t.Client.SSHKeys.List(context.TODO(), t.Organization, &tfc.SSHKeyListOptions{}) if err != nil { return "", err } @@ -346,7 +346,7 @@ func (t *TerraformCloudClient) CheckNotifications(instance *appv1alpha1.Workspac workspaceID := instance.Status.WorkspaceID notifications, err := t.Client.NotificationConfigurations.List(context.TODO(), workspaceID, - tfc.NotificationConfigurationListOptions{}) + &tfc.NotificationConfigurationListOptions{}) if err != nil { return err } @@ -381,7 +381,7 @@ func (t *TerraformCloudClient) CheckNotifications(instance *appv1alpha1.Workspac // refresh notifications list notifications, err = t.Client.NotificationConfigurations.List(context.TODO(), workspaceID, - tfc.NotificationConfigurationListOptions{}) + &tfc.NotificationConfigurationListOptions{}) if err != nil { return err } @@ -402,6 +402,13 @@ func (t *TerraformCloudClient) CheckNotifications(instance *appv1alpha1.Workspac // Add missing notifications for _, notification := range toAdd { + triggers := []tfc.NotificationTriggerType{} + + // Convert []string to []tfc.NotificationTriggerType + for _, trigger := range notification.Triggers { + triggers = append(triggers, tfc.NotificationTriggerType(trigger)) + } + createOpts := tfc.NotificationConfigurationCreateOptions{ Name: ¬ification.Name, DestinationType: ¬ification.Type, @@ -409,7 +416,7 @@ func (t *TerraformCloudClient) CheckNotifications(instance *appv1alpha1.Workspac Token: ¬ification.Token, URL: ¬ification.URL, EmailAddresses: notification.Recipients, - Triggers: notification.Triggers, + Triggers: triggers, } for _, user := range notification.Users { createOpts.EmailUsers = append(createOpts.EmailUsers, &tfc.User{ID: user}) diff --git a/workspacehelper/tfc_output.go b/workspacehelper/tfc_output.go index f6418e73..acda5171 100644 --- a/workspacehelper/tfc_output.go +++ b/workspacehelper/tfc_output.go @@ -15,7 +15,7 @@ import ( // GetStateVersionDownloadURL retrieves download URL for state file func (t *TerraformCloudClient) GetStateVersionDownloadURL(workspaceID string) (string, error) { - stateVersion, err := t.Client.StateVersions.Current(context.TODO(), workspaceID) + stateVersion, err := t.Client.StateVersions.ReadCurrent(context.TODO(), workspaceID) if err != nil { return "", fmt.Errorf("could not get current state version, WorkspaceID, %s, Error, %v", workspaceID, err) } diff --git a/workspacehelper/tfc_run.go b/workspacehelper/tfc_run.go index 2c5b5afe..dd9c4ad8 100644 --- a/workspacehelper/tfc_run.go +++ b/workspacehelper/tfc_run.go @@ -78,7 +78,7 @@ func isError(status string) bool { // DeleteRuns cancels runs that haven't been applied or planned func (t *TerraformCloudClient) DeleteRuns(workspaceID string) error { message := "operator, finalizer, cancelling run" - runs, err := t.Client.Runs.List(context.TODO(), workspaceID, tfc.RunListOptions{}) + runs, err := t.Client.Runs.List(context.TODO(), workspaceID, &tfc.RunListOptions{}) if err != nil { return err } diff --git a/workspacehelper/tfc_run_trigger.go b/workspacehelper/tfc_run_trigger.go index 27e98eae..8575cb5a 100644 --- a/workspacehelper/tfc_run_trigger.go +++ b/workspacehelper/tfc_run_trigger.go @@ -74,9 +74,9 @@ func (t *TerraformCloudClient) CheckRunTriggers(workspace string, specRunTrigger func (t *TerraformCloudClient) listRunTriggers(workspaceID string) ([]*tfc.RunTrigger, error) { options := tfc.RunTriggerListOptions{ - RunTriggerType: tfc.String("inbound"), + RunTriggerType: tfc.RunTriggerFilterOp("inbound"), } - runTriggers, err := t.Client.RunTriggers.List(context.TODO(), workspaceID, options) + runTriggers, err := t.Client.RunTriggers.List(context.TODO(), workspaceID, &options) if err != nil { return nil, err } diff --git a/workspacehelper/tfc_variable.go b/workspacehelper/tfc_variable.go index db012e2f..dd33516a 100644 --- a/workspacehelper/tfc_variable.go +++ b/workspacehelper/tfc_variable.go @@ -82,6 +82,9 @@ func checkIfVariableChanged(specVariable *tfc.Variable, workspaceVariable *tfc.V if specVariable.HCL != workspaceVariable.HCL { return true } + if specVariable.Category != workspaceVariable.Category { + return true + } if !specVariable.Sensitive && workspaceVariable.Sensitive { return true } @@ -186,7 +189,7 @@ func (t *TerraformCloudClient) listVariables(workspaceID string) ([]*tfc.Variabl options := tfc.VariableListOptions{ ListOptions: tfc.ListOptions{PageSize: PageSize}, } - variables, err := t.Client.Variables.List(context.TODO(), workspaceID, options) + variables, err := t.Client.Variables.List(context.TODO(), workspaceID, &options) if err != nil { return nil, err } @@ -212,6 +215,7 @@ func (t *TerraformCloudClient) UpdateTerraformVariables(variables []*tfc.Variabl Key: &v.Key, Value: &v.Value, HCL: &v.HCL, + Category: &v.Category, Sensitive: &v.Sensitive, } _, err := t.Client.Variables.Update(context.TODO(), v.Workspace.ID, v.ID, options) diff --git a/workspacehelper/workspace_helper.go b/workspacehelper/workspace_helper.go index 8e0ad998..489d24dd 100644 --- a/workspacehelper/workspace_helper.go +++ b/workspacehelper/workspace_helper.go @@ -392,7 +392,7 @@ func (r *WorkspaceHelper) prepareVCSRun(instance *appv1alpha1.Workspace) (bool, instance.Spec.Organization, "Name", instance.Name, "Namespace", instance.Namespace) configVersions, err := r.tfclient.Client.ConfigurationVersions.List(context.TODO(), - instance.Status.WorkspaceID, tfe.ConfigurationVersionListOptions{}) + instance.Status.WorkspaceID, &tfe.ConfigurationVersionListOptions{}) if err != nil { return false, err }