diff --git a/.changelog/4722.txt b/.changelog/4722.txt new file mode 100644 index 0000000000..58db78a038 --- /dev/null +++ b/.changelog/4722.txt @@ -0,0 +1,3 @@ +```release-note:bug +serviceusage: Made `google_project_service.service` validation reject invalid service domains that don't contain a period +``` diff --git a/google-beta/resource_google_project_service.go b/google-beta/resource_google_project_service.go index b627b61679..e89a6f4c1f 100644 --- a/google-beta/resource_google_project_service.go +++ b/google-beta/resource_google_project_service.go @@ -56,6 +56,21 @@ var renamedServicesByOldAndNewServiceNames = mergeStringMaps(renamedServices, re const maxServiceUsageBatchSize = 20 +func validateProjectServiceService(val interface{}, key string) (warns []string, errs []error) { + bannedServicesFunc := StringNotInSlice(append(ignoredProjectServices, bannedProjectServices...), false) + warns, errs = bannedServicesFunc(val, key) + if len(errs) > 0 { + return + } + + // StringNotInSlice already validates that this is a string + v, _ := val.(string) + if !strings.Contains(v, ".") { + errs = append(errs, fmt.Errorf("expected %s to be a domain like serviceusage.googleapis.com", v)) + } + return +} + func resourceGoogleProjectService() *schema.Resource { return &schema.Resource{ Create: resourceGoogleProjectServiceCreate, @@ -79,7 +94,7 @@ func resourceGoogleProjectService() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: StringNotInSlice(append(ignoredProjectServices, bannedProjectServices...), false), + ValidateFunc: validateProjectServiceService, }, "project": { Type: schema.TypeString, diff --git a/google-beta/resource_google_project_service_test.go b/google-beta/resource_google_project_service_test.go index af3d474c41..7d5719cb73 100644 --- a/google-beta/resource_google_project_service_test.go +++ b/google-beta/resource_google_project_service_test.go @@ -10,6 +10,44 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) +func TestProjectServiceServiceValidateFunc(t *testing.T) { + cases := map[string]struct { + val interface{} + ExpectValidationError bool + }{ + "ignoredProjectService": { + val: "dataproc-control.googleapis.com", + ExpectValidationError: true, + }, + "bannedProjectService": { + val: "bigquery-json.googleapis.com", + ExpectValidationError: true, + }, + "third party API": { + val: "whatever.example.com", + ExpectValidationError: false, + }, + "not a domain": { + val: "monitoring", + ExpectValidationError: true, + }, + "not a string": { + val: 5, + ExpectValidationError: true, + }, + } + + for tn, tc := range cases { + _, errs := validateProjectServiceService(tc.val, "service") + if tc.ExpectValidationError && len(errs) == 0 { + t.Errorf("bad: %s, %q passed validation but was expected to fail", tn, tc.val) + } + if !tc.ExpectValidationError && len(errs) > 0 { + t.Errorf("bad: %s, %q failed validation but was expected to pass. errs: %q", tn, tc.val, errs) + } + } +} + // Test that services can be enabled and disabled on a project func TestAccProjectService_basic(t *testing.T) { t.Parallel()