Skip to content

Commit

Permalink
Add provider attribute universe_domain (#8657)
Browse files Browse the repository at this point in the history
* Add provider attribute universe_domain

* Replace universe domain field with env vars, added more tests for storage and pubsub

* Extract universe domain from SA key file

* Include JWT scoped flow for authentication

* Add JWT Scoped flow in default application credentials

* Add test for GDU, enhance logging

* Resolve build issue

* Create package for tests, resolve review discussions

* Modify logic to copy test files to down-streams

* Change test package name from universe_test to universe

* Update indentation on framework_config.go.erb

* Update indentation on framework_config.go.erb

* Update credential logging message

Co-authored-by: Riley Karson <[email protected]>

* Remove universe_domain replacement in utils.go for long URL replacement

* Update error message for conflicted universe_domain definition

* Add basic description for universe_domain.

* Modify error message when universe domain mismatches

---------

Co-authored-by: Riley Karson <[email protected]>
  • Loading branch information
hao-nan-li and rileykarson authored Oct 20, 2023
1 parent 180e0a9 commit 340c76d
Show file tree
Hide file tree
Showing 15 changed files with 402 additions and 11 deletions.
4 changes: 2 additions & 2 deletions mmv1/provider/terraform/common~copy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
<% end -%>

<%
Dir["third_party/terraform/provider/*.go"].each do |file_path|
fname = file_path.split('/')[-1]
Dir["third_party/terraform/provider/**/*.go"].each do |file_path|
fname = file_path.delete_prefix("third_party/terraform/provider")
-%>
'<%= dir -%>/provider/<%= fname -%>': 'third_party/terraform/provider/<%= fname -%>'
<% end -%>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import_url = import_format.gsub(/({{)%?(\w+)(}})/, '%s').gsub(object.__product.b
<% else -%>
<% import_qualifiers.push("\"#{example.region_override}\"") -%>
<% end -%>
<% elsif param == 'universe_domain' -%>
<% import_qualifiers.push('envvar.GetTestUniverseDomainFromEnv()') -%>
<% end -%>
<% end -%>
func TestAcc<%= resource_name -%>IamBindingGenerated(t *testing.T) {
Expand Down
10 changes: 10 additions & 0 deletions mmv1/third_party/terraform/envvar/envvar_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ var OrgTargetEnvVars = []string{
"GOOGLE_ORG_2",
}

var UniverseDomainEnvVars = []string{
"GOOGLE_UNIVERSE_DOMAIN",
}

// This is the billing account that will be charged for the infrastructure used during testing. For
// that reason, it is also the billing account used for creating new projects.
var BillingAccountEnvVars = []string{
Expand Down Expand Up @@ -111,6 +115,12 @@ func GetTestCredsFromEnv() string {
return transport_tpg.MultiEnvSearch(CredsEnvVars)
}

// Returns googleapis.com if there's no universe set.
func GetTestUniverseDomainFromEnv(t *testing.T) string {
SkipIfEnvNotSet(t, IdentityUserEnvVars...)
return transport_tpg.MultiEnvSearch(UniverseDomainEnvVars)
}

// AccTestPreCheck ensures at least one of the region env variables is set.
func GetTestRegionFromEnv() string {
return transport_tpg.MultiEnvSearch(RegionEnvVars)
Expand Down
1 change: 1 addition & 0 deletions mmv1/third_party/terraform/fwmodels/provider_model.go.erb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type ProviderModel struct {
UserProjectOverride types.Bool `tfsdk:"user_project_override"`
RequestTimeout types.String `tfsdk:"request_timeout"`
RequestReason types.String `tfsdk:"request_reason"`
UniverseDomain types.String `tfsdk:"universe_domain"`
DefaultLabels types.Map `tfsdk:"default_labels"`

// Generated Products
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,13 @@ func (p *FrameworkProvider) Schema(_ context.Context, _ provider.SchemaRequest,
"request_reason": schema.StringAttribute{
Optional: true,
},
"universe_domain": schema.StringAttribute{
Optional: true,
},
"default_labels": schema.MapAttribute{
Optional: true,
ElementType: types.StringType,
},

// Generated Products
<% products.each do |product| -%>
"<%= product[:definitions].name.underscore -%>_custom_endpoint": &schema.StringAttribute{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type FrameworkProviderConfig struct {
RequestBatcherServiceUsage *transport_tpg.RequestBatcher
Scopes types.List
TokenSource oauth2.TokenSource
UniverseDomain types.String
UserAgent string
UserProjectOverride types.Bool

Expand Down Expand Up @@ -103,6 +104,8 @@ func (p *FrameworkProviderConfig) LoadAndValidateFramework(ctx context.Context,
p.Zone = data.Zone
p.UserProjectOverride = data.UserProjectOverride
p.PollInterval = 10 * time.Second
p.Project = data.Project
p.UniverseDomain = data.UniverseDomain
p.RequestBatcherServiceUsage = transport_tpg.NewRequestBatcher("Service Usage", ctx, batchingConfig)
p.RequestBatcherIam = transport_tpg.NewRequestBatcher("IAM", ctx, batchingConfig)
}
Expand Down
44 changes: 44 additions & 0 deletions mmv1/third_party/terraform/provider/provider.go.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package provider

import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
Expand Down Expand Up @@ -113,6 +115,11 @@ func Provider() *schema.Provider {
Elem: &schema.Schema{Type: schema.TypeString},
},

"universe_domain": {
Type: schema.TypeString,
Optional: true,
},

"batching": {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -706,6 +713,43 @@ func ProviderConfigure(ctx context.Context, d *schema.ResourceData, p *schema.Pr
"GOOGLE_OAUTH_ACCESS_TOKEN",
})
}

// set universe_domain based on the service account key file.
if config.Credentials != "" {
contents, _, err := verify.PathOrContents(config.Credentials)
if err != nil {
return nil, diag.FromErr(fmt.Errorf("error loading service account credentials: %s", err))
}
var content map[string]any

if err := json.Unmarshal([]byte(contents), &content); err != nil {
return nil, diag.FromErr(err)
}

if content["universe_domain"] != nil {
config.UniverseDomain = content["universe_domain"].(string)
}
}

// Check if the user provided a value from the universe_domain field
if v, ok := d.GetOk("universe_domain"); ok {
if config.UniverseDomain == "" {
config.UniverseDomain = v.(string)
} else if v.(string) != config.UniverseDomain {
if _, err := os.Stat(config.Credentials); err == nil {
return nil, diag.FromErr(fmt.Errorf("'%s' does not match the universe domain '%s' already set in the credential file '%s'. The 'universe_domain' provider configuration can not be used to override the universe domain that is defined in the active credential. Set the 'universe_domain' provider configuration when universe domain information is not already available in the credential, e.g. when authenticating with a JWT token.", v, config.UniverseDomain, config.Credentials))
} else {
return nil, diag.FromErr(fmt.Errorf("'%s' does not match the universe domain '%s' supplied directly to Terraform. The 'universe_domain' provider configuration can not be used to override the universe domain that is defined in the active credential. Set the 'universe_domain' provider configuration when universe domain information is not already available in the credential, e.g. when authenticating with a JWT token.", v, config.UniverseDomain))
}
}
}

// Replace hostname by the universe_domain field.
if config.UniverseDomain != "" && config.UniverseDomain != "googleapis.com" {
for key, basePath := range transport_tpg.DefaultBasePaths {
transport_tpg.DefaultBasePaths[key] = strings.ReplaceAll(basePath, "googleapis.com", config.UniverseDomain)
}
}

// Given that impersonate_service_account is a secondary auth method, it has
// no conflicts to worry about. We pull the env var in a DefaultFunc.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package universe_test

import (
"fmt"
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"

"github.com/hashicorp/terraform-provider-google/google/acctest"
"github.com/hashicorp/terraform-provider-google/google/envvar"
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
)

func TestAccUniverseDomainDisk(t *testing.T) {
// Skip this test in all env since this can only run in specific test project.
t.Skip()

universeDomain := envvar.GetTestUniverseDomainFromEnv(t)

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccCheckComputeDiskDestroyProducer(t),
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccUniverseDomain_basic_disk(universeDomain),
},
},
})
}

func TestAccDefaultUniverseDomainDisk(t *testing.T) {
universeDomain := "googleapis.com"

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccCheckComputeDiskDestroyProducer(t),
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccUniverseDomain_basic_disk(universeDomain),
},
},
})
}

