Skip to content

Commit

Permalink
Add acceptance tests for how provider handles `impersonate_service_ac…
Browse files Browse the repository at this point in the history
…count` argument (#11641) (#19597)

[upstream:b6c3277a6d303977d199ca084f52e77b72004bcc]

Signed-off-by: Modular Magician <magic-modules@google.com>
  • Loading branch information
modular-magician committed Sep 24, 2024
1 parent 3a3905d commit 7d9adcb
Show file tree
Hide file tree
Showing 7 changed files with 351 additions and 6 deletions.
3 changes: 3 additions & 0 deletions .changelog/11641.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:none

```
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ func (d *GoogleProviderConfigPluginFrameworkDataSource) Read(ctx context.Context

data.Credentials = d.providerConfig.Credentials
data.AccessToken = d.providerConfig.AccessToken
// TODO(SarahFrench) - impersonate_service_account
data.ImpersonateServiceAccount = d.providerConfig.ImpersonateServiceAccount
// TODO(SarahFrench) - impersonate_service_account_delegates
data.Project = d.providerConfig.Project
data.Region = d.providerConfig.Region
Expand Down
3 changes: 2 additions & 1 deletion google/fwprovider/framework_provider_access_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ func testAccFwProvider_access_token_configPrecedenceOverEnvironmentVariables(t *
Config: testAccFwProvider_access_tokenInProviderBlock(context),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.google_provider_config_plugin_framework.default", "access_token", providerAccessToken),
)},
),
},
},
})
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package fwprovider_test

import (
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-provider-google/google/acctest"
)

// TestAccFwProvider_impersonate_service_account is a series of acc tests asserting how the plugin-framework provider handles impersonate_service_account arguments
// It is plugin-framework specific because the HCL used provisions plugin-framework-implemented resources
// It is a counterpart to TestAccSdkProvider_impersonate_service_account
func TestAccFwProvider_impersonate_service_account(t *testing.T) {
testCases := map[string]func(t *testing.T){
// Configuring the provider using inputs
"config takes precedence over environment variables": testAccFwProvider_impersonate_service_account_configPrecedenceOverEnvironmentVariables,
"when impersonate_service_account is unset in the config, environment variables are used in a given order": testAccFwProvider_impersonate_service_account_precedenceOrderEnvironmentVariables, // GOOGLE_IMPERSONATE_SERVICE_ACCOUNT

// Schema-level validation
"when impersonate_service_account is set to an empty string in the config the value isn't ignored and results in an error": testAccFwProvider_impersonate_service_account_emptyStringValidation,

// Usage
// We need to wait for a non-Firebase resource to be migrated to the plugin-framework to enable writing this test
// "impersonate_service_account controls which service account is used for actions"
}

for name, tc := range testCases {
// shadow the tc variable into scope so that when
// the loop continues, if t.Run hasn't executed tc(t)
// yet, we don't have a race condition
// see https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables
tc := tc
t.Run(name, func(t *testing.T) {
tc(t)
})
}
}

func testAccFwProvider_impersonate_service_account_configPrecedenceOverEnvironmentVariables(t *testing.T) {
acctest.SkipIfVcr(t) // Test doesn't interact with API

impersonateServiceAccountEnvironment := "value-from-envs@example.com"
impersonateServiceAccountProviderBlock := "value-from-provider-block@example.com"

// ensure all possible impersonate_service_account env vars set; show they aren't used
t.Setenv("GOOGLE_IMPERSONATE_SERVICE_ACCOUNT", impersonateServiceAccountEnvironment)

context := map[string]interface{}{
"impersonate_service_account": impersonateServiceAccountProviderBlock,
}

acctest.VcrTest(t, resource.TestCase{
// No PreCheck for checking ENVs
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
Steps: []resource.TestStep{
{
Config: testAccFwProvider_impersonate_service_account_inProviderBlock(context),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.google_provider_config_plugin_framework.default", "impersonate_service_account", impersonateServiceAccountProviderBlock),
),
},
},
})
}

func testAccFwProvider_impersonate_service_account_precedenceOrderEnvironmentVariables(t *testing.T) {
acctest.SkipIfVcr(t) // Test doesn't interact with API
/*
These are all the ENVs for impersonate_service_account, and they are in order of precedence.
GOOGLE_IMPERSONATE_SERVICE_ACCOUNT
*/

impersonateServiceAccount := "foobar@example.com"

context := map[string]interface{}{}

acctest.VcrTest(t, resource.TestCase{
// No PreCheck for checking ENVs
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
Steps: []resource.TestStep{
{
PreConfig: func() {
t.Setenv("GOOGLE_IMPERSONATE_SERVICE_ACCOUNT", impersonateServiceAccount)
},
Config: testAccFwProvider_impersonate_service_account_inEnvsOnly(context),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.google_provider_config_plugin_framework.default", "impersonate_service_account", impersonateServiceAccount),
),
},
},
})
}

func testAccFwProvider_impersonate_service_account_emptyStringValidation(t *testing.T) {
acctest.SkipIfVcr(t) // Test doesn't interact with API

impersonateServiceAccountEnvironment := "value-from-envs@example.com"

// ensure all possible impersonate_service_account env vars set; show they aren't used
t.Setenv("GOOGLE_IMPERSONATE_SERVICE_ACCOUNT", impersonateServiceAccountEnvironment)

context := map[string]interface{}{
"impersonate_service_account": "", // empty string used
}

acctest.VcrTest(t, resource.TestCase{
// No PreCheck for checking ENVs
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
Steps: []resource.TestStep{
{
Config: testAccFwProvider_impersonate_service_account_inProviderBlock(context),
PlanOnly: true,
ExpectError: regexp.MustCompile("expected a non-empty string"),
},
},
})
}

// testAccFwProvider_impersonate_service_account_inProviderBlock allows setting the impersonate_service_account argument in a provider block.
func testAccFwProvider_impersonate_service_account_inProviderBlock(context map[string]interface{}) string {
return acctest.Nprintf(`
provider "google" {
impersonate_service_account = "%{impersonate_service_account}"
}
data "google_provider_config_plugin_framework" "default" {}
`, context)
}

// testAccFwProvider_impersonate_service_account_inEnvsOnly allows testing when the impersonate_service_account argument
// is only supplied via ENVs
func testAccFwProvider_impersonate_service_account_inEnvsOnly(context map[string]interface{}) string {
return acctest.Nprintf(`
data "google_provider_config_plugin_framework" "default" {}
`, context)
}
8 changes: 5 additions & 3 deletions google/fwtransport/framework_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ import (

type FrameworkProviderConfig struct {
// Temporary, as we'll replace use of FrameworkProviderConfig with transport_tpg.Config soon
// transport_tpg.Config has a Credentials field, hence this change is needed
Credentials types.String
AccessToken types.String
// transport_tpg.Config has a the fields below, hence these changes are needed
Credentials types.String
AccessToken types.String
ImpersonateServiceAccount types.String
// End temporary

BillingProject types.String
Expand Down Expand Up @@ -343,6 +344,7 @@ func (p *FrameworkProviderConfig) LoadAndValidateFramework(ctx context.Context,
// Temporary
p.Credentials = data.Credentials
p.AccessToken = data.AccessToken
p.ImpersonateServiceAccount = data.ImpersonateServiceAccount
// End temporary

// Copy values from the ProviderModel struct containing data about the provider configuration (present only when responsing to ConfigureProvider rpc calls)
Expand Down
3 changes: 2 additions & 1 deletion google/provider/provider_access_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ func testAccSdkProvider_access_token_configPrecedenceOverEnvironmentVariables(t
Config: testAccSdkProvider_access_tokenInProviderBlock(context),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.google_provider_config_sdk.default", "access_token", providerAccessToken),
)},
),
},
},
})
}
Expand Down
197 changes: 197 additions & 0 deletions google/provider/provider_impersonate_service_account_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package provider_test

import (
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-provider-google/google/acctest"
)

// TestAccSdkProvider_impersonate_service_account is a series of acc tests asserting how the SDK provider handles impersonate_service_account arguments
// It is SDK specific because the HCL used provisions SDK-implemented resources
// It is a counterpart to TestAccFwProvider_impersonate_service_account
func TestAccSdkProvider_impersonate_service_account(t *testing.T) {
testCases := map[string]func(t *testing.T){
// Configuring the provider using inputs
"config takes precedence over environment variables": testAccSdkProvider_impersonate_service_account_configPrecedenceOverEnvironmentVariables,
"when impersonate_service_account is unset in the config, environment variables are used in a given order": testAccSdkProvider_impersonate_service_account_precedenceOrderEnvironmentVariables, // GOOGLE_IMPERSONATE_SERVICE_ACCOUNT

// Schema-level validation
"when impersonate_service_account is set to an empty string in the config the value isn't ignored and results in an error": testAccSdkProvider_impersonate_service_account_emptyStringValidation,

// Usage
"impersonate_service_account controls which service account is used for actions": testAccSdkProvider_impersonate_service_account_usage,
}

for name, tc := range testCases {
// shadow the tc variable into scope so that when
// the loop continues, if t.Run hasn't executed tc(t)
// yet, we don't have a race condition
// see https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables
tc := tc
t.Run(name, func(t *testing.T) {
tc(t)
})
}
}

func testAccSdkProvider_impersonate_service_account_configPrecedenceOverEnvironmentVariables(t *testing.T) {
acctest.SkipIfVcr(t) // Test doesn't interact with API

impersonateServiceAccountEnvironment := "value-from-envs@example.com"
impersonateServiceAccountProviderBlock := "value-from-provider-block@example.com"

// ensure all possible impersonate_service_account env vars set; show they aren't used
t.Setenv("GOOGLE_IMPERSONATE_SERVICE_ACCOUNT", impersonateServiceAccountEnvironment)

context := map[string]interface{}{
"impersonate_service_account": impersonateServiceAccountProviderBlock,
}

acctest.VcrTest(t, resource.TestCase{
// No PreCheck for checking ENVs
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
Steps: []resource.TestStep{
{
Config: testAccSdkProvider_impersonate_service_account_inProviderBlock(context),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.google_provider_config_sdk.default", "impersonate_service_account", impersonateServiceAccountProviderBlock),
)},
},
})
}

func testAccSdkProvider_impersonate_service_account_precedenceOrderEnvironmentVariables(t *testing.T) {
acctest.SkipIfVcr(t) // Test doesn't interact with API
/*
These are all the ENVs for impersonate_service_account, and they are in order of precedence.
GOOGLE_IMPERSONATE_SERVICE_ACCOUNT
*/

impersonateServiceAccount := "foobar@example.com"

context := map[string]interface{}{}

acctest.VcrTest(t, resource.TestCase{
// No PreCheck for checking ENVs
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
Steps: []resource.TestStep{
{
PreConfig: func() {
t.Setenv("GOOGLE_IMPERSONATE_SERVICE_ACCOUNT", impersonateServiceAccount)
},
Config: testAccSdkProvider_impersonate_service_account_inEnvsOnly(context),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.google_provider_config_sdk.default", "impersonate_service_account", impersonateServiceAccount),
)},
},
})
}

func testAccSdkProvider_impersonate_service_account_emptyStringValidation(t *testing.T) {
acctest.SkipIfVcr(t) // Test doesn't interact with API

impersonateServiceAccountEnvironment := "value-from-envs@example.com"

// ensure all possible impersonate_service_account env vars set; show they aren't used
t.Setenv("GOOGLE_IMPERSONATE_SERVICE_ACCOUNT", impersonateServiceAccountEnvironment)

context := map[string]interface{}{
"impersonate_service_account": "", // empty string used
}

acctest.VcrTest(t, resource.TestCase{
// No PreCheck for checking ENVs
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
Steps: []resource.TestStep{
{
Config: testAccSdkProvider_impersonate_service_account_inProviderBlock(context),
PlanOnly: true,
ExpectError: regexp.MustCompile("expected a non-empty string"),
},
},
})
}

func testAccSdkProvider_impersonate_service_account_usage(t *testing.T) {
acctest.SkipIfVcr(t) // Test doesn't interact with API

// ensure env vars unset
t.Setenv("GOOGLE_IMPERSONATE_SERVICE_ACCOUNT", "")

context := map[string]interface{}{
"random_suffix": acctest.RandString(t, 10),
}

acctest.VcrTest(t, resource.TestCase{
// No PreCheck for checking ENVs
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
Steps: []resource.TestStep{
{
Config: testAccSdkProvider_impersonate_service_account_testViaFailure(context),
ExpectError: regexp.MustCompile("Error creating Topic: googleapi: Error 403: User not authorized"),
},
},
})
}

// testAccSdkProvider_impersonate_service_account_inProviderBlock allows setting the impersonate_service_account argument in a provider block.
// This function uses data.google_provider_config_sdk because it is implemented with the SDKv2
func testAccSdkProvider_impersonate_service_account_inProviderBlock(context map[string]interface{}) string {
return acctest.Nprintf(`
provider "google" {
impersonate_service_account = "%{impersonate_service_account}"
}
data "google_provider_config_sdk" "default" {}
`, context)
}

// testAccSdkProvider_impersonate_service_account_inEnvsOnly allows testing when the impersonate_service_account argument
// is only supplied via ENVs
func testAccSdkProvider_impersonate_service_account_inEnvsOnly(context map[string]interface{}) string {
return acctest.Nprintf(`
data "google_provider_config_sdk" "default" {}
`, context)
}

func testAccSdkProvider_impersonate_service_account_testViaFailure(context map[string]interface{}) string {
return acctest.Nprintf(`
// This will succeed due to the Terraform identity having necessary permissions
resource "google_pubsub_topic" "ok" {
name = "tf-test-%{random_suffix}-ok"
}
// Create a service account and ensure the Terraform identity can make tokens for it
resource "google_service_account" "default" {
account_id = "tf-test-%{random_suffix}"
display_name = "Acceptance test impersonated service account"
}
data "google_client_openid_userinfo" "me" {
}
resource "google_service_account_iam_member" "token" {
service_account_id = google_service_account.default.name
role = "roles/iam.serviceAccountTokenCreator"
member = "serviceAccount:${data.google_client_openid_userinfo.me.email}"
}
// Impersonate the created service account
provider "google" {
alias = "impersonation"
impersonate_service_account = google_service_account.default.email
}
// This will fail due to the impersonated service account not having any permissions
resource "google_pubsub_topic" "fail" {
provider = google.impersonation
name = "tf-test-%{random_suffix}-fail"
}
`, context)
}

0 comments on commit 7d9adcb

Please sign in to comment.