diff --git a/config/tests/samples/create/harness.go b/config/tests/samples/create/harness.go index 0b765aa82b..7c1ce6bd3a 100644 --- a/config/tests/samples/create/harness.go +++ b/config/tests/samples/create/harness.go @@ -31,6 +31,9 @@ import ( "golang.org/x/oauth2/google" cloudresourcemanagerv1 "google.golang.org/api/cloudresourcemanager/v1" "google.golang.org/api/option" + "google.golang.org/grpc" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" "gopkg.in/dnaeon/go-vcr.v3/recorder" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -264,11 +267,14 @@ func NewHarnessWithOptions(ctx context.Context, t *testing.T, opts *HarnessOptio } } + var mockCloudGRPCClientConnection *grpc.ClientConn if targetGCP := os.Getenv("E2E_GCP_TARGET"); targetGCP == "mock" { t.Logf("creating mock gcp") mockCloud := mockgcp.NewMockRoundTripper(t, h.client, storage.NewInMemoryStorage()) + mockCloudGRPCClientConnection = mockCloud.NewGRPCConnection(ctx) + roundTripper := http.RoundTripper(mockCloud) ctx = context.WithValue(ctx, httpRoundTripperKey, roundTripper) @@ -448,6 +454,42 @@ func NewHarnessWithOptions(ctx context.Context, t *testing.T, opts *HarnessOptio return ret } } else { + // Intercept (and log) GRPC requests + grpcUnaryInterceptor := func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + entry := &test.LogEntry{} + + entry.Request.URL = method + entry.Request.Method = "GRPC" + + if req != nil { + requestBytes, _ := protojson.Marshal(req.(proto.Message)) + entry.Request.Body = string(requestBytes) + } + + if mockCloudGRPCClientConnection != nil { + cc = mockCloudGRPCClientConnection + } + err := invoker(ctx, method, req, reply, cc, opts...) + + if reply != nil { + replyBytes, _ := protojson.Marshal(reply.(proto.Message)) + entry.Response.Body = string(replyBytes) + } + + if err != nil { + entry.Response.Status = fmt.Sprintf("error: %v", err) + } else { + entry.Response.Status = "OK" + } + + for _, eventSink := range eventSinks { + eventSink.AddHTTPEvent(ctx, entry) + } + return err + } + + transport_tpg.GRPCUnaryClientInterceptor = grpcUnaryInterceptor + // Intercept (and log) DCL requests if len(eventSinks) != 0 { if kccConfig.HTTPClient == nil { diff --git a/go.mod b/go.mod index d795b4e2e3..0df3eb2db1 100644 --- a/go.mod +++ b/go.mod @@ -49,6 +49,7 @@ require ( golang.org/x/sync v0.7.0 golang.org/x/time v0.5.0 google.golang.org/api v0.177.0 + google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.34.0 gopkg.in/dnaeon/go-vcr.v3 v3.2.0 gopkg.in/yaml.v2 v2.4.0 @@ -212,7 +213,6 @@ require ( google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect - google.golang.org/grpc v1.63.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/mockgcp/common/operations/operations.go b/mockgcp/common/operations/operations.go index 1bb815609f..8039b940a0 100644 --- a/mockgcp/common/operations/operations.go +++ b/mockgcp/common/operations/operations.go @@ -22,6 +22,7 @@ import ( pb "google.golang.org/genproto/googleapis/longrunning" rpcstatus "google.golang.org/genproto/googleapis/rpc/status" + "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" @@ -44,6 +45,10 @@ func NewOperationsService(storage storage.Storage) *Operations { } } +func (s *Operations) RegisterGRPCServices(grpcServer *grpc.Server) { + pb.RegisterOperationsServer(grpcServer, s) +} + func (s *Operations) NewLRO(ctx context.Context) (*pb.Operation, error) { now := time.Now() millis := now.UnixMilli() diff --git a/mockgcp/mock_http_roundtrip.go b/mockgcp/mock_http_roundtrip.go index 59263d508e..88b8575407 100644 --- a/mockgcp/mock_http_roundtrip.go +++ b/mockgcp/mock_http_roundtrip.go @@ -28,6 +28,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/GoogleCloudPlatform/k8s-config-connector/mockgcp/common" @@ -187,6 +188,18 @@ func NewMockRoundTripper(t *testing.T, k8sClient client.Client, storage storage. return mockRoundTripper } +func (m *mockRoundTripper) NewGRPCConnection(ctx context.Context) *grpc.ClientConn { + endpoint := m.grpcListener.Addr().String() + + var opts []grpc.DialOption + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + conn, err := grpc.DialContext(ctx, endpoint, opts...) + if err != nil { + klog.Fatalf("error dialing grpc endpoint %q: %v", endpoint, err) + } + return conn +} + func (m *mockRoundTripper) prefilterRequest(req *http.Request) error { if req.Body != nil { var requestBody bytes.Buffer diff --git a/pkg/test/resourcefixture/testdata/basic/bigtable/v1beta1/bigtabletable/_generated_object_bigtabletable.golden.yaml b/pkg/test/resourcefixture/testdata/basic/bigtable/v1beta1/bigtabletable/_generated_object_bigtabletable.golden.yaml new file mode 100644 index 0000000000..6edde25012 --- /dev/null +++ b/pkg/test/resourcefixture/testdata/basic/bigtable/v1beta1/bigtabletable/_generated_object_bigtabletable.golden.yaml @@ -0,0 +1,47 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: bigtable.cnrm.cloud.google.com/v1beta1 +kind: BigtableTable +metadata: + annotations: + cnrm.cloud.google.com/management-conflict-prevention-policy: none + cnrm.cloud.google.com/project-id: ${projectId} + cnrm.cloud.google.com/state-into-spec: merge + finalizers: + - cnrm.cloud.google.com/finalizer + - cnrm.cloud.google.com/deletion-defender + generation: 3 + labels: + cnrm-test: "true" + name: bigtabletable-${uniqueId} + namespace: ${uniqueId} +spec: + columnFamily: + - family: family3 + - family: family2 + deletionProtection: UNPROTECTED + instanceRef: + name: bigtable-dep-${uniqueId} + resourceID: bigtabletable-${uniqueId} + splitKeys: + - a +status: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: The resource is up to date + reason: UpToDate + status: "True" + type: Ready + observedGeneration: 3 diff --git a/pkg/test/resourcefixture/testdata/basic/bigtable/v1beta1/bigtabletable/_http.log b/pkg/test/resourcefixture/testdata/basic/bigtable/v1beta1/bigtabletable/_http.log new file mode 100644 index 0000000000..b42a1370df --- /dev/null +++ b/pkg/test/resourcefixture/testdata/basic/bigtable/v1beta1/bigtabletable/_http.log @@ -0,0 +1,521 @@ +GRPC /google.bigtable.admin.v2.BigtableInstanceAdmin/ListInstances + +{ + "parent": "projects/${projectId}" +} + +OK + +{} + +--- + +GRPC /google.bigtable.admin.v2.BigtableInstanceAdmin/CreateInstance + +{ + "clusters": { + "cluster1-${uniqueId}": { + "defaultStorageType": "SSD", + "encryptionConfig": {}, + "location": "projects/${projectId}/locations/us-east1-b", + "serveNodes": 3 + }, + "cluster2-${uniqueId}": { + "defaultStorageType": "SSD", + "encryptionConfig": {}, + "location": "projects/${projectId}/locations/us-west1-a", + "serveNodes": 3 + } + }, + "instance": { + "displayName": "BigtableSample", + "labels": { + "cnrm-test": "true", + "managed-by-cnrm": "true" + }, + "type": "PRODUCTION" + }, + "instanceId": "bigtable-dep-${uniqueId}", + "parent": "projects/${projectId}" +} + +OK + +{ + "metadata": { + "@type": "type.googleapis.com/google.bigtable.admin.v2.CreateInstanceMetadata", + "originalRequest": { + "clusters": { + "cluster1-${uniqueId}": { + "defaultStorageType": "SSD", + "location": "projects/${projectId}/locations/us-east1-b", + "serveNodes": 3 + }, + "cluster2-${uniqueId}": { + "defaultStorageType": "SSD", + "location": "projects/${projectId}/locations/us-west1-a", + "serveNodes": 3 + } + }, + "instance": { + "displayName": "BigtableSample", + "labels": { + "cnrm-test": "true", + "managed-by-cnrm": "true" + }, + "type": "PRODUCTION" + }, + "instanceId": "bigtable-dep-${uniqueId}", + "parent": "projects/${projectId}" + }, + "requestTime": "9999-12-31T23:59:59.999999999Z" + }, + "name": "operations/projects/${projectId}/instances/bigtable-dep-${uniqueId}/locations/us-west1-a/operations/1494670242314888474" +} + +--- + +GRPC /google.longrunning.Operations/GetOperation + +{ + "name": "operations/projects/${projectId}/instances/bigtable-dep-${uniqueId}/locations/us-west1-a/operations/1494670242314888474" +} + +OK + +{ + "done": true, + "metadata": { + "@type": "type.googleapis.com/google.bigtable.admin.v2.CreateInstanceMetadata", + "finishTime": "2024-04-26T15:32:45.058427Z", + "originalRequest": { + "clusters": { + "cluster1-${uniqueId}": { + "defaultStorageType": "SSD", + "location": "projects/${projectId}/locations/us-east1-b", + "serveNodes": 3 + }, + "cluster2-${uniqueId}": { + "defaultStorageType": "SSD", + "location": "projects/${projectId}/locations/us-west1-a", + "serveNodes": 3 + } + }, + "instance": { + "displayName": "BigtableSample", + "labels": { + "cnrm-test": "true", + "managed-by-cnrm": "true" + }, + "type": "PRODUCTION" + }, + "instanceId": "bigtable-dep-${uniqueId}", + "parent": "projects/${projectId}" + }, + "requestTime": "2024-04-26T15:32:42.198085Z" + }, + "name": "operations/projects/${projectId}/instances/bigtable-dep-${uniqueId}/locations/us-west1-a/operations/1494670242314888474", + "response": { + "@type": "type.googleapis.com/google.bigtable.admin.v2.Instance", + "displayName": "BigtableSample", + "labels": { + "cnrm-test": "true", + "managed-by-cnrm": "true" + }, + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}", + "state": "READY", + "type": "PRODUCTION" + } +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableInstanceAdmin/ListInstances + +{ + "parent": "projects/${projectId}" +} + +OK + +{ + "instances": [ + { + "createTime": "2024-04-26T15:32:42.198085Z", + "displayName": "BigtableSample", + "labels": { + "cnrm-test": "true", + "managed-by-cnrm": "true" + }, + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}", + "state": "READY", + "type": "PRODUCTION" + } + ] +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableInstanceAdmin/ListClusters + +{ + "parent": "projects/${projectId}/instances/bigtable-dep-${uniqueId}" +} + +OK + +{ + "clusters": [ + { + "defaultStorageType": "SSD", + "location": "projects/${projectId}/locations/us-west1-a", + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/clusters/cluster2-${uniqueId}", + "serveNodes": 3, + "state": "READY" + }, + { + "defaultStorageType": "SSD", + "location": "projects/${projectId}/locations/us-east1-b", + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/clusters/cluster1-${uniqueId}", + "serveNodes": 3, + "state": "READY" + } + ] +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableInstanceAdmin/ListInstances + +{ + "parent": "projects/${projectId}" +} + +OK + +{ + "instances": [ + { + "createTime": "2024-04-26T15:32:42.198085Z", + "displayName": "BigtableSample", + "labels": { + "cnrm-test": "true", + "managed-by-cnrm": "true" + }, + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}", + "state": "READY", + "type": "PRODUCTION" + } + ] +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableInstanceAdmin/ListClusters + +{ + "parent": "projects/${projectId}/instances/bigtable-dep-${uniqueId}" +} + +OK + +{ + "clusters": [ + { + "defaultStorageType": "SSD", + "location": "projects/${projectId}/locations/us-west1-a", + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/clusters/cluster2-${uniqueId}", + "serveNodes": 3, + "state": "READY" + }, + { + "defaultStorageType": "SSD", + "location": "projects/${projectId}/locations/us-east1-b", + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/clusters/cluster1-${uniqueId}", + "serveNodes": 3, + "state": "READY" + } + ] +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableTableAdmin/GetTable + +{ + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}", + "view": "SCHEMA_VIEW" +} + +error: rpc error: code = NotFound desc = Table not found: projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId} + +{} + +--- + +GRPC /google.bigtable.admin.v2.BigtableTableAdmin/CreateTable + +{ + "initialSplits": [ + { + "key": "YQ==" + } + ], + "parent": "projects/${projectId}/instances/bigtable-dep-${uniqueId}", + "table": { + "columnFamilies": { + "family1": { + "gcRule": {} + }, + "family2": { + "gcRule": {} + } + } + }, + "tableId": "bigtabletable-${uniqueId}" +} + +OK + +{ + "columnFamilies": { + "family1": { + "gcRule": {} + }, + "family2": { + "gcRule": {} + } + }, + "granularity": "MILLIS", + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}" +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableTableAdmin/GetTable + +{ + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}", + "view": "SCHEMA_VIEW" +} + +OK + +{ + "columnFamilies": { + "family1": {}, + "family2": {} + }, + "granularity": "MILLIS", + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}" +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableTableAdmin/GetTable + +{ + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}", + "view": "SCHEMA_VIEW" +} + +OK + +{ + "columnFamilies": { + "family1": {}, + "family2": {} + }, + "granularity": "MILLIS", + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}" +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableTableAdmin/GetTable + +{ + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}", + "view": "SCHEMA_VIEW" +} + +OK + +{ + "columnFamilies": { + "family1": {}, + "family2": {} + }, + "granularity": "MILLIS", + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}" +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableTableAdmin/ModifyColumnFamilies + +{ + "modifications": [ + { + "create": {}, + "id": "family3" + } + ], + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}" +} + +OK + +{ + "columnFamilies": { + "family1": {}, + "family2": {}, + "family3": {} + }, + "granularity": "MILLIS", + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}" +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableTableAdmin/ModifyColumnFamilies + +{ + "modifications": [ + { + "drop": true, + "id": "family1" + } + ], + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}" +} + +OK + +{ + "columnFamilies": { + "family2": {}, + "family3": {} + }, + "granularity": "MILLIS", + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}" +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableTableAdmin/GetTable + +{ + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}", + "view": "SCHEMA_VIEW" +} + +OK + +{ + "columnFamilies": { + "family2": {}, + "family3": {} + }, + "granularity": "MILLIS", + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}" +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableTableAdmin/GetTable + +{ + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}", + "view": "SCHEMA_VIEW" +} + +OK + +{ + "columnFamilies": { + "family2": {}, + "family3": {} + }, + "granularity": "MILLIS", + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}" +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableTableAdmin/DeleteTable + +{ + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/tables/bigtabletable-${uniqueId}" +} + +OK + +{} + +--- + +GRPC /google.bigtable.admin.v2.BigtableInstanceAdmin/ListInstances + +{ + "parent": "projects/${projectId}" +} + +OK + +{ + "instances": [ + { + "createTime": "2024-04-26T15:32:42.198085Z", + "displayName": "BigtableSample", + "labels": { + "cnrm-test": "true", + "managed-by-cnrm": "true" + }, + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}", + "state": "READY", + "type": "PRODUCTION" + } + ] +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableInstanceAdmin/ListClusters + +{ + "parent": "projects/${projectId}/instances/bigtable-dep-${uniqueId}" +} + +OK + +{ + "clusters": [ + { + "defaultStorageType": "SSD", + "location": "projects/${projectId}/locations/us-west1-a", + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/clusters/cluster2-${uniqueId}", + "serveNodes": 3, + "state": "READY" + }, + { + "defaultStorageType": "SSD", + "location": "projects/${projectId}/locations/us-east1-b", + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}/clusters/cluster1-${uniqueId}", + "serveNodes": 3, + "state": "READY" + } + ] +} + +--- + +GRPC /google.bigtable.admin.v2.BigtableInstanceAdmin/DeleteInstance + +{ + "name": "projects/${projectId}/instances/bigtable-dep-${uniqueId}" +} + +OK + +{} \ No newline at end of file diff --git a/third_party/github.com/hashicorp/terraform-provider-google-beta/google-beta/transport/bigtable_client_factory.go b/third_party/github.com/hashicorp/terraform-provider-google-beta/google-beta/transport/bigtable_client_factory.go index 48e320e51e..c38828d669 100644 --- a/third_party/github.com/hashicorp/terraform-provider-google-beta/google-beta/transport/bigtable_client_factory.go +++ b/third_party/github.com/hashicorp/terraform-provider-google-beta/google-beta/transport/bigtable_client_factory.go @@ -9,6 +9,7 @@ import ( "cloud.google.com/go/bigtable" "golang.org/x/oauth2" "google.golang.org/api/option" + "google.golang.org/grpc" ) type BigtableClientFactory struct { @@ -32,6 +33,10 @@ func (s BigtableClientFactory) NewInstanceAdminClient(project string) (*bigtable opts = append(opts, option.WithTokenSource(s.TokenSource), option.WithUserAgent(s.UserAgent)) opts = append(opts, s.gRPCLoggingOptions...) + if GRPCUnaryClientInterceptor != nil { + opts = append(opts, option.WithGRPCDialOption(grpc.WithUnaryInterceptor(GRPCUnaryClientInterceptor))) + } + return bigtable.NewInstanceAdminClient(context.Background(), project, opts...) } @@ -48,6 +53,10 @@ func (s BigtableClientFactory) NewAdminClient(project, instance string) (*bigtab opts = append(opts, option.WithTokenSource(s.TokenSource), option.WithUserAgent(s.UserAgent)) opts = append(opts, s.gRPCLoggingOptions...) + if GRPCUnaryClientInterceptor != nil { + opts = append(opts, option.WithGRPCDialOption(grpc.WithUnaryInterceptor(GRPCUnaryClientInterceptor))) + } + return bigtable.NewAdminClient(context.Background(), project, instance, opts...) } @@ -64,5 +73,9 @@ func (s BigtableClientFactory) NewClient(project, instance string) (*bigtable.Cl opts = append(opts, option.WithTokenSource(s.TokenSource), option.WithUserAgent(s.UserAgent)) opts = append(opts, s.gRPCLoggingOptions...) + if GRPCUnaryClientInterceptor != nil { + opts = append(opts, option.WithGRPCDialOption(grpc.WithUnaryInterceptor(GRPCUnaryClientInterceptor))) + } + return bigtable.NewClient(context.Background(), project, instance, opts...) } diff --git a/third_party/github.com/hashicorp/terraform-provider-google-beta/google-beta/transport/config.go b/third_party/github.com/hashicorp/terraform-provider-google-beta/google-beta/transport/config.go index cf5ccc2d76..591b0fd383 100644 --- a/third_party/github.com/hashicorp/terraform-provider-google-beta/google-beta/transport/config.go +++ b/third_party/github.com/hashicorp/terraform-provider-google-beta/google-beta/transport/config.go @@ -596,6 +596,8 @@ var DefaultHTTPClientTransformer func(ctx context.Context, inner *http.Client) * // This is very handy in tests, for example. var OAuth2HTTPClientTransformer func(ctx context.Context, inner *http.Client) *http.Client = nil +var GRPCUnaryClientInterceptor grpc.UnaryClientInterceptor + func HandleSDKDefaults(d *schema.ResourceData) error { if d.Get("impersonate_service_account") == "" { d.Set("impersonate_service_account", MultiEnvDefault([]string{