diff --git a/azuread/resource_application.go b/azuread/resource_application.go index 4eccd26549..1cd0fd75d6 100644 --- a/azuread/resource_application.go +++ b/azuread/resource_application.go @@ -285,6 +285,11 @@ func resourceApplication() *schema.Resource { }, }, }, + "prevent_duplicate_names": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, }, } } @@ -294,6 +299,14 @@ func resourceApplicationCreate(d *schema.ResourceData, meta interface{}) error { ctx := meta.(*ArmClient).StopContext name := d.Get("name").(string) + + if d.Get("prevent_duplicate_names").(bool) { + err := aadApplicationCheckNameAvailability(client, ctx, name) + if err != nil { + return err + } + } + appType := d.Get("type") identUrls, hasIdentUrls := d.GetOk("identifier_uris") if appType == "native" { @@ -400,6 +413,13 @@ func resourceApplicationUpdate(d *schema.ResourceData, meta interface{}) error { name := d.Get("name").(string) + if d.Get("prevent_duplicate_names").(bool) { + err := aadApplicationCheckNameAvailability(client, ctx, name) + if err != nil { + return err + } + } + var properties graphrbac.ApplicationUpdateParameters if d.HasChange("name") { @@ -591,6 +611,10 @@ func resourceApplicationRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("setting `owners`: %+v", err) } + if preventDuplicates := d.Get("prevent_duplicate_names").(bool); !preventDuplicates { + d.Set("prevent_duplicate_names", false) + } + return nil } @@ -904,3 +928,31 @@ func adApplicationSetOwnersTo(client graphrbac.ApplicationsClient, ctx context.C return nil } + +func aadApplicationFindByName(client graphrbac.ApplicationsClient, ctx context.Context, name string) (*graphrbac.Application, error) { + nameFilter := fmt.Sprintf("displayName eq '%s'", name) + resp, err := client.List(ctx, nameFilter) + + if err != nil { + return nil, fmt.Errorf("unable to list Applications with filter %q: %+v", nameFilter, err) + } + + for _, app := range resp.Values() { + if *app.DisplayName == name { + return &app, nil + } + } + + return nil, nil +} + +func aadApplicationCheckNameAvailability(client graphrbac.ApplicationsClient, ctx context.Context, name string) error { + existingApp, err := aadApplicationFindByName(client, ctx, name) + if err != nil { + return err + } + if existingApp != nil { + return fmt.Errorf("Existing Application with name %q (AppID: %q) was found and `prevent_duplicate_names` was specified", name, *existingApp.AppID) + } + return nil +} diff --git a/azuread/resource_application_test.go b/azuread/resource_application_test.go index db184e29e5..d64d648073 100644 --- a/azuread/resource_application_test.go +++ b/azuread/resource_application_test.go @@ -584,6 +584,22 @@ func TestAccAzureADApplication_oauth2PermissionsUpdate(t *testing.T) { }) } +func TestAccAzureADApplication_preventDuplicateNames(t *testing.T) { + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckADApplicationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccADApplication_duplicateName(ri), + ExpectError: regexp.MustCompile("Existing Application .+ was found"), + }, + }, + }) +} + func testCheckADApplicationExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] @@ -913,3 +929,14 @@ resource "azuread_application" "test" { } `, ri) } + +func testAccADApplication_duplicateName(ri int) string { + return fmt.Sprintf(` +%s + +resource "azuread_application" "duplicate" { + name = azuread_application.test.name + prevent_duplicate_names = true +} +`, testAccADApplication_basic(ri)) +} diff --git a/website/docs/r/application.html.markdown b/website/docs/r/application.html.markdown index 3b4b821278..070511245f 100644 --- a/website/docs/r/application.html.markdown +++ b/website/docs/r/application.html.markdown @@ -137,6 +137,8 @@ The following arguments are supported: * `oauth2_permissions` - (Optional) A collection of OAuth 2.0 permission scopes that the web API (resource) app exposes to client apps. Each permission is covered by `oauth2_permissions` blocks as documented below. +* `prevent_duplicate_names` - (Optional) If `true`, will return an error when an existing Application is found with the same name. Defaults to `false`. + --- `required_resource_access` supports the following: