Skip to content

Commit

Permalink
Manage tfv test source code in magic-modules (GoogleCloudPlatform#5599)
Browse files Browse the repository at this point in the history
  • Loading branch information
ScottSuarez authored and lcaggio committed Mar 18, 2022
1 parent 2f1eecf commit 08a458f
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 16 deletions.
4 changes: 1 addition & 3 deletions mmv1/provider/terraform_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,7 @@ def copy_common_files(output_folder, generate_code, _generate_docs)
['converters/google/resources/spanner_instance_iam.go',
'third_party/validator/spanner_instance_iam.go'],
['converters/google/resources/storage_bucket_iam.go',
'third_party/validator/storage_bucket_iam.go'],
['converters/google/resources/organization_policy.go',
'third_party/validator/organization_policy.go']
'third_party/validator/storage_bucket_iam.go']
])
end

Expand Down
35 changes: 30 additions & 5 deletions mmv1/third_party/validator/tests/source/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ import (
"github.com/GoogleCloudPlatform/terraform-validator/converters/google"
)

const (
samplePolicyPath = "../testdata/sample_policies"
defaultAncestry = "organization/12345/folder/67890"
defaultOrganization = "12345"
defaultFolder = "67890"
defaultProject = "foobar"
defaultProviderVersion = "4.4.0"
)

var (
data *testData
tfvBinary string
Expand Down Expand Up @@ -46,15 +55,31 @@ func init() {
log.Fatalf("cannot get current directory: %v", err)
}
tfvBinary = filepath.Join(cwd, "..", "bin", "terraform-validator")
credentials := getTestCredsFromEnv()
project := getTestProjectFromEnv()
org := getTestOrgFromEnv(nil)
billingAccount := getTestBillingAccountFromEnv(nil)
project, ok := os.LookupEnv("TEST_PROJECT")
if !ok {
log.Printf("Missing required env var TEST_PROJECT. Default (%s) will be used.", defaultProject)
project = defaultProject
}
org, ok := os.LookupEnv("TEST_ORG_ID")
if !ok {
log.Printf("Missing required env var TEST_ORG_ID. Default (%s) will be used.", defaultOrganization)
org = defaultOrganization
}
folder, ok := os.LookupEnv("TEST_FOLDER_ID")
if !ok {
log.Printf("Missing required env var TEST_FOLDER_ID. Default (%s) will be used.", defaultFolder)
folder = defaultFolder
}
credentials, ok := os.LookupEnv("TEST_CREDENTIALS")
if ok {
// Make credentials path relative to repo root rather than
// test/ dir if it is a relative path.
if !filepath.IsAbs(credentials) {
credentials = filepath.Join(cwd, "..", credentials)
}
} else {
log.Printf("missing env var TEST_CREDENTIALS, will try to use Application Default Credentials")
}
ancestry, ok := os.LookupEnv("TEST_ANCESTRY")
if !ok {
log.Printf("Missing required env var TEST_ANCESTRY. Default (%s) will be used.", defaultAncestry)
Expand All @@ -76,7 +101,7 @@ func init() {
Project: map[string]string{
"Name": "My Project Name",
"ProjectId": "my-project-id",
"BillingAccountName": billingAccount,
"BillingAccountName": "012345-567890-ABCDEF",
"Number": "1234567890",
},
OrgID: org,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@
"schema_version": 0,
"values": {
"database_version": "POSTGRES_9_6",
"name": "main-instance",
"name": "master-instance",
"region": "us-central1",
"settings": [
{
Expand Down Expand Up @@ -1092,7 +1092,7 @@
],
"before": {
"database_version": "POSTGRES_9_6",
"name": "main-instance",
"name": "master-instance",
"region": "us-central1",
"settings": [
{
Expand All @@ -1112,7 +1112,7 @@
},
"after": {
"database_version": "POSTGRES_9_6",
"name": "main-instance",
"name": "master-instance",
"region": "us-central1",
"settings": [
{
Expand Down Expand Up @@ -1669,7 +1669,7 @@
"constant_value": "POSTGRES_9_6"
},
"name": {
"constant_value": "main-instance"
"constant_value": "master-instance"
},
"region": {
"constant_value": "us-central1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@
"schema_version": 0,
"values": {
"database_version": "POSTGRES_9_6",
"name": "main-instance",
"name": "master-instance",
"region": "us-central1",
"settings": [
{
Expand Down Expand Up @@ -1092,7 +1092,7 @@
],
"before": {
"database_version": "POSTGRES_9_6",
"name": "main-instance",
"name": "master-instance",
"region": "us-central1",
"settings": [
{
Expand All @@ -1112,7 +1112,7 @@
},
"after": {
"database_version": "POSTGRES_9_6",
"name": "main-instance",
"name": "master-instance",
"region": "us-central1",
"settings": [
{
Expand Down Expand Up @@ -1669,7 +1669,7 @@
"constant_value": "POSTGRES_9_6"
},
"name": {
"constant_value": "main-instance"
"constant_value": "master-instance"
},
"region": {
"constant_value": "us-central1"
Expand Down
234 changes: 234 additions & 0 deletions mmv1/third_party/validator/tests/source/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package test

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"strings"
"testing"

"github.com/GoogleCloudPlatform/terraform-validator/converters/google"
"github.com/stretchr/testify/require"
)

func defaultCompareConverterOutput(t *testing.T, expected []google.Asset, actual []google.Asset, offline bool) {
expectedAssets := normalizeAssets(t, expected, offline)
actualAssets := normalizeAssets(t, actual, offline)
require.ElementsMatch(t, expectedAssets, actualAssets)
}

func testConvertCommand(t *testing.T, dir, name string, offline bool, compare compareConvertOutputFunc) {

if compare == nil {
compare = defaultCompareConverterOutput
}

// Load expected assets
var expectedRaw []byte
testfile := filepath.Join(dir, name+".json")
expectedRaw, err := ioutil.ReadFile(testfile)
if err != nil {
t.Fatalf("Error reading %v: %v", testfile, err)
}
var expected []google.Asset
if err := json.Unmarshal(expectedRaw, &expected); err != nil {
t.Fatalf("unmarshaling: %v", err)
}

// Get converted assets
var actualRaw []byte
fileNameToConvert := name + ".tfplan.json"
actualRaw = tfvConvert(t, dir, fileNameToConvert, offline)
var actual []google.Asset
err = json.Unmarshal(actualRaw, &actual)
if err != nil {
t.Fatalf("unmarshaling: %v", err)
}

compare(t, expected, actual, offline)
}

func testValidateCommandGeneric(t *testing.T, dir, name string, offline bool) {

wantViolation := true
wantContents := "Constraint GCPAlwaysViolatesConstraintV1.always_violates_all on resource"
constraintName := "always_violate"

testValidateCommand(t, wantViolation, wantContents, dir, name, offline, constraintName)
}

func testValidateCommand(t *testing.T, wantViolation bool, wantContents, dir, name string, offline bool, constraintName string) {
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("cannot get current directory: %v", err)
}
policyPath := filepath.Join(cwd, samplePolicyPath, constraintName)
var got []byte
got = tfvValidate(t, wantViolation, dir, name+".tfplan.json", policyPath, offline)
wantRe := regexp.MustCompile(wantContents)
if wantContents != "" && !wantRe.Match(got) {
t.Fatalf("binary did not return expect output, \ngot=%s \nwant (regex)=%s", string(got), wantContents)
}
}

func terraformWorkflow(t *testing.T, dir, name string) {
terraformInit(t, "terraform", dir)
terraformPlan(t, "terraform", dir, name+".tfplan")
payload := terraformShow(t, "terraform", dir, name+".tfplan")
saveFile(t, dir, name+".tfplan.json", payload)
}

func terraformInit(t *testing.T, executable, dir string) {
terraformExec(t, executable, dir, "init", "-input=false")
}

func terraformPlan(t *testing.T, executable, dir, tfplan string) {
terraformExec(t, executable, dir, "plan", "-input=false", "-refresh=false", "-out", tfplan)
}

func terraformShow(t *testing.T, executable, dir, tfplan string) []byte {
return terraformExec(t, executable, dir, "show", "--json", tfplan)
}

func terraformExec(t *testing.T, executable, dir string, args ...string) []byte {
cmd := exec.Command(executable, args...)
cmd.Env = []string{"HOME=" + filepath.Join(dir, "fakehome")}
cmd.Dir = dir
wantError := false
payload, _ := run(t, cmd, wantError)
return payload
}

func saveFile(t *testing.T, dir, filename string, payload []byte) {
fullpath := filepath.Join(dir, filename)
f, err := os.Create(fullpath)
if err != nil {
t.Fatalf("error while creating file %s, error %v", fullpath, err)
}
_, err = f.Write(payload)
if err != nil {
t.Fatalf("error while writing to file %s, error %v", fullpath, err)
}
}

func tfvConvert(t *testing.T, dir, tfPlanFile string, offline bool) []byte {
executable := tfvBinary
wantError := false
args := []string{"convert", "--project", data.Provider["project"]}
if offline {
args = append(args, "--offline", "--ancestry", data.Ancestry)
}
args = append(args, tfPlanFile)
cmd := exec.Command(executable, args...)
// Remove environment variables inherited from the test runtime.
cmd.Env = []string{}
// Add credentials back.
if data.Provider["credentials"] != "" {
cmd.Env = append(cmd.Env, "GOOGLE_APPLICATION_CREDENTIALS="+data.Provider["credentials"])
}
cmd.Dir = dir
payload, _ := run(t, cmd, wantError)
return payload
}

func tfvValidate(t *testing.T, wantError bool, dir, tfplan, policyPath string, offline bool) []byte {
executable := tfvBinary
args := []string{"validate", "--project", data.Provider["project"], "--policy-path", policyPath}
if offline {
args = append(args, "--offline", "--ancestry", data.Ancestry)
}
args = append(args, tfplan)
cmd := exec.Command(executable, args...)
cmd.Env = []string{"GOOGLE_APPLICATION_CREDENTIALS=" + data.Provider["credentials"]}
cmd.Dir = dir
payload, _ := run(t, cmd, wantError)
return payload
}

// run a command and call t.Fatal on non-zero exit.
func run(t *testing.T, cmd *exec.Cmd, wantError bool) ([]byte, []byte) {
var stderr, stdout bytes.Buffer
cmd.Stderr, cmd.Stdout = &stderr, &stdout
err := cmd.Run()
if gotError := (err != nil); gotError != wantError {
t.Fatalf("running %s: \nerror=%v \nstderr=%s \nstdout=%s", cmdToString(cmd), err, stderr.String(), stdout.String())
}
// Print env, stdout and stderr if verbose flag is used.
if len(cmd.Env) != 0 {
t.Logf("=== Environment Variable of %s ===", cmdToString(cmd))
t.Log(strings.Join(cmd.Env, "\n"))
}
if stdout.String() != "" {
t.Logf("=== STDOUT of %s ===", cmdToString(cmd))
t.Log(stdout.String())
}
if stderr.String() != "" {
t.Logf("=== STDERR of %s ===", cmdToString(cmd))
t.Log(stderr.String())
}
return stdout.Bytes(), stderr.Bytes()
}

// cmdToString clones the logic of https://golang.org/pkg/os/exec/#Cmd.String.
func cmdToString(c *exec.Cmd) string {
// report the exact executable path (plus args)
b := new(strings.Builder)
b.WriteString(c.Path)
for _, a := range c.Args[1:] {
b.WriteByte(' ')
b.WriteString(a)
}
return b.String()
}

func generateTFVconvertedAsset(t *testing.T, testDir, testSlug string) {
// Get converted assets
var conversionRaw []byte
fileNameToConvert := testSlug + ".tfplan.json"
conversionRaw = tfvConvert(t, testDir, fileNameToConvert, true)
dstDir := "../testdata/generatedconvert"
if _, err := os.Stat(dstDir); os.IsNotExist(err) {
os.MkdirAll(dstDir, 0700)
}

conversionPretty := &bytes.Buffer{}
if err := json.Indent(conversionPretty, conversionRaw, "", " "); err != nil {
panic(err)
}

dstFile := path.Join(dstDir, testSlug+".json")
err := os.WriteFile(dstFile, conversionPretty.Bytes(), 0666)
if err != nil {
t.Fatalf("error while writing to file %s, error %v", dstFile, err)
}

fmt.Println("created file : " + dstFile)
}

func getTestPrefix() string {
credentials, ok := data.Provider["credentials"]
if ok {
credentials = "credentials = \"" + credentials + "\""
}

return fmt.Sprintf(`terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> %s"
}
}
}
provider "google" {
%s
}
`, data.Provider["version"], credentials)
}

0 comments on commit 08a458f

Please sign in to comment.