diff --git a/.changelog/3431.txt b/.changelog/3431.txt new file mode 100644 index 0000000000..5df2a21df1 --- /dev/null +++ b/.changelog/3431.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +cloudfunctions: Added validation to label keys for `google_cloudfunctions_function` as API errors aren't useful. +``` diff --git a/google-beta/resource_cloudfunctions_function.go b/google-beta/resource_cloudfunctions_function.go index 6fd92aa760..cc778c31aa 100644 --- a/google-beta/resource_cloudfunctions_function.go +++ b/google-beta/resource_cloudfunctions_function.go @@ -1,6 +1,8 @@ package google import ( + "regexp" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" "google.golang.org/api/cloudfunctions/v1" @@ -50,6 +52,23 @@ func (s *cloudFunctionId) cloudFunctionId() string { return fmt.Sprintf("projects/%s/locations/%s/functions/%s", s.Project, s.Region, s.Name) } +// matches all international lower case letters, number, underscores and dashes. +var labelKeyRegex = regexp.MustCompile(`^[\p{Ll}0-9_-]+$`) + +func labelKeyValidator(val interface{}, key string) (warns []string, errs []error) { + if val == nil { + return + } + + m := val.(map[string]interface{}) + for k := range m { + if !labelKeyRegex.MatchString(k) { + errs = append(errs, fmt.Errorf("%q is an invalid label key. See https://cloud.google.com/resource-manager/docs/creating-managing-labels#requirements", k)) + } + } + return +} + func (s *cloudFunctionId) locationId() string { return fmt.Sprintf("projects/%s/locations/%s", s.Project, s.Region) } @@ -193,8 +212,9 @@ func resourceCloudFunctionsFunction() *schema.Resource { }, "labels": { - Type: schema.TypeMap, - Optional: true, + Type: schema.TypeMap, + ValidateFunc: labelKeyValidator, + Optional: true, }, "runtime": { diff --git a/google-beta/resource_cloudfunctions_function_test.go b/google-beta/resource_cloudfunctions_function_test.go index e75c20508f..3b04875c33 100644 --- a/google-beta/resource_cloudfunctions_function_test.go +++ b/google-beta/resource_cloudfunctions_function_test.go @@ -71,6 +71,51 @@ func TestCloudFunctionsFunction_nameValidator(t *testing.T) { } } +func TestValidLabelKeys(t *testing.T) { + testCases := []struct { + labelKey string + valid bool + }{ + { + "test-label", true, + }, + { + "test_label", true, + }, + { + "MixedCase", false, + }, + { + "number-09-dash", true, + }, + { + "", false, + }, + { + "test-label", true, + }, + { + "mixed*symbol", false, + }, + { + "intérnätional", true, + }, + } + + for _, tc := range testCases { + labels := make(map[string]interface{}) + labels[tc.labelKey] = "test value" + + _, errs := labelKeyValidator(labels, "") + if tc.valid && len(errs) > 0 { + t.Errorf("Validation failure, key: '%s' should be valid but actual errors were %q", tc.labelKey, errs) + } + if !tc.valid && len(errs) < 1 { + t.Errorf("Validation failure, key: '%s' should fail but actual errors were %q", tc.labelKey, errs) + } + } +} + func TestAccCloudFunctionsFunction_basic(t *testing.T) { t.Parallel() diff --git a/website/docs/r/cloudfunctions_function.html.markdown b/website/docs/r/cloudfunctions_function.html.markdown index 13aa62af6a..d2766fa4bd 100644 --- a/website/docs/r/cloudfunctions_function.html.markdown +++ b/website/docs/r/cloudfunctions_function.html.markdown @@ -125,7 +125,7 @@ Eg. `"nodejs8"`, `"nodejs10"`, `"python37"`, `"go111"`. * `ingress_settings` - (Optional) String value that controls what traffic can reach the function. Allowed values are ALLOW_ALL and ALLOW_INTERNAL_ONLY. Changes to this field will recreate the cloud function. -* `labels` - (Optional) A set of key/value label pairs to assign to the function. +* `labels` - (Optional) A set of key/value label pairs to assign to the function. Label keys must follow the requirements at https://cloud.google.com/resource-manager/docs/creating-managing-labels#requirements. * `service_account_email` - (Optional) If provided, the self-provided service account to run the function with. @@ -147,7 +147,7 @@ Eg. `"nodejs8"`, `"nodejs10"`, `"python37"`, `"go111"`. The `event_trigger` block supports: * `event_type` - (Required) The type of event to observe. For example: `"google.storage.object.finalize"`. -See the documentation on [calling Cloud Functions](https://cloud.google.com/functions/docs/calling/) for a +See the documentation on [calling Cloud Functions](https://cloud.google.com/functions/docs/calling/) for a full reference of accepted triggers. * `resource` - (Required) Required. The name or partial URI of the resource from