fix(cloud_reader): update tests + tfcloud token flag
parent
a03127100a
commit
499c1215c3
|
@ -130,6 +130,13 @@ func NewScanCmd() *cobra.Command {
|
|||
"Use those HTTP headers to query the provided URL.\n"+
|
||||
"Only used with tfstate+http(s) backend for now.\n",
|
||||
)
|
||||
fl.StringVarP(&opts.BackendOptions.TerraformCloudToken,
|
||||
"terraform-cloud-token",
|
||||
"",
|
||||
"",
|
||||
"Terraform Cloud / Enterprise API token.\n"+
|
||||
"Only used with tfstate+tfcloud backend.\n",
|
||||
)
|
||||
fl.BoolVar(&opts.StrictMode,
|
||||
"strict",
|
||||
false,
|
||||
|
|
|
@ -20,7 +20,8 @@ var supportedBackends = []string{
|
|||
type Backend io.ReadCloser
|
||||
|
||||
type Options struct {
|
||||
Headers map[string]string
|
||||
Headers map[string]string
|
||||
TerraformCloudToken string
|
||||
}
|
||||
|
||||
func IsSupported(backend string) bool {
|
||||
|
|
|
@ -7,28 +7,29 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const BackendKeyCloud = "tfcloud"
|
||||
const TerraformCloudAPI = "https://app.terraform.io/api/v2"
|
||||
|
||||
type Attributes struct {
|
||||
type TFCloudAttributes struct {
|
||||
HostedStateDownloadUrl string `json:"hosted-state-download-url"`
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
Attributes Attributes `json:"attributes"`
|
||||
type TFCloudData struct {
|
||||
Attributes TFCloudAttributes `json:"attributes"`
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Data Data `json:"data"`
|
||||
type TFCloudBody struct {
|
||||
Data TFCloudData `json:"data"`
|
||||
}
|
||||
|
||||
func NewCloudReader(workspaceId string, opts *Options) (*HTTPBackend, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/workspaces/%s/current-state-version", TerraformCloudAPI, workspaceId), nil)
|
||||
|
||||
req.Header.Add("Content-Type", "application/vnd.api+json")
|
||||
req.Header.Add("Authorization", opts.Headers["Authorization"])
|
||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", opts.TerraformCloudToken))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -37,12 +38,8 @@ func NewCloudReader(workspaceId string, opts *Options) (*HTTPBackend, error) {
|
|||
client := &http.Client{}
|
||||
res, err := client.Do(req)
|
||||
|
||||
if res.StatusCode == 404 {
|
||||
return nil, errors.Errorf("Error reading state from Terraform Cloud/Enterprise workspace: wrong workspace id")
|
||||
}
|
||||
|
||||
if res.StatusCode == 401 {
|
||||
return nil, errors.Errorf("Error reading state from Terraform Cloud/Enterprise workspace: bad authentication token")
|
||||
if res.StatusCode < 200 || res.StatusCode >= 400 {
|
||||
return nil, errors.Errorf("error requesting Cloud backend state: status code: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -51,14 +48,15 @@ func NewCloudReader(workspaceId string, opts *Options) (*HTTPBackend, error) {
|
|||
|
||||
bodyBytes, _ := ioutil.ReadAll(res.Body)
|
||||
|
||||
body := Body{}
|
||||
body := TFCloudBody{}
|
||||
err = json.Unmarshal(bodyBytes, &body)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("error:", err)
|
||||
panic(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawURL := body.Data.Attributes.HostedStateDownloadUrl
|
||||
logrus.WithFields(logrus.Fields{"hosted-state-download-url": rawURL}).Trace("Cloud backend response")
|
||||
|
||||
opt := Options{}
|
||||
return NewHTTPReader(rawURL, &opt)
|
||||
|
|
|
@ -17,66 +17,82 @@ func TestNewCloudReader(t *testing.T) {
|
|||
options *Options
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
url string
|
||||
wantURL string
|
||||
wantErr error
|
||||
responder httpmock.Responder
|
||||
name string
|
||||
args args
|
||||
url string
|
||||
wantURL string
|
||||
wantErr error
|
||||
mock func()
|
||||
}{
|
||||
{
|
||||
name: "Should fetch URL with auth header",
|
||||
args: args{
|
||||
workspaceId: "workspaceId",
|
||||
options: &Options{
|
||||
Headers: map[string]string{
|
||||
"Authorization": "Bearer TOKEN",
|
||||
},
|
||||
TerraformCloudToken: "TOKEN",
|
||||
},
|
||||
},
|
||||
url: "https://app.terraform.io/api/v2/workspaces/workspaceId/current-state-version",
|
||||
wantURL: "https://archivist.terraform.io/v1/object/test",
|
||||
wantErr: nil,
|
||||
responder: httpmock.NewBytesResponder(http.StatusOK, []byte(`{"data":{"attributes":{"hosted-state-download-url":"https://archivist.terraform.io/v1/object/test"}}}`)),
|
||||
url: "https://app.terraform.io/api/v2/workspaces/workspaceId/current-state-version",
|
||||
wantURL: "https://archivist.terraform.io/v1/object/test",
|
||||
wantErr: nil,
|
||||
mock: func() {
|
||||
httpmock.Reset()
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
"https://app.terraform.io/api/v2/workspaces/workspaceId/current-state-version",
|
||||
httpmock.NewBytesResponder(http.StatusOK, []byte(`{"data":{"attributes":{"hosted-state-download-url":"https://archivist.terraform.io/v1/object/test"}}}`)),
|
||||
)
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
"https://archivist.terraform.io/v1/object/test",
|
||||
httpmock.NewBytesResponder(http.StatusOK, []byte(`{}`)),
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Should fail with wrong workspaceId",
|
||||
args: args{
|
||||
workspaceId: "wrong_workspaceId",
|
||||
options: &Options{
|
||||
Headers: map[string]string{
|
||||
"Authorization": "Bearer TOKEN",
|
||||
},
|
||||
TerraformCloudToken: "TOKEN",
|
||||
},
|
||||
},
|
||||
url: "https://app.terraform.io/api/v2/workspaces/wrong_workspaceId/current-state-version",
|
||||
wantURL: "",
|
||||
wantErr: errors.New("Error reading state from Terraform Cloud/Enterprise workspace: wrong workspace id"),
|
||||
responder: httpmock.NewBytesResponder(http.StatusNotFound, []byte{}),
|
||||
url: "https://app.terraform.io/api/v2/workspaces/wrong_workspaceId/current-state-version",
|
||||
wantURL: "",
|
||||
mock: func() {
|
||||
httpmock.Reset()
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
"https://app.terraform.io/api/v2/workspaces/wrong_workspaceId/current-state-version",
|
||||
httpmock.NewBytesResponder(http.StatusNotFound, []byte{}),
|
||||
)
|
||||
},
|
||||
wantErr: errors.New("error requesting Cloud backend state: status code: 404"),
|
||||
},
|
||||
{
|
||||
name: "Should fail with bad authentication token",
|
||||
args: args{
|
||||
workspaceId: "workspaceId",
|
||||
options: &Options{
|
||||
Headers: map[string]string{
|
||||
"Authorization": "Bearer WRONG_TOKEN",
|
||||
},
|
||||
TerraformCloudToken: "TOKEN",
|
||||
},
|
||||
},
|
||||
url: "https://app.terraform.io/api/v2/workspaces/workspaceId/current-state-version",
|
||||
wantURL: "",
|
||||
wantErr: errors.New("Error reading state from Terraform Cloud/Enterprise workspace: bad authentication token"),
|
||||
responder: httpmock.NewBytesResponder(http.StatusUnauthorized, []byte{}),
|
||||
url: "https://app.terraform.io/api/v2/workspaces/workspaceId/current-state-version",
|
||||
wantURL: "",
|
||||
mock: func() {
|
||||
httpmock.Reset()
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
"https://app.terraform.io/api/v2/workspaces/workspaceId/current-state-version",
|
||||
httpmock.NewBytesResponder(http.StatusUnauthorized, []byte{}),
|
||||
)
|
||||
},
|
||||
wantErr: errors.New("error requesting Cloud backend state: status code: 401"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
httpmock.Reset()
|
||||
httpmock.RegisterResponder("GET", tt.url, tt.responder)
|
||||
if tt.name == "Should fetch URL with auth header" {
|
||||
httpmock.RegisterResponder("GET", "https://archivist.terraform.io/v1/object/test", httpmock.NewBytesResponder(http.StatusOK, []byte(`{}`)))
|
||||
}
|
||||
tt.mock()
|
||||
got, err := NewCloudReader(tt.args.workspaceId, tt.args.options)
|
||||
if tt.wantErr != nil {
|
||||
assert.EqualError(t, err, tt.wantErr.Error())
|
||||
|
|
Loading…
Reference in New Issue