Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set google_service_account IAM-related fields during plan stage #11929

Merged
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package resourcemanager

import (
"context"
"fmt"
"log"
"strings"
"time"

Expand Down Expand Up @@ -30,6 +32,7 @@ func ResourceGoogleServiceAccount() *schema.Resource {
},
CustomizeDiff: customdiff.All(
tpgresource.DefaultProviderProject,
resourceServiceAccountCustomDiff,
),
Schema: map[string]*schema.Schema{
"email": {
Expand Down Expand Up @@ -322,3 +325,34 @@ func resourceGoogleServiceAccountImport(d *schema.ResourceData, meta interface{}

return []*schema.ResourceData{d}, nil
}

func ResourceServiceAccountCustomDiffFunc(diff tpgresource.TerraformResourceDiff) error {
if !tpgresource.IsNewResource(diff) && !diff.HasChange("account_id") {
return nil
}

aid := diff.Get("account_id").(string)
proj := diff.Get("project").(string)
if aid == "" || proj == "" {
return nil
}

email := fmt.Sprintf("%s@%s.iam.gserviceaccount.com", aid, proj)
if err := diff.SetNew("email", email); err != nil {
return fmt.Errorf("error setting email: %s", err)
}
if err := diff.SetNew("member", "serviceAccount:"+email); err != nil {
return fmt.Errorf("error setting member: %s", err)
}

return nil
}
func resourceServiceAccountCustomDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error {
if ud := transport_tpg.GetUniverseDomainFromMeta(meta); ud != "googleapis.com" {
log.Printf("[WARN] The UniverseDomain is set to %q. Skipping resourceServiceAccountCustomDiff", ud)
return nil
}

// separate func to allow unit testing
return ResourceServiceAccountCustomDiffFunc(diff)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package resourcemanager_test

import (
"fmt"
"maps"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/hashicorp/terraform-provider-google/google/acctest"
"github.com/hashicorp/terraform-provider-google/google/envvar"
tpgresourcemanager "github.com/hashicorp/terraform-provider-google/google/services/resourcemanager"
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
)

// Test that a service account resource can be created, updated, and destroyed
Expand Down Expand Up @@ -299,3 +302,135 @@ resource "google_service_account" "acceptance" {
}
`, account, name, desc, disabled)
}

func TestResourceServiceAccountCustomDiff(t *testing.T) {
t.Parallel()

accountId := "a" + acctest.RandString(t, 10)
project := envvar.GetTestProjectFromEnv()
if project == "" {
project = "test-project"
}
expectedEmail := fmt.Sprintf("%s@%s.iam.gserviceaccount.com", accountId, project)
expectedMember := "serviceAccount:" + expectedEmail

cases := []struct {
name string
isNew bool
before map[string]interface{}
after map[string]interface{}
result map[string]interface{}
mikesmitty marked this conversation as resolved.
Show resolved Hide resolved
wantError bool
mikesmitty marked this conversation as resolved.
Show resolved Hide resolved
}{
{
name: "normal",
isNew: true,
wantError: false,
before: map[string]interface{}{},
after: map[string]interface{}{
"account_id": accountId,
"project": project,
},
result: map[string]interface{}{
"account_id": accountId,
"project": project,
"email": expectedEmail,
"member": expectedMember,
},
},
{
name: "no change",
isNew: false,
wantError: false,
before: map[string]interface{}{
"account_id": accountId,
"email": "dontchange",
"member": "dontchange",
"project": project,
},
after: map[string]interface{}{
"account_id": accountId,
"project": project,
},
result: map[string]interface{}{
"account_id": accountId,
"project": project,
},
},
{
name: "recreate",
isNew: true,
wantError: false,
before: map[string]interface{}{
"account_id": "recreate-account",
"email": "recreate-email",
"member": "recreate-member",
"project": project,
},
after: map[string]interface{}{
"account_id": accountId,
"project": project,
},
result: map[string]interface{}{
"account_id": accountId,
"project": project,
"email": expectedEmail,
"member": expectedMember,
},
},
{
name: "missing account_id",
isNew: true,
wantError: false,
before: map[string]interface{}{},
after: map[string]interface{}{
"account_id": "",
"project": project,
},
result: map[string]interface{}{
"account_id": "",
"project": project,
},
},
{
name: "missing project",
isNew: true,
wantError: false,
before: map[string]interface{}{},
after: map[string]interface{}{
"account_id": accountId,
"project": "",
},
result: map[string]interface{}{
"account_id": accountId,
"project": "",
},
},
}
for _, tc := range cases {
tn := tc.name
tc.after["name"] = "whatever"
if tc.isNew {
tc.after["name"] = ""
tn = tc.name + " new"
}
tc.result["name"] = tc.after["name"]
mikesmitty marked this conversation as resolved.
Show resolved Hide resolved
t.Run(tn, func(t *testing.T) {
diff := &tpgresource.ResourceDiffMock{
Before: tc.before,
After: tc.after,
Schema: tpgresourcemanager.ResourceGoogleServiceAccount().Schema,
}
err := tpgresourcemanager.ResourceServiceAccountCustomDiffFunc(diff)
if tc.wantError && err == nil {
t.Fatalf("want error, got nil")
}
if !tc.wantError && err != nil {
t.Fatalf("got unexpected error: %v", err)
}
if !maps.Equal(tc.result, diff.After) {
t.Fatalf("got unexpected change: %v expected: %v", diff.After, tc.result)
}
mikesmitty marked this conversation as resolved.
Show resolved Hide resolved
})
}
}
27 changes: 27 additions & 0 deletions mmv1/third_party/terraform/tpgresource/resource_test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ type ResourceDiffMock struct {
Before map[string]interface{}
After map[string]interface{}
Cleared map[string]interface{}
Schema map[string]*schema.Schema
IsForceNew bool
}

Expand Down Expand Up @@ -113,6 +114,32 @@ func (d *ResourceDiffMock) ForceNew(key string) error {
return nil
}

func (d *ResourceDiffMock) SetNew(key string, value interface{}) error {
if len(d.Schema) > 0 {
if err := d.checkKey(key, "SetNew"); err != nil {
return err
}
}

d.After[key] = value
return nil
}

func (d *ResourceDiffMock) checkKey(key, caller string) error {
var schema *schema.Schema
s, ok := d.Schema[key]
if ok {
schema = s
}
if schema == nil {
return fmt.Errorf("%s: invalid key: %s", caller, key)
}
if !schema.Computed {
return fmt.Errorf("%s only operates on computed keys - %s is not one", caller, key)
}
return nil
}

// This function isn't a test of transport.go; instead, it is used as an alternative
// to ReplaceVars inside tests.
func ReplaceVarsForTest(config *transport_tpg.Config, rs *terraform.ResourceState, linkTmpl string) (string, error) {
Expand Down
1 change: 1 addition & 0 deletions mmv1/third_party/terraform/tpgresource/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type TerraformResourceDiff interface {
GetOk(string) (interface{}, bool)
Clear(string) error
ForceNew(string) error
SetNew(string, interface{}) error
}

// Contains functions that don't really belong anywhere else.
Expand Down
8 changes: 8 additions & 0 deletions mmv1/third_party/terraform/transport/config.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -1352,3 +1352,11 @@ func GetRegionFromRegionSelfLink(selfLink string) string {
}
return selfLink
}

func GetUniverseDomainFromMeta(meta interface{}) string {
config := meta.(*Config)
if config.UniverseDomain != "" && config.UniverseDomain != "googleapis.com" {
mikesmitty marked this conversation as resolved.
Show resolved Hide resolved
return "googleapis.com"
mikesmitty marked this conversation as resolved.
Show resolved Hide resolved
}
return config.UniverseDomain
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
"oauthScopes": [
"https://www.googleapis.com/auth/cloud-platform"
],
"preemptible": true
"preemptible": true,
"serviceAccount": "service-account-cc@{{.Provider.project}}.iam.gserviceaccount.com"
},
"location": "us-central1",
"management": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{
"role": "roles/composer.worker",
"members": [
""
"serviceAccount:composer-new-account@{{.Provider.project}}.iam.gserviceaccount.com"
]
}
]
Expand Down
Loading