Skip to content

Commit 7ebc2a1

Browse files
secretmanager: ephemeral support for google_secret_manager_secret_version (#14700)
1 parent b095b76 commit 7ebc2a1

File tree

4 files changed

+383
-6
lines changed

4 files changed

+383
-6
lines changed

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ import (
2222
"github.com/hashicorp/terraform-provider-google/google/fwmodels"
2323
"github.com/hashicorp/terraform-provider-google/google/services/resourcemanager"
2424
"github.com/hashicorp/terraform-provider-google/google/services/apigee"
25-
25+
"github.com/hashicorp/terraform-provider-google/google/services/secretmanager"
2626
"github.com/hashicorp/terraform-provider-google/version"
2727
{{- if ne $.TargetVersionName "ga" }}
2828
"github.com/hashicorp/terraform-provider-google/google/services/firebase"
2929
{{- end }}
30-
"github.com/hashicorp/terraform-provider-google/google/services/storage"
30+
"github.com/hashicorp/terraform-provider-google/google/services/storage"
3131

3232
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
3333
)
@@ -380,9 +380,10 @@ 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.GoogleEphemeralServiceAccountAccessToken,
384-
resourcemanager.GoogleEphemeralServiceAccountIdToken,
385-
resourcemanager.GoogleEphemeralServiceAccountJwt,
386-
resourcemanager.GoogleEphemeralServiceAccountKey,
383+
resourcemanager.GoogleEphemeralServiceAccountAccessToken,
384+
resourcemanager.GoogleEphemeralServiceAccountIdToken,
385+
resourcemanager.GoogleEphemeralServiceAccountJwt,
386+
resourcemanager.GoogleEphemeralServiceAccountKey,
387+
secretmanager.GoogleEphemeralSecretManagerSecretVersion,
387388
}
388389
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package secretmanager
2+
3+
import (
4+
"context"
5+
"encoding/base64"
6+
"fmt"
7+
8+
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
9+
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
10+
"github.com/hashicorp/terraform-plugin-framework/types"
11+
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
12+
)
13+
14+
var _ ephemeral.EphemeralResource = &googleEphemeralSecretManagerSecretVersion{}
15+
16+
func GoogleEphemeralSecretManagerSecretVersion() ephemeral.EphemeralResource {
17+
return &googleEphemeralSecretManagerSecretVersion{}
18+
}
19+
20+
type googleEphemeralSecretManagerSecretVersion struct {
21+
providerConfig *transport_tpg.Config
22+
}
23+
24+
func (p *googleEphemeralSecretManagerSecretVersion) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) {
25+
resp.TypeName = req.ProviderTypeName + "_secret_manager_secret_version"
26+
}
27+
28+
type ephemeralSecretManagerSecretVersionModel struct {
29+
Project types.String `tfsdk:"project"`
30+
Secret types.String `tfsdk:"secret"`
31+
Version types.String `tfsdk:"version"`
32+
IsSecretDataBase64 types.Bool `tfsdk:"is_secret_data_base64"`
33+
SecretData types.String `tfsdk:"secret_data"`
34+
Name types.String `tfsdk:"name"`
35+
CreateTime types.String `tfsdk:"create_time"`
36+
DestroyTime types.String `tfsdk:"destroy_time"`
37+
Enabled types.Bool `tfsdk:"enabled"`
38+
}
39+
40+
func (p *googleEphemeralSecretManagerSecretVersion) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
41+
resp.Schema.Description = "This ephemeral resource provides access to a Google Secret Manager secret version."
42+
resp.Schema.MarkdownDescription = "This ephemeral resource provides access to a Google Secret Manager secret version."
43+
44+
resp.Schema = schema.Schema{
45+
Attributes: map[string]schema.Attribute{
46+
// Arguments
47+
"project": schema.StringAttribute{
48+
Description: "The project to get the secret version for. If it is not provided, the provider project is used.",
49+
Optional: true,
50+
Computed: true,
51+
},
52+
"secret": schema.StringAttribute{
53+
Description: "The secret to get the secret version for.",
54+
Required: true,
55+
},
56+
"version": schema.StringAttribute{
57+
Description: "The version of the secret to get. If it is not provided, the latest version is retrieved.",
58+
Optional: true,
59+
},
60+
"is_secret_data_base64": schema.BoolAttribute{
61+
Description: "If true, the secret data returned will not get base64 decoded. Defaults to false.",
62+
Optional: true,
63+
},
64+
// Attributes
65+
"secret_data": schema.StringAttribute{
66+
Description: "The secret data. No larger than 64KiB.",
67+
Computed: true,
68+
Sensitive: true,
69+
},
70+
"name": schema.StringAttribute{
71+
Description: "The resource name of the SecretVersion. Format: `projects/{{project}}/secrets/{{secret_id}}/versions/{{version}}`.",
72+
Computed: true,
73+
},
74+
"create_time": schema.StringAttribute{
75+
Description: "The time at which the Secret was created.",
76+
Computed: true,
77+
},
78+
"destroy_time": schema.StringAttribute{
79+
Description: "The time at which the Secret was destroyed. Only present if state is DESTROYED.",
80+
Computed: true,
81+
},
82+
"enabled": schema.BoolAttribute{
83+
Description: "True if the current state of the SecretVersion is enabled.",
84+
Computed: true,
85+
},
86+
},
87+
}
88+
}
89+
90+
func (p *googleEphemeralSecretManagerSecretVersion) Configure(ctx context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) {
91+
// Prevent panic if the provider has not been configured.
92+
if req.ProviderData == nil {
93+
return
94+
}
95+
96+
pd, ok := req.ProviderData.(*transport_tpg.Config)
97+
if !ok {
98+
resp.Diagnostics.AddError(
99+
"Unexpected Data Source Configure Type",
100+
fmt.Sprintf("Expected *transport_tpg.Config, got: %T. Please report this issue to the provider developers.", req.ProviderData),
101+
)
102+
return
103+
}
104+
105+
// Required for accessing userAgent and passing as an argument into a util function
106+
p.providerConfig = pd
107+
}
108+
109+
func (p *googleEphemeralSecretManagerSecretVersion) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
110+
var data ephemeralSecretManagerSecretVersionModel
111+
112+
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
113+
if resp.Diagnostics.HasError() {
114+
return
115+
}
116+
117+
config := p.providerConfig
118+
userAgent := p.providerConfig.UserAgent
119+
120+
secret := data.Secret.ValueString()
121+
project := data.Project.ValueString()
122+
version := data.Version.ValueString()
123+
124+
if project == "" {
125+
project = config.Project
126+
}
127+
128+
var url string
129+
if version != "" {
130+
url = fmt.Sprintf("%s%s/versions/%s", config.SecretManagerBasePath, secret, version)
131+
} else {
132+
url = fmt.Sprintf("%s%s/versions/latest", config.SecretManagerBasePath, secret)
133+
}
134+
135+
versionResp, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
136+
Config: config,
137+
Method: "GET",
138+
Project: project,
139+
RawURL: url,
140+
UserAgent: userAgent,
141+
})
142+
if err != nil {
143+
resp.Diagnostics.AddError("Error retrieving secret version", err.Error())
144+
return
145+
}
146+
147+
accessURL := fmt.Sprintf("%s%s:access", config.SecretManagerBasePath, versionResp["name"])
148+
accessResp, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
149+
Config: config,
150+
Method: "GET",
151+
Project: project,
152+
RawURL: accessURL,
153+
UserAgent: userAgent,
154+
})
155+
if err != nil {
156+
resp.Diagnostics.AddError("Error accessing secret data", err.Error())
157+
return
158+
}
159+
160+
// This check seems counterintuitive, but read docs on the `google_secret_manager_secret_version` resource.
161+
// It states: "If set to 'true', the secret data is expected to be base64-encoded string and would be sent as is."
162+
payload := accessResp["payload"].(map[string]interface{})
163+
payloadData := payload["data"].(string)
164+
if !data.IsSecretDataBase64.ValueBool() {
165+
decoded, err := base64.StdEncoding.DecodeString(payload["data"].(string))
166+
if err != nil {
167+
resp.Diagnostics.AddError("Error decoding secret data", err.Error())
168+
return
169+
}
170+
payloadData = string(decoded)
171+
}
172+
173+
data.SecretData = types.StringValue(payloadData)
174+
data.Name = types.StringValue(versionResp["name"].(string))
175+
data.CreateTime = types.StringValue(versionResp["createTime"].(string))
176+
data.Project = types.StringValue(project)
177+
data.Enabled = types.BoolValue(true)
178+
179+
if destroyTime, ok := versionResp["destroyTime"]; ok {
180+
data.DestroyTime = types.StringValue(destroyTime.(string))
181+
}
182+
183+
resp.Diagnostics.Append(resp.Result.Set(ctx, data)...)
184+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package secretmanager_test
2+
3+
import (
4+
"encoding/base64"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
9+
"github.com/hashicorp/terraform-provider-google/google/acctest"
10+
)
11+
12+
func TestAccEphemeralSecretManagerSecretVersion_basic(t *testing.T) {
13+
t.Parallel()
14+
acctest.SkipIfVcr(t)
15+
16+
secret := "tf-test-secret-" + acctest.RandString(t, 10)
17+
secretData := "secret-data"
18+
19+
resource.Test(t, resource.TestCase{
20+
PreCheck: func() { acctest.AccTestPreCheck(t) },
21+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
22+
Steps: []resource.TestStep{
23+
{
24+
Config: testAccEphemeralSecretManagerSecretVersion_basic(secret, secretData),
25+
Check: resource.ComposeTestCheckFunc(
26+
resource.TestCheckResourceAttr("data.google_secret_manager_secret_version.default", "secret_data", secretData),
27+
),
28+
},
29+
},
30+
})
31+
}
32+
33+
func TestAccEphemeralSecretManagerSecretVersion_base64(t *testing.T) {
34+
t.Parallel()
35+
acctest.SkipIfVcr(t)
36+
37+
secret := "tf-test-secret-" + acctest.RandString(t, 10)
38+
secretData := "secret-data"
39+
40+
resource.Test(t, resource.TestCase{
41+
PreCheck: func() { acctest.AccTestPreCheck(t) },
42+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
43+
Steps: []resource.TestStep{
44+
{
45+
Config: testAccEphemeralSecretManagerSecretVersion_base64(secret, secretData),
46+
Check: resource.ComposeTestCheckFunc(
47+
resource.TestCheckResourceAttr("data.google_secret_manager_secret_version.default", "is_secret_data_base64", "true"),
48+
resource.TestCheckResourceAttr("data.google_secret_manager_secret_version.default", "secret_data", base64.StdEncoding.EncodeToString([]byte(secretData))),
49+
),
50+
},
51+
},
52+
})
53+
}
54+
55+
func testAccEphemeralSecretManagerSecretVersion_basic(secret, secretData string) string {
56+
return fmt.Sprintf(`
57+
resource "google_secret_manager_secret" "secret" {
58+
secret_id = "%s"
59+
60+
replication {
61+
auto {}
62+
}
63+
}
64+
65+
resource "google_secret_manager_secret_version" "version" {
66+
secret = google_secret_manager_secret.secret.id
67+
secret_data = "%s"
68+
}
69+
70+
ephemeral "google_secret_manager_secret_version" "ephemeral" {
71+
secret = google_secret_manager_secret_version.version.secret
72+
version = google_secret_manager_secret_version.version.version
73+
}
74+
75+
resource "google_secret_manager_secret_version" "version_two_based_on_ephemeral" {
76+
secret = google_secret_manager_secret_version.version.secret
77+
secret_data_wo = ephemeral.google_secret_manager_secret_version.ephemeral.secret_data
78+
secret_data_wo_version = "1"
79+
}
80+
81+
data "google_secret_manager_secret_version" "default" {
82+
secret = google_secret_manager_secret_version.version_two_based_on_ephemeral.secret
83+
version = google_secret_manager_secret_version.version_two_based_on_ephemeral.version
84+
}
85+
`, secret, secretData)
86+
}
87+
88+
func testAccEphemeralSecretManagerSecretVersion_base64(secret, secretData string) string {
89+
return fmt.Sprintf(`
90+
resource "google_secret_manager_secret" "secret" {
91+
secret_id = "%s"
92+
93+
replication {
94+
auto {}
95+
}
96+
}
97+
98+
resource "google_secret_manager_secret_version" "version" {
99+
secret = google_secret_manager_secret.secret.id
100+
secret_data = base64encode("%s")
101+
is_secret_data_base64 = true
102+
}
103+
104+
ephemeral "google_secret_manager_secret_version" "ephemeral" {
105+
secret = google_secret_manager_secret_version.version.secret
106+
version = google_secret_manager_secret_version.version.version
107+
is_secret_data_base64 = true
108+
}
109+
110+
resource "google_secret_manager_secret_version" "version_two_based_on_ephemeral" {
111+
secret = google_secret_manager_secret_version.version.secret
112+
secret_data_wo = ephemeral.google_secret_manager_secret_version.ephemeral.secret_data
113+
secret_data_wo_version = "1"
114+
is_secret_data_base64 = true
115+
}
116+
117+
data "google_secret_manager_secret_version" "default" {
118+
secret = google_secret_manager_secret_version.version_two_based_on_ephemeral.secret
119+
version = google_secret_manager_secret_version.version_two_based_on_ephemeral.version
120+
is_secret_data_base64 = true
121+
}
122+
`, secret, secretData)
123+
}

0 commit comments

Comments
 (0)