Skip to content

Commit 4dbb570

Browse files
authored
framework storage notification (#14936)
1 parent 572fd14 commit 4dbb570

File tree

9 files changed

+663
-202
lines changed

9 files changed

+663
-202
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
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"
3031

3132
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
3233
)
@@ -332,6 +333,7 @@ func (p *FrameworkProvider) DataSources(_ context.Context) []func() datasource.D
332333
func (p *FrameworkProvider) Resources(_ context.Context) []func() resource.Resource {
333334
return []func() resource.Resource{
334335
apigee.NewApigeeKeystoresAliasesKeyCertFileResource,
336+
storage.NewStorageNotificationResource,
335337
}
336338
}
337339

mmv1/third_party/terraform/fwresource/field_helpers.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,10 @@ func ReplaceVarsForFrameworkTest(prov *transport_tpg.Config, rs *terraform.Resou
119119

120120
return re.ReplaceAllStringFunc(linkTmpl, replaceFunc), nil
121121
}
122+
123+
func FlattenStringEmptyToNull(configuredValue types.String, apiValue string) types.String {
124+
if configuredValue.IsNull() && apiValue == "" {
125+
return types.StringNull()
126+
}
127+
return types.StringValue(apiValue)
128+
}

mmv1/third_party/terraform/fwvalidators/framework_validators.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"strings"
1010
"time"
1111

12+
"github.com/hashicorp/terraform-plugin-framework/types"
13+
1214
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
1315

1416
googleoauth "golang.org/x/oauth2/google"
@@ -262,3 +264,81 @@ func (v jwtValidator) ValidateString(ctx context.Context, request validator.Stri
262264
func JWTValidator() validator.String {
263265
return jwtValidator{}
264266
}
267+
268+
// stringValuesInSetValidator validates that all string elements in a set
269+
// are present in the configured list of valid strings.
270+
type stringValuesInSetValidator struct {
271+
ValidStrings []string
272+
}
273+
274+
func (v stringValuesInSetValidator) Description(_ context.Context) string {
275+
return fmt.Sprintf("all elements must be one of: %q", v.ValidStrings)
276+
}
277+
278+
func (v stringValuesInSetValidator) MarkdownDescription(ctx context.Context) string {
279+
return v.Description(ctx)
280+
}
281+
282+
func (v stringValuesInSetValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) {
283+
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
284+
return
285+
}
286+
287+
valid := make(map[string]struct{}, len(v.ValidStrings))
288+
for _, s := range v.ValidStrings {
289+
valid[s] = struct{}{}
290+
}
291+
292+
var elements []types.String
293+
resp.Diagnostics.Append(req.ConfigValue.ElementsAs(ctx, &elements, false)...)
294+
if resp.Diagnostics.HasError() {
295+
return
296+
}
297+
298+
for _, el := range elements {
299+
if _, ok := valid[el.ValueString()]; !ok {
300+
resp.Diagnostics.AddAttributeError(
301+
req.Path,
302+
"Invalid Set Element",
303+
fmt.Sprintf("Element %q is not a valid value. %s.", el.ValueString(), v.Description(ctx)),
304+
)
305+
}
306+
}
307+
}
308+
309+
func StringValuesInSet(validStrings ...string) validator.Set {
310+
return stringValuesInSetValidator{
311+
ValidStrings: validStrings,
312+
}
313+
}
314+
315+
type TopicPrefixValidator struct{}
316+
317+
func (v TopicPrefixValidator) Description(ctx context.Context) string {
318+
return "ensures the topic does not start with '//pubsub.googleapis.com/'"
319+
}
320+
321+
func (v TopicPrefixValidator) MarkdownDescription(ctx context.Context) string {
322+
return "Ensures the topic does not start with `//pubsub.googleapis.com/`."
323+
}
324+
325+
func (v TopicPrefixValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
326+
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
327+
return
328+
}
329+
330+
value := req.ConfigValue.ValueString()
331+
forbiddenPrefix := "//pubsub.googleapis.com/"
332+
333+
if strings.HasPrefix(value, forbiddenPrefix) {
334+
resp.Diagnostics.AddAttributeError(
335+
req.Path,
336+
"Invalid Topic Format",
337+
fmt.Sprintf("The topic must not start with '%s', please use the format projects/{project}/topics/{topic} instead.", forbiddenPrefix),
338+
)
339+
}
340+
}
341+
342+
func NewTopicPrefixValidator() validator.String {
343+
return TopicPrefixValidator{}
344+
}

mmv1/third_party/terraform/fwvalidators/framework_validators_test.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,3 +307,141 @@ func TestBoundedDuration(t *testing.T) {
307307
})
308308
}
309309
}
310+
311+
func TestStringValuesInSetValidator(t *testing.T) {
312+
t.Parallel()
313+
314+
// Define the set of valid strings for the validator
315+
validStrings := []string{"APPLE", "BANANA", "CHERRY"}
316+
317+
stringSet := func(elems []string) types.Set {
318+
if elems == nil {
319+
return types.SetNull(types.StringType)
320+
}
321+
val, diags := types.SetValueFrom(context.Background(), types.StringType, elems)
322+
if diags.HasError() {
323+
t.Fatalf("Failed to create test set: %v", diags)
324+
}
325+
return val
326+
}
327+
328+
cases := map[string]struct {
329+
ConfigValue types.Set
330+
ExpectedErrorCount int
331+
}{
332+
"valid set with one element": {
333+
ConfigValue: stringSet([]string{"APPLE"}),
334+
ExpectedErrorCount: 0,
335+
},
336+
"valid set with multiple elements": {
337+
ConfigValue: stringSet([]string{"BANANA", "CHERRY"}),
338+
ExpectedErrorCount: 0,
339+
},
340+
"valid empty set": {
341+
ConfigValue: stringSet([]string{}),
342+
ExpectedErrorCount: 0,
343+
},
344+
"null set is valid": {
345+
ConfigValue: stringSet(nil),
346+
ExpectedErrorCount: 0,
347+
},
348+
"unknown set is valid": {
349+
ConfigValue: types.SetUnknown(types.StringType),
350+
ExpectedErrorCount: 0,
351+
},
352+
"invalid set with one element": {
353+
ConfigValue: stringSet([]string{"DURIAN"}),
354+
ExpectedErrorCount: 1,
355+
},
356+
"invalid set with multiple elements": {
357+
ConfigValue: stringSet([]string{"DURIAN", "ELDERBERRY"}),
358+
ExpectedErrorCount: 2,
359+
},
360+
"set with mixed valid and invalid elements": {
361+
ConfigValue: stringSet([]string{"APPLE", "DURIAN", "CHERRY"}),
362+
ExpectedErrorCount: 1,
363+
},
364+
}
365+
366+
for tn, tc := range cases {
367+
tn, tc := tn, tc
368+
t.Run(tn, func(t *testing.T) {
369+
t.Parallel()
370+
371+
req := validator.SetRequest{
372+
Path: path.Root("test_attribute"),
373+
ConfigValue: tc.ConfigValue,
374+
}
375+
resp := &validator.SetResponse{
376+
Diagnostics: diag.Diagnostics{},
377+
}
378+
v := fwvalidators.StringValuesInSet(validStrings...)
379+
380+
v.ValidateSet(context.Background(), req, resp)
381+
382+
if resp.Diagnostics.ErrorsCount() != tc.ExpectedErrorCount {
383+
t.Errorf("Expected %d errors, but got %d. Errors: %v", tc.ExpectedErrorCount, resp.Diagnostics.ErrorsCount(), resp.Diagnostics.Errors())
384+
}
385+
})
386+
}
387+
}
388+
389+
func TestTopicPrefixValidator(t *testing.T) {
390+
t.Parallel()
391+
392+
type testCase struct {
393+
value types.String
394+
expectError bool
395+
errorContains string
396+
}
397+
398+
tests := map[string]testCase{
399+
"valid topic format": {
400+
value: types.StringValue("projects/my-project/topics/my-topic"),
401+
expectError: false,
402+
},
403+
"invalid topic format - starts with pubsub prefix": {
404+
value: types.StringValue("//pubsub.googleapis.com/projects/my-project/topics/my-topic"),
405+
expectError: true,
406+
errorContains: "The topic must not start with '//pubsub.googleapis.com/', please use the format projects/{project}/topics/{topic} instead.",
407+
},
408+
}
409+
410+
for name, test := range tests {
411+
name, test := name, test
412+
t.Run(name, func(t *testing.T) {
413+
t.Parallel()
414+
415+
request := validator.StringRequest{
416+
Path: path.Root("test_topic"),
417+
PathExpression: path.MatchRoot("test_topic"),
418+
ConfigValue: test.value,
419+
}
420+
response := validator.StringResponse{}
421+
v := fwvalidators.NewTopicPrefixValidator()
422+
423+
v.ValidateString(context.Background(), request, &response)
424+
425+
if test.expectError && !response.Diagnostics.HasError() {
426+
t.Errorf("expected error, got none for value: %q", test.value.ValueString())
427+
}
428+
429+
if !test.expectError && response.Diagnostics.HasError() {
430+
t.Errorf("got unexpected error for value: %q: %s", test.value.ValueString(), response.Diagnostics.Errors())
431+
}
432+
433+
if test.errorContains != "" {
434+
foundError := false
435+
for _, err := range response.Diagnostics.Errors() {
436+
if err.Detail() == test.errorContains {
437+
foundError = true
438+
break
439+
}
440+
}
441+
if !foundError {
442+
t.Errorf("expected error with detail %q, got none", test.errorContains)
443+
}
444+
}
445+
})
446+
}
447+
}

mmv1/third_party/terraform/provider/provider_mmv1_resources.go.tmpl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,6 @@ var handwrittenResources = map[string]*schema.Resource{
439439
"google_storage_bucket_object": storage.ResourceStorageBucketObject(),
440440
"google_storage_object_acl": storage.ResourceStorageObjectAcl(),
441441
"google_storage_default_object_acl": storage.ResourceStorageDefaultObjectAcl(),
442-
"google_storage_notification": storage.ResourceStorageNotification(),
443442
"google_storage_transfer_job": storagetransfer.ResourceStorageTransferJob(),
444443
"google_tags_location_tag_binding": tags.ResourceTagsLocationTagBinding(),
445444
// ####### END handwritten resources ###########

0 commit comments

Comments
 (0)