blob: 2c33e7bae68fe659a08f17a7da94265fefab25d2 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package provider_test
import (
"fmt"
"regexp"
"strings"
"testing"
"github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest"
"github.com/hashicorp/terraform-provider-google-beta/google-beta/envvar"
"github.com/hashicorp/terraform-provider-google-beta/google-beta/provider"
"github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource"
transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
func TestProvider(t *testing.T) {
if err := provider.Provider().InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestProvider_impl(t *testing.T) {
var _ *schema.Provider = provider.Provider()
}
func TestProvider_noDuplicatesInResourceMap(t *testing.T) {
_, err := provider.ResourceMapWithErrors()
if err != nil {
t.Error(err)
}
}
func TestProvider_noDuplicatesInDatasourceMap(t *testing.T) {
_, err := provider.DatasourceMapWithErrors()
if err != nil {
t.Error(err)
}
}
func TestAccProviderBasePath_setBasePath(t *testing.T) {
t.Parallel()
acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccCheckComputeAddressDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccProviderBasePath_setBasePath("https://www.googleapis.com/compute/beta/", acctest.RandString(t, 10)),
},
{
ResourceName: "google_compute_address.default",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func TestAccProviderBasePath_setInvalidBasePath(t *testing.T) {
t.Parallel()
acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccCheckComputeAddressDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccProviderBasePath_setBasePath("https://www.example.com/compute/beta/", acctest.RandString(t, 10)),
ExpectError: regexp.MustCompile("got HTTP response code 404 with body"),
},
},
})
}
func TestAccProviderMeta_setModuleName(t *testing.T) {
t.Parallel()
moduleName := "my-module"
acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccCheckComputeAddressDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccProviderMeta_setModuleName(moduleName, acctest.RandString(t, 10)),
},
{
ResourceName: "google_compute_address.default",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func TestAccProviderUserProjectOverride(t *testing.T) {
// Parallel fine-grained resource creation
acctest.SkipIfVcr(t)
t.Parallel()
org := envvar.GetTestOrgFromEnv(t)
billing := envvar.GetTestBillingAccountFromEnv(t)
pid := "tf-test-" + acctest.RandString(t, 10)
topicName := "tf-test-topic-" + acctest.RandString(t, 10)
config := acctest.BootstrapConfig(t)
accessToken, err := acctest.SetupProjectsAndGetAccessToken(org, billing, pid, "pubsub", config)
if err != nil {
t.Error(err)
}
acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
// No TestDestroy since that's not really the point of this test
Steps: []resource.TestStep{
{
Config: testAccProviderUserProjectOverride_step2(accessToken, pid, false, topicName),
ExpectError: regexp.MustCompile("Cloud Pub/Sub API has not been used"),
},
{
Config: testAccProviderUserProjectOverride_step2(accessToken, pid, true, topicName),
},
{
ResourceName: "google_pubsub_topic.project-2-topic",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"labels", "terraform_labels"},
},
{
Config: testAccProviderUserProjectOverride_step3(accessToken, true),
},
},
})
}
// Do the same thing as TestAccProviderUserProjectOverride, but using a resource that gets its project via
// a reference to a different resource instead of a project field.
func TestAccProviderIndirectUserProjectOverride(t *testing.T) {
// Parallel fine-grained resource creation
acctest.SkipIfVcr(t)
t.Parallel()
org := envvar.GetTestOrgFromEnv(t)
billing := envvar.GetTestBillingAccountFromEnv(t)
pid := "tf-test-" + acctest.RandString(t, 10)
config := acctest.BootstrapConfig(t)
accessToken, err := acctest.SetupProjectsAndGetAccessToken(org, billing, pid, "cloudkms", config)
if err != nil {
t.Error(err)
}
acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
// No TestDestroy since that's not really the point of this test
Steps: []resource.TestStep{
{
Config: testAccProviderIndirectUserProjectOverride_step2(pid, accessToken, false),
ExpectError: regexp.MustCompile(`Cloud Key Management Service \(KMS\) API has not been used`),
},
{
Config: testAccProviderIndirectUserProjectOverride_step2(pid, accessToken, true),
},
{
ResourceName: "google_kms_crypto_key.project-2-key",
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccProviderIndirectUserProjectOverride_step3(accessToken, true),
},
},
})
}
func TestAccProviderCredentialsEmptyString(t *testing.T) {
// Test is not parallel because ENVs are set.
// Need to skip VCR as this test downloads providers from the Terraform Registry
acctest.SkipIfVcr(t)
creds := envvar.GetTestCredsFromEnv()
project := envvar.GetTestProjectFromEnv()
t.Setenv("GOOGLE_CREDENTIALS", creds)
t.Setenv("GOOGLE_PROJECT", project)
pid := "tf-test-" + acctest.RandString(t, 10)
acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
// No TestDestroy since that's not really the point of this test
Steps: []resource.TestStep{
{
// This is a control for the other test steps; the provider block doesn't contain `credentials = ""`
Config: testAccProviderCredentials_actWithCredsFromEnv(pid),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
PlanOnly: true,
ExpectNonEmptyPlan: true,
},
{
// Assert that errors are expected with credentials when
// - GOOGLE_CREDENTIALS is set
// - provider block has credentials = ""
// - TPG v4.60.2 is used
// Context: this was an addidental breaking change introduced with muxing
Config: testAccProviderCredentials_actWithCredsFromEnv_emptyString(pid),
ExternalProviders: map[string]resource.ExternalProvider{
"google": {
VersionConstraint: "4.60.2",
Source: "hashicorp/google",
},
},
PlanOnly: true,
ExpectNonEmptyPlan: true,
ExpectError: regexp.MustCompile(`unexpected end of JSON input`),
},
{
// Assert that errors are NOT expected with credentials when
// - GOOGLE_CREDENTIALS is set
// - provider block has credentials = ""
// - TPG v4.84.0 is used
// Context: this was the fix for the unintended breaking change in 4.60.2
Config: testAccProviderCredentials_actWithCredsFromEnv_emptyString(pid),
ExternalProviders: map[string]resource.ExternalProvider{
"google": {
VersionConstraint: "4.84.0",
Source: "hashicorp/google",
},
},
PlanOnly: true,
ExpectNonEmptyPlan: true,
},
{
// Validation errors are expected in 5.0.0+
// Context: we intentionally introduced the breaking change again in 5.0.0+
Config: testAccProviderCredentials_actWithCredsFromEnv_emptyString(pid),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
PlanOnly: true,
ExpectNonEmptyPlan: true,
ExpectError: regexp.MustCompile(`expected a non-empty string`),
},
},
})
}
func TestAccProviderEmptyStrings(t *testing.T) {
t.Parallel()
acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
// No TestDestroy since that's not really the point of this test
Steps: []resource.TestStep{
// When no values are set in the provider block there are no errors
// This test case is a control to show validation doesn't accidentally flag unset fields
// The "" argument is a lack of key = value being passed into the provider block
{
Config: testAccProvider_checkPlanTimeErrors("", acctest.RandString(t, 10)),
PlanOnly: true,
ExpectNonEmptyPlan: true,
},
// credentials as an empty string causes a validation error
{
Config: testAccProvider_checkPlanTimeErrors(`credentials = ""`, acctest.RandString(t, 10)),
PlanOnly: true,
ExpectNonEmptyPlan: true,
ExpectError: regexp.MustCompile(`expected a non-empty string`),
},
// access_token as an empty string causes a validation error
{
Config: testAccProvider_checkPlanTimeErrors(`access_token = ""`, acctest.RandString(t, 10)),
PlanOnly: true,
ExpectNonEmptyPlan: true,
ExpectError: regexp.MustCompile(`expected a non-empty string`),
},
// impersonate_service_account as an empty string causes a validation error
{
Config: testAccProvider_checkPlanTimeErrors(`impersonate_service_account = ""`, acctest.RandString(t, 10)),
PlanOnly: true,
ExpectNonEmptyPlan: true,
ExpectError: regexp.MustCompile(`expected a non-empty string`),
},
// project as an empty string causes a validation error
{
Config: testAccProvider_checkPlanTimeErrors(`project = ""`, acctest.RandString(t, 10)),
PlanOnly: true,
ExpectNonEmptyPlan: true,
ExpectError: regexp.MustCompile(`expected a non-empty string`),
},
// billing_project as an empty string causes a validation error
{
Config: testAccProvider_checkPlanTimeErrors(`billing_project = ""`, acctest.RandString(t, 10)),
PlanOnly: true,
ExpectNonEmptyPlan: true,
ExpectError: regexp.MustCompile(`expected a non-empty string`),
},
// region as an empty string causes a validation error
{
Config: testAccProvider_checkPlanTimeErrors(`region = ""`, acctest.RandString(t, 10)),
PlanOnly: true,
ExpectNonEmptyPlan: true,
ExpectError: regexp.MustCompile(`expected a non-empty string`),
},
// zone as an empty string causes a validation error
{
Config: testAccProvider_checkPlanTimeErrors(`zone = ""`, acctest.RandString(t, 10)),
PlanOnly: true,
ExpectNonEmptyPlan: true,
ExpectError: regexp.MustCompile(`expected a non-empty string`),
},
},
})
}
func testAccProviderBasePath_setBasePath(endpoint, name string) string {
return fmt.Sprintf(`
provider "google" {
alias = "compute_custom_endpoint"
compute_custom_endpoint = "%s"
}
resource "google_compute_address" "default" {
provider = google.compute_custom_endpoint
name = "tf-test-address-%s"
}`, endpoint, name)
}
func testAccProviderMeta_setModuleName(key, name string) string {
return fmt.Sprintf(`
terraform {
provider_meta "google" {
module_name = "%s"
}
}
resource "google_compute_address" "default" {
name = "tf-test-address-%s"
}`, key, name)
}
// Set up two projects. Project 1 has a service account that is used to create a
// pubsub topic in project 2. The pubsub API is only enabled in project 2,
// which causes the create to fail unless user_project_override is set to true.
func testAccProviderUserProjectOverride_step2(accessToken, pid string, override bool, topicName string) string {
return fmt.Sprintf(`
// See step 3 below, which is really step 2 minus the pubsub topic.
// Step 3 exists because provider configurations can't be removed while objects
// created by that provider still exist in state. Step 3 will remove the
// pubsub topic so the whole config can be deleted.
%s
resource "google_pubsub_topic" "project-2-topic" {
provider = google.project-1-token
project = "%s-2"
name = "%s"
labels = {
foo = "bar"
}
}
`, testAccProviderUserProjectOverride_step3(accessToken, override), pid, topicName)
}
func testAccProviderUserProjectOverride_step3(accessToken string, override bool) string {
return fmt.Sprintf(`
provider "google" {
alias = "project-1-token"
access_token = "%s"
user_project_override = %v
}
`, accessToken, override)
}
func testAccProviderIndirectUserProjectOverride_step2(pid, accessToken string, override bool) string {
return fmt.Sprintf(`
// See step 3 below, which is really step 2 minus the kms resources.
// Step 3 exists because provider configurations can't be removed while objects
// created by that provider still exist in state. Step 3 will remove the
// kms resources so the whole config can be deleted.
%s
resource "google_kms_key_ring" "project-2-keyring" {
provider = google.project-1-token
project = "%s-2"
name = "%s"
location = "us-central1"
}
resource "google_kms_crypto_key" "project-2-key" {
provider = google.project-1-token
name = "%s"
key_ring = google_kms_key_ring.project-2-keyring.id
}
data "google_kms_secret_ciphertext" "project-2-ciphertext" {
provider = google.project-1-token
crypto_key = google_kms_crypto_key.project-2-key.id
plaintext = "my-secret"
}
`, testAccProviderIndirectUserProjectOverride_step3(accessToken, override), pid, pid, pid)
}
func testAccProviderIndirectUserProjectOverride_step3(accessToken string, override bool) string {
return fmt.Sprintf(`
provider "google" {
alias = "project-1-token"
access_token = "%s"
user_project_override = %v
}
`, accessToken, override)
}
// Copy the Mmv1 generated function testAccCheckComputeAddressDestroyProducer from the compute_test package to here,
// as that function is in the _test.go file and not importable.
func testAccCheckComputeAddressDestroyProducer(t *testing.T) func(s *terraform.State) error {
return func(s *terraform.State) error {
for name, rs := range s.RootModule().Resources {
if rs.Type != "google_compute_address" {
continue
}
if strings.HasPrefix(name, "data.") {
continue
}
config := acctest.GoogleProviderConfig(t)
url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{ComputeBasePath}}projects/{{project}}/regions/{{region}}/addresses/{{name}}")
if err != nil {
return err
}
billingProject := ""
if config.BillingProject != "" {
billingProject = config.BillingProject
}
_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: billingProject,
RawURL: url,
UserAgent: config.UserAgent,
})
if err == nil {
return fmt.Errorf("ComputeAddress still exists at %s", url)
}
}
return nil
}
}
func testAccProviderCredentials_actWithCredsFromEnv(name string) string {
return fmt.Sprintf(`
provider "google" {
alias = "testing_credentials"
}
resource "google_compute_address" "default" {
provider = google.testing_credentials
name = "%s"
}`, name)
}
func testAccProviderCredentials_actWithCredsFromEnv_emptyString(name string) string {
return fmt.Sprintf(`
provider "google" {
alias = "testing_credentials"
credentials = ""
}
resource "google_compute_address" "default" {
provider = google.testing_credentials
name = "%s"
}`, name)
}
func testAccProvider_checkPlanTimeErrors(providerArgument, randString string) string {
return fmt.Sprintf(`
provider "google" {
%s
}
# A random resource so that the test can generate a plan (can't check validation errors when plan is empty)
resource "google_pubsub_topic" "example" {
name = "tf-test-planned-resource-%s"
}
`, providerArgument, randString)
}