diff --git a/google/resource_google_project_iam_custom_role.go b/google/resource_google_project_iam_custom_role.go index 71c4ed5f..3097696c 100644 --- a/google/resource_google_project_iam_custom_role.go +++ b/google/resource_google_project_iam_custom_role.go @@ -21,9 +21,10 @@ func resourceGoogleProjectIamCustomRole() *schema.Resource { Schema: map[string]*schema.Schema{ "role_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateIAMCustomRoleID, }, "title": { Type: schema.TypeString, diff --git a/google/validation.go b/google/validation.go index 5c800476..2ce56d2e 100644 --- a/google/validation.go +++ b/google/validation.go @@ -27,6 +27,9 @@ const ( // Format of default Compute service accounts created by Google // ${PROJECT_ID}-compute@developer.gserviceaccount.com where PROJECT_ID is an int64 (max 20 digits) ComputeServiceAccountNameRegex = "[0-9]{1,20}-compute@developer.gserviceaccount.com" + + // https://cloud.google.com/iam/docs/understanding-custom-roles#naming_the_role + IAMCustomRoleIDRegex = "^[a-zA-Z0-9_\\.\\-]{1,30}$" ) var ( @@ -155,6 +158,15 @@ func validateCloudIoTID(v interface{}, k string) (warnings []string, errors []er return } +func validateIAMCustomRoleID(v interface{}, k string) (warnings []string, errors []error) { + value := v.(string) + if !regexp.MustCompile(IAMCustomRoleIDRegex).MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q (%q) doesn't match regexp %q", k, value, IAMCustomRoleIDRegex)) + } + return +} + func orEmpty(f schema.SchemaValidateFunc) schema.SchemaValidateFunc { return func(i interface{}, k string) ([]string, []error) { v, ok := i.(string) diff --git a/google/validation_test.go b/google/validation_test.go index adeeb321..e1373fdb 100644 --- a/google/validation_test.go +++ b/google/validation_test.go @@ -317,3 +317,30 @@ func TestValidateProjectName(t *testing.T) { t.Errorf("Failed to validate project ID's: %v", es) } } + +func TestValidateIAMCustomRoleIDRegex(t *testing.T) { + x := []StringValidationTestCase{ + // No errors + {TestName: "basic", Value: "foobar"}, + {TestName: "with numbers", Value: "foobar123"}, + {TestName: "with capipals", Value: "FooBar"}, + {TestName: "short", Value: "f"}, + {TestName: "long", Value: "foobarfoobarfoobarfoobarfoobar"}, + {TestName: "has a hyphen", Value: "foo-bar"}, + {TestName: "has a dot", Value: "foo.bar"}, + {TestName: "has an underscore", Value: "foo_bar"}, + {TestName: "all of the above", Value: "foo.Bar-Baz_123"}, + + // With errors + {TestName: "empty", Value: "", ExpectError: true}, + {TestName: "has an slash", Value: "foo/bar", ExpectError: true}, + {TestName: "has a dollar", Value: "foo$", ExpectError: true}, + {TestName: "has a space", Value: "foo bar", ExpectError: true}, + {TestName: "too long", Value: strings.Repeat("f", 31), ExpectError: true}, + } + + es := testStringValidationCases(x, validateIAMCustomRoleID) + if len(es) > 0 { + t.Errorf("Failed to validate IAMCustomRole IDs: %v", es) + } +}