terraform-provider-google/google/resource_google_project_tes...

379 lines
11 KiB
Go

package google
import (
"fmt"
"os"
"reflect"
"strconv"
"strings"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"google.golang.org/api/cloudresourcemanager/v1"
)
var (
pname = "Terraform Acceptance Tests"
originalPolicy *cloudresourcemanager.Policy
)
// Test that a Project resource can be created without an organization
func TestAccProject_createWithoutOrg(t *testing.T) {
t.Parallel()
creds := multiEnvSearch(credsEnvVars)
if strings.Contains(creds, "iam.gserviceaccount.com") {
t.Skip("Service accounts cannot create projects without a parent. Requires user credentials.")
}
pid := "terraform-" + acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
// This step creates a new project
{
Config: testAccProject_createWithoutOrg(pid, pname),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectExists("google_project.acceptance", pid),
),
},
},
})
}
// Test that a Project resource can be created and an IAM policy
// associated
func TestAccProject_create(t *testing.T) {
t.Parallel()
org := getTestOrgFromEnv(t)
pid := "terraform-" + acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
// This step creates a new project
{
Config: testAccProject_create(pid, pname, org),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectExists("google_project.acceptance", pid),
),
},
},
})
}
// Test that a Project resource can be created with an associated
// billing account
func TestAccProject_billing(t *testing.T) {
t.Parallel()
org := getTestOrgFromEnv(t)
skipIfEnvNotSet(t, "GOOGLE_BILLING_ACCOUNT_2")
billingId2 := os.Getenv("GOOGLE_BILLING_ACCOUNT_2")
billingId := getTestBillingAccountFromEnv(t)
pid := "terraform-" + acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
// This step creates a new project with a billing account
{
Config: testAccProject_createBilling(pid, pname, org, billingId),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectHasBillingAccount("google_project.acceptance", pid, billingId),
),
},
// Make sure import supports billing account
{
ResourceName: "google_project.acceptance",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"skip_delete"},
},
// Update to a different billing account
{
Config: testAccProject_createBilling(pid, pname, org, billingId2),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectHasBillingAccount("google_project.acceptance", pid, billingId2),
),
},
// Unlink the billing account
{
Config: testAccProject_create(pid, pname, org),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectHasBillingAccount("google_project.acceptance", pid, ""),
),
},
},
})
}
// Test that a Project resource can be created with labels
func TestAccProject_labels(t *testing.T) {
t.Parallel()
org := getTestOrgFromEnv(t)
pid := "terraform-" + acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccProject_labels(pid, pname, org, map[string]string{"test": "that"}),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectHasLabels("google_project.acceptance", pid, map[string]string{"test": "that"}),
),
},
// Make sure import supports labels
{
ResourceName: "google_project.acceptance",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"skip_delete"},
},
// update project with labels
{
Config: testAccProject_labels(pid, pname, org, map[string]string{"label": "label-value"}),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectExists("google_project.acceptance", pid),
testAccCheckGoogleProjectHasLabels("google_project.acceptance", pid, map[string]string{"label": "label-value"}),
),
},
// update project delete labels
{
Config: testAccProject_create(pid, pname, org),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectExists("google_project.acceptance", pid),
testAccCheckGoogleProjectHasNoLabels("google_project.acceptance", pid),
),
},
},
})
}
func TestAccProject_deleteDefaultNetwork(t *testing.T) {
t.Parallel()
org := getTestOrgFromEnv(t)
pid := "terraform-" + acctest.RandString(10)
billingId := getTestBillingAccountFromEnv(t)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccProject_deleteDefaultNetwork(pid, pname, org, billingId),
},
},
})
}
func TestAccProject_parentFolder(t *testing.T) {
t.Parallel()
org := getTestOrgFromEnv(t)
pid := "terraform-" + acctest.RandString(10)
folderDisplayName := "tf-test-" + acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccProject_parentFolder(pid, pname, folderDisplayName, org),
},
},
})
}
func testAccCheckGoogleProjectExists(r, pid string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[r]
if !ok {
return fmt.Errorf("Not found: %s", r)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
if rs.Primary.ID != pid {
return fmt.Errorf("Expected project %q to match ID %q in state", pid, rs.Primary.ID)
}
return nil
}
}
func testAccCheckGoogleProjectHasBillingAccount(r, pid, billingId string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[r]
if !ok {
return fmt.Errorf("Not found: %s", r)
}
// State should match expected
if rs.Primary.Attributes["billing_account"] != billingId {
return fmt.Errorf("Billing ID in state (%s) does not match expected value (%s)", rs.Primary.Attributes["billing_account"], billingId)
}
// Actual value in API should match state and expected
// Read the billing account
config := testAccProvider.Meta().(*Config)
ba, err := config.clientBilling.Projects.GetBillingInfo(prefixedProject(pid)).Do()
if err != nil {
return fmt.Errorf("Error reading billing account for project %q: %v", prefixedProject(pid), err)
}
if billingId != strings.TrimPrefix(ba.BillingAccountName, "billingAccounts/") {
return fmt.Errorf("Billing ID returned by API (%s) did not match expected value (%s)", ba.BillingAccountName, billingId)
}
return nil
}
}
func testAccCheckGoogleProjectHasLabels(r, pid string, expected map[string]string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[r]
if !ok {
return fmt.Errorf("Not found: %s", r)
}
// State should have the same number of labels
if rs.Primary.Attributes["labels.%"] != strconv.Itoa(len(expected)) {
return fmt.Errorf("Expected %d labels, got %s", len(expected), rs.Primary.Attributes["labels.%"])
}
// Actual value in API should match state and expected
config := testAccProvider.Meta().(*Config)
found, err := config.clientResourceManager.Projects.Get(pid).Do()
if err != nil {
return err
}
actual := found.Labels
if !reflect.DeepEqual(actual, expected) {
// Determine only the different attributes
for k, v := range expected {
if av, ok := actual[k]; ok && v == av {
delete(expected, k)
delete(actual, k)
}
}
spewConf := spew.NewDefaultConfig()
spewConf.SortKeys = true
return fmt.Errorf(
"Labels not equivalent. Difference is shown below. Top is actual, bottom is expected."+
"\n\n%s\n\n%s",
spewConf.Sdump(actual), spewConf.Sdump(expected),
)
}
return nil
}
}
func testAccCheckGoogleProjectHasNoLabels(r, pid string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[r]
if !ok {
return fmt.Errorf("Not found: %s", r)
}
// State should have zero labels
if v, ok := rs.Primary.Attributes["labels.%"]; ok && v != "0" {
return fmt.Errorf("Expected 0 labels, got %s", rs.Primary.Attributes["labels.%"])
}
// Actual value in API should match state and expected
config := testAccProvider.Meta().(*Config)
found, err := config.clientResourceManager.Projects.Get(pid).Do()
if err != nil {
return err
}
spewConf := spew.NewDefaultConfig()
spewConf.SortKeys = true
if found.Labels != nil {
return fmt.Errorf("Labels should be empty. Actual \n%s", spewConf.Sdump(found.Labels))
}
return nil
}
}
func testAccProject_createWithoutOrg(pid, name string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
}`, pid, name)
}
func testAccProject_createBilling(pid, name, org, billing string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
billing_account = "%s"
}`, pid, name, org, billing)
}
func testAccProject_labels(pid, name, org string, labels map[string]string) string {
r := fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
labels = {`, pid, name, org)
l := ""
for key, value := range labels {
l += fmt.Sprintf("%q = %q\n", key, value)
}
l += fmt.Sprintf("}\n}")
return r + l
}
func testAccProject_deleteDefaultNetwork(pid, name, org, billing string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
billing_account = "%s" # requires billing to enable compute API
auto_create_network = false
}`, pid, name, org, billing)
}
func testAccProject_parentFolder(pid, projectName, folderName, org string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
# ensures we can set both org_id and folder_id as long as only one is not empty.
org_id = ""
folder_id = "${google_folder.folder1.id}"
}
resource "google_folder" "folder1" {
display_name = "%s"
parent = "organizations/%s"
}
`, pid, projectName, folderName, org)
}
func skipIfEnvNotSet(t *testing.T, envs ...string) {
for _, k := range envs {
if os.Getenv(k) == "" {
t.Skipf("Environment variable %s is not set", k)
}
}
}