func testAccUniverseDomain_basic_disk(universeDomain string) string {
return fmt.Sprintf(`
provider "google" {
universe_domain = "%s"
}
resource "google_compute_instance_template" "instance_template" {
name = "demo-this"
machine_type = "n1-standard-1"
// boot disk
disk {
disk_size_gb = 20
}
network_interface {
network = "default"
}
}
`, universeDomain)
}

func testAccCheckComputeDiskDestroyProducer(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_disk" {
continue
}
if strings.HasPrefix(name, "data.") {
continue
}

config := acctest.GoogleProviderConfig(t)

url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{ComputeBasePath}}projects/{{project}}/zones/{{zone}}/disks/{{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("ComputeDisk still exists at %s", url)
}
}

return nil
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package universe_test

import (
"fmt"
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"

"github.com/hashicorp/terraform-provider-google/google/acctest"
"github.com/hashicorp/terraform-provider-google/google/envvar"
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
)

func TestAccUniverseDomainPubSub(t *testing.T) {
// Skip this test in all env since this can only run in specific test project.
t.Skip()

universeDomain := envvar.GetTestUniverseDomainFromEnv(t)
topic := fmt.Sprintf("tf-test-topic-%s", acctest.RandString(t, 10))
subscription := fmt.Sprintf("tf-test-sub-%s", acctest.RandString(t, 10))

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
CheckDestroy: testAccCheckPubsubSubscriptionDestroyProducer(t),
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccUniverseDomain_basic_pubsub(universeDomain, topic, subscription),
},
},
})
}

func testAccUniverseDomain_basic_pubsub(universeDomain, topic, subscription string) string {
return fmt.Sprintf(`
provider "google" {
universe_domain = "%s"
}
resource "google_pubsub_topic" "foo" {
name = "%s"
}
resource "google_pubsub_subscription" "foo" {
name = "%s"
topic = google_pubsub_topic.foo.id
message_retention_duration = "1200s"
retain_acked_messages = true
ack_deadline_seconds = 20
expiration_policy {
ttl = ""
}
enable_message_ordering = false
}
`, universeDomain, topic, subscription)
}

func testAccCheckPubsubSubscriptionDestroyProducer(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_pubsub_subscription" {
continue
}
if strings.HasPrefix(name, "data.") {
continue
}

config := acctest.GoogleProviderConfig(t)

url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{PubsubBasePath}}projects/{{project}}/subscriptions/{{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("PubsubSubscription still exists at %s", url)
}
}

return nil
}
}
Loading

0 comments on commit 340c76d

Please sign in to comment.