fix(cloud_reader): update tests + tfcloud token flag

main
Rémi Doreau 2021-04-27 14:03:29 +02:00 committed by sundowndev
parent a03127100a
commit 499c1215c3
4 changed files with 70 additions and 48 deletions

View File

@ -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,

View File

@ -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 {

View File

@ -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)

View File

@ -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())