Skip to content

Commit f6729db

Browse files
authored
Add ephemeral google_client_config resource (#15099)
1 parent 1e1ffed commit f6729db

File tree

7 files changed

+383
-15
lines changed

7 files changed

+383
-15
lines changed

mmv1/third_party/terraform/fwprovider/data_source_provider_config_plugin_framework.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,7 @@ func (d *GoogleProviderConfigPluginFrameworkDataSource) Read(ctx context.Context
230230
data.RequestReason = types.StringValue(d.providerConfig.RequestReason)
231231
data.RequestTimeout = types.StringValue(d.providerConfig.RequestTimeout.String())
232232

233-
lbs := make(map[string]attr.Value, len(d.providerConfig.DefaultLabels))
234-
for k, v := range d.providerConfig.DefaultLabels {
235-
lbs[k] = types.StringValue(v)
236-
}
237-
labels, di := types.MapValueFrom(ctx, types.StringType, lbs)
233+
labels, di := types.MapValueFrom(ctx, types.StringType, d.providerConfig.DefaultLabels)
238234
if di.HasError() {
239235
resp.Diagnostics.Append(di...)
240236
}

mmv1/third_party/terraform/fwprovider/framework_provider.go.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ func (p *FrameworkProvider) Functions(_ context.Context) []func() function.Funct
380380
// EphemeralResources defines the resources that are of ephemeral type implemented in the provider.
381381
func (p *FrameworkProvider) EphemeralResources(_ context.Context) []func() ephemeral.EphemeralResource {
382382
return []func() ephemeral.EphemeralResource{
383+
resourcemanager.GoogleEphemeralClientConfig,
383384
resourcemanager.GoogleEphemeralServiceAccountAccessToken,
384385
resourcemanager.GoogleEphemeralServiceAccountIdToken,
385386
resourcemanager.GoogleEphemeralServiceAccountJwt,

mmv1/third_party/terraform/services/resourcemanager/data_source_google_client_config.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,7 @@ func (d *GoogleClientConfigDataSource) Read(ctx context.Context, req datasource.
123123
data.Region = types.StringValue(d.providerConfig.Region)
124124
data.Zone = types.StringValue(d.providerConfig.Zone)
125125

126-
// Convert default labels from SDK type system to plugin-framework data type
127-
m := map[string]*string{}
128-
for k, v := range d.providerConfig.DefaultLabels {
129-
// m[k] = types.StringValue(v)
130-
val := v
131-
m[k] = &val
132-
}
133-
dls, diags := types.MapValueFrom(ctx, types.StringType, m)
126+
dls, diags := types.MapValueFrom(ctx, types.StringType, d.providerConfig.DefaultLabels)
134127
resp.Diagnostics.Append(diags...)
135128
if resp.Diagnostics.HasError() {
136129
return
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package resourcemanager
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
8+
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
9+
"github.com/hashicorp/terraform-plugin-framework/types"
10+
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
11+
)
12+
13+
// Ensure the ephemeral resource satisfies the expected interfaces.
14+
var (
15+
_ ephemeral.EphemeralResource = &GoogleClientConfigEphemeralResource{}
16+
_ ephemeral.EphemeralResourceWithConfigure = &GoogleClientConfigEphemeralResource{}
17+
)
18+
19+
func GoogleEphemeralClientConfig() ephemeral.EphemeralResource {
20+
return &GoogleClientConfigEphemeralResource{}
21+
}
22+
23+
type GoogleClientConfigEphemeralResource struct {
24+
providerConfig *transport_tpg.Config
25+
}
26+
27+
type GoogleClientConfigEphemeralModel struct {
28+
// Id could/should be removed in future as it's not necessary in the plugin framework
29+
// https://github.com/hashicorp/terraform-plugin-testing/issues/84
30+
Id types.String `tfsdk:"id"`
31+
Project types.String `tfsdk:"project"`
32+
Region types.String `tfsdk:"region"`
33+
Zone types.String `tfsdk:"zone"`
34+
AccessToken types.String `tfsdk:"access_token"`
35+
DefaultLabels types.Map `tfsdk:"default_labels"`
36+
}
37+
38+
func (e *GoogleClientConfigEphemeralResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) {
39+
resp.TypeName = req.ProviderTypeName + "_client_config"
40+
}
41+
42+
func (e *GoogleClientConfigEphemeralResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
43+
44+
resp.Schema = schema.Schema{
45+
46+
Description: "Use this ephemeral resource to access the configuration of the Google Cloud provider.",
47+
MarkdownDescription: "Use this ephemeral resource to access the configuration of the Google Cloud provider.",
48+
Attributes: map[string]schema.Attribute{
49+
"id": schema.StringAttribute{
50+
Computed: true,
51+
Description: "The ID of this ephemeral resource in Terraform state. It is created in a projects/{{project}}/regions/{{region}}/zones/{{zone}} format and is NOT used by the ephemeral resource in requests to Google APIs.",
52+
MarkdownDescription: "The ID of this ephemeral resource in Terraform state. It is created in a projects/{{project}}/regions/{{region}}/zones/{{zone}} format and is NOT used by the ephemeral resource in requests to Google APIs.",
53+
},
54+
"project": schema.StringAttribute{
55+
Description: "The ID of the project to apply any resources to.",
56+
MarkdownDescription: "The ID of the project to apply any resources to.",
57+
Computed: true,
58+
},
59+
"region": schema.StringAttribute{
60+
Description: "The region to operate under.",
61+
MarkdownDescription: "The region to operate under.",
62+
Computed: true,
63+
},
64+
"zone": schema.StringAttribute{
65+
Description: "The zone to operate under.",
66+
MarkdownDescription: "The zone to operate under.",
67+
Computed: true,
68+
},
69+
"access_token": schema.StringAttribute{
70+
Description: "The OAuth2 access token used by the client to authenticate against the Google Cloud API.",
71+
MarkdownDescription: "The OAuth2 access token used by the client to authenticate against the Google Cloud API.",
72+
Computed: true,
73+
Sensitive: true,
74+
},
75+
"default_labels": schema.MapAttribute{
76+
Description: "The default labels configured on the provider.",
77+
MarkdownDescription: "The default labels configured on the provider.",
78+
Computed: true,
79+
ElementType: types.StringType,
80+
},
81+
},
82+
}
83+
}
84+
85+
func (e *GoogleClientConfigEphemeralResource) Configure(ctx context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) {
86+
// Prevent panic if the provider has not been configured.
87+
if req.ProviderData == nil {
88+
return
89+
}
90+
91+
p, ok := req.ProviderData.(*transport_tpg.Config)
92+
if !ok {
93+
resp.Diagnostics.AddError(
94+
"Unexpected Ephemeral Resource Configure Type",
95+
fmt.Sprintf("Expected *transport_tpg.Config, got: %T. Please report this issue to the provider developers.", req.ProviderData),
96+
)
97+
return
98+
}
99+
100+
// Required for accessing project, region, zone and tokenSource
101+
e.providerConfig = p
102+
}
103+
104+
func (e *GoogleClientConfigEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
105+
var data GoogleClientConfigEphemeralModel
106+
107+
// Read Terraform configuration data into the model
108+
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
109+
if resp.Diagnostics.HasError() {
110+
return
111+
}
112+
113+
data.Id = types.StringValue(fmt.Sprintf("projects/%s/regions/%s/zones/%s", e.providerConfig.Project, e.providerConfig.Region, e.providerConfig.Zone))
114+
data.Project = types.StringValue(e.providerConfig.Project)
115+
data.Region = types.StringValue(e.providerConfig.Region)
116+
data.Zone = types.StringValue(e.providerConfig.Zone)
117+
118+
dls, diags := types.MapValueFrom(ctx, types.StringType, e.providerConfig.DefaultLabels)
119+
resp.Diagnostics.Append(diags...)
120+
if resp.Diagnostics.HasError() {
121+
return
122+
}
123+
124+
data.DefaultLabels = dls
125+
126+
token, err := e.providerConfig.TokenSource.Token()
127+
if err != nil {
128+
resp.Diagnostics.AddError("Error setting access_token", err.Error())
129+
return
130+
}
131+
data.AccessToken = types.StringValue(token.AccessToken)
132+
133+
// Save data into ephemeral resource result
134+
resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...)
135+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package resourcemanager_test
2+
3+
import (
4+
"regexp"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
8+
"github.com/hashicorp/terraform-provider-google/google/acctest"
9+
)
10+
11+
func TestAccEphemeralGoogleClientConfig_basic(t *testing.T) {
12+
t.Parallel()
13+
14+
resource.Test(t, resource.TestCase{
15+
PreCheck: func() { acctest.AccTestPreCheck(t) },
16+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
17+
Steps: []resource.TestStep{
18+
{
19+
// Note: For ephemeral resources, we can't directly check attributes
20+
// since they don't persist in state. Instead, we verify the configuration
21+
// compiles and runs without error.
22+
Config: testAccCheckEphemeralGoogleClientConfig_basic,
23+
},
24+
},
25+
})
26+
}
27+
28+
func TestAccEphemeralGoogleClientConfig_omitLocation(t *testing.T) {
29+
t.Setenv("GOOGLE_REGION", "")
30+
t.Setenv("GOOGLE_ZONE", "")
31+
32+
resource.Test(t, resource.TestCase{
33+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
34+
Steps: []resource.TestStep{
35+
{
36+
// Note: For ephemeral resources, we can't directly check attributes
37+
// since they don't persist in state. Instead, we verify the configuration
38+
// compiles and runs without error.
39+
Config: testAccCheckEphemeralGoogleClientConfig_basic,
40+
},
41+
},
42+
})
43+
}
44+
45+
func TestAccEphemeralGoogleClientConfig_invalidCredentials(t *testing.T) {
46+
badCreds := acctest.GenerateFakeCredentialsJson("test")
47+
t.Setenv("GOOGLE_CREDENTIALS", badCreds)
48+
49+
resource.Test(t, resource.TestCase{
50+
PreCheck: func() { acctest.AccTestPreCheck(t) },
51+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
52+
Steps: []resource.TestStep{
53+
{
54+
Config: testAccCheckEphemeralGoogleClientConfig_basic,
55+
ExpectError: regexp.MustCompile("Error setting access_token"),
56+
},
57+
},
58+
})
59+
}
60+
61+
func TestAccEphemeralGoogleClientConfig_usedInProvider(t *testing.T) {
62+
t.Parallel()
63+
64+
resource.Test(t, resource.TestCase{
65+
PreCheck: func() { acctest.AccTestPreCheck(t) },
66+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
67+
Steps: []resource.TestStep{
68+
{
69+
Config: testAccCheckEphemeralGoogleClientConfig_usedInProvider,
70+
Check: resource.ComposeTestCheckFunc(
71+
// Verify that the ephemeral resource can be used to configure a provider
72+
// and that the provider works correctly
73+
resource.TestCheckResourceAttrSet("data.google_client_openid_userinfo.me", "email"),
74+
),
75+
},
76+
},
77+
})
78+
}
79+
80+
const testAccCheckEphemeralGoogleClientConfig_basic = `
81+
provider "google" {
82+
default_labels = {
83+
default_key = "default_value"
84+
}
85+
}
86+
87+
ephemeral "google_client_config" "current" { }
88+
`
89+
90+
const testAccCheckEphemeralGoogleClientConfig_usedInProvider = `
91+
provider "google" {
92+
default_labels = {
93+
default_key = "default_value"
94+
}
95+
}
96+
97+
ephemeral "google_client_config" "current" { }
98+
99+
provider "google" {
100+
alias = "ephemeral_configured"
101+
access_token = ephemeral.google_client_config.current.access_token
102+
project = ephemeral.google_client_config.current.project
103+
region = ephemeral.google_client_config.current.region
104+
zone = ephemeral.google_client_config.current.zone
105+
}
106+
107+
data "google_client_openid_userinfo" "me" {
108+
provider = google.ephemeral_configured
109+
}
110+
`
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
subcategory: "Cloud Platform"
3+
description: |-
4+
Get information about the configuration of the Google Cloud provider.
5+
---
6+
7+
# google_client_config
8+
9+
Use this ephemeral resource to access the configuration of the Google Cloud provider.
10+
11+
Unlike the `google_client_config` data source, this ephemeral resource does not persist sensitive credentials in Terraform's state, making it ideal for scenarios where you need to access provider configuration without storing sensitive data.
12+
13+
## Example Usage
14+
15+
```tf
16+
ephemeral "google_client_config" "current" {
17+
}
18+
19+
output "project" {
20+
value = ephemeral.google_client_config.current.project
21+
}
22+
```
23+
24+
## Example Usage: Configure provider with ephemeral credentials
25+
26+
```tf
27+
provider "google" {
28+
default_labels = {
29+
environment = "production"
30+
}
31+
}
32+
33+
ephemeral "google_client_config" "current" {
34+
}
35+
36+
provider "google" {
37+
alias = "ephemeral_configured"
38+
access_token = ephemeral.google_client_config.current.access_token
39+
project = ephemeral.google_client_config.current.project
40+
region = ephemeral.google_client_config.current.region
41+
zone = ephemeral.google_client_config.current.zone
42+
}
43+
44+
data "google_client_openid_userinfo" "me" {
45+
provider = google.ephemeral_configured
46+
}
47+
```
48+
49+
## Example Usage: Configure Kubernetes provider with ephemeral OAuth2 access token
50+
51+
```tf
52+
ephemeral "google_client_config" "default" {
53+
}
54+
55+
data "google_container_cluster" "my_cluster" {
56+
name = "my-cluster"
57+
zone = "us-east1-a"
58+
}
59+
60+
provider "kubernetes" {
61+
host = "https://${data.google_container_cluster.my_cluster.endpoint}"
62+
token = ephemeral.google_client_config.default.access_token
63+
cluster_ca_certificate = base64decode(
64+
data.google_container_cluster.my_cluster.master_auth[0].cluster_ca_certificate,
65+
)
66+
}
67+
```
68+
69+
## Argument Reference
70+
71+
There are no arguments available for this ephemeral resource.
72+
73+
## Attributes Reference
74+
75+
The following attributes are exported:
76+
77+
* `project` - The ID of the project to apply any resources to.
78+
79+
* `region` - The region to operate under.
80+
81+
* `zone` - The zone to operate under.
82+
83+
* `access_token` - The OAuth2 access token used by the client to authenticate against the Google Cloud API.
84+
85+
* `default_labels` - The default labels configured on the provider.
86+
87+
* `id` - The ID of this ephemeral resource in Terraform state. It is created in a projects/{{project}}/regions/{{region}}/zones/{{zone}} format and is NOT used by the ephemeral resource in requests to Google APIs.
88+
89+
## Benefits over Data Source
90+
91+
The main advantage of using this ephemeral resource over the `google_client_config` data source is that sensitive credentials (like `access_token`) are not persisted in Terraform's state file. This is particularly important when:
92+
93+
- Using remote state backends where state files might be accessible to multiple users
94+
- Working with sensitive credentials that should not be stored persistently
95+
- Implementing security best practices for credential management
96+
97+
The ephemeral resource provides the same functionality as the data source but with improved security characteristics for credential handling.

0 commit comments

Comments
 (0)