Skip to content

Commit

Permalink
Set google_service_account IAM-related fields during plan stage (#11929)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikesmitty authored Nov 27, 2024
1 parent 4bffa3e commit 1e21e33
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 2 deletions.
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,16 @@ package resourcemanager_test

import (
"fmt"
"maps"
"testing"

"github.com/google/go-cmp/cmp"
"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 +303,107 @@ 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
before map[string]interface{}
after map[string]interface{}
wantEmail string
wantMember string
}{
{
name: "normal (new)",
before: map[string]interface{}{},
after: map[string]interface{}{
"account_id": accountId,
"name": "", // Empty name indicates a new resource
"project": project,
},
wantEmail: expectedEmail,
wantMember: expectedMember,
},
{
name: "no change",
before: map[string]interface{}{
"account_id": accountId,
"email": "dontchange",
"member": "dontchange",
"project": project,
},
after: map[string]interface{}{
"account_id": accountId,
"name": "unimportant",
"project": project,
},
wantEmail: "",
wantMember: "",
},
{
name: "recreate (new)",
before: map[string]interface{}{
"account_id": "recreate-account",
"email": "recreate-email",
"member": "recreate-member",
"project": project,
},
after: map[string]interface{}{
"account_id": accountId,
"name": "",
"project": project,
},
wantEmail: expectedEmail,
wantMember: expectedMember,
},
{
name: "missing account_id (new)",
before: map[string]interface{}{},
after: map[string]interface{}{
"account_id": "",
"name": "",
"project": project,
},
wantEmail: "",
wantMember: "",
},
{
name: "missing project (new)",
before: map[string]interface{}{},
after: map[string]interface{}{
"account_id": accountId,
"name": "",
"project": "",
},
wantEmail: "",
wantMember: "",
},
}
for _, tc := range cases {
result := maps.Clone(tc.after)
if tc.wantEmail != "" || tc.wantMember != "" {
result["email"] = tc.wantEmail
result["member"] = tc.wantMember
}
t.Run(tc.name, func(t *testing.T) {
diff := &tpgresource.ResourceDiffMock{
Before: tc.before,
After: tc.after,
Schema: tpgresourcemanager.ResourceGoogleServiceAccount().Schema,
}
tpgresourcemanager.ResourceServiceAccountCustomDiffFunc(diff)
if d := cmp.Diff(result, diff.After); d != "" {
t.Fatalf("got unexpected change: %v expected: %v", diff.After, result)
}
})
}
}
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 @@ -1372,3 +1372,11 @@ func GetRegionFromRegionSelfLink(selfLink string) string {
}
return selfLink
}

func GetUniverseDomainFromMeta(meta interface{}) string {
config := meta.(*Config)
if config.UniverseDomain == "" {
return "googleapis.com"
}
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

0 comments on commit 1e21e33

Please sign in to comment.