diff --git a/internal/services/applications/application_data_source.go b/internal/services/applications/application_data_source.go index d55ea33e19..85e44106b2 100644 --- a/internal/services/applications/application_data_source.go +++ b/internal/services/applications/application_data_source.go @@ -44,6 +44,7 @@ func applicationDataSource() *pluginsdk.Resource { Computed: true, ExactlyOneOf: []string{"application_id", "client_id", "display_name", "object_id"}, ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + Deprecated: "The `application_id` property has been replaced with the `client_id` property and will be removed in version 3.0 of the AzureAD provider", }, "client_id": { diff --git a/internal/services/applications/application_data_source_test.go b/internal/services/applications/application_data_source_test.go index 3edff3e6b1..f0ab09f400 100644 --- a/internal/services/applications/application_data_source_test.go +++ b/internal/services/applications/application_data_source_test.go @@ -25,13 +25,13 @@ func TestAccApplicationDataSource_byObjectId(t *testing.T) { }) } -func TestAccApplicationDataSource_byApplicationId(t *testing.T) { +func TestAccApplicationDataSource_byApplicationIdDeprecated(t *testing.T) { data := acceptance.BuildTestData(t, "data.azuread_application", "test") r := ApplicationDataSource{} data.DataSourceTest(t, []acceptance.TestStep{ { - Config: r.applicationId(data), + Config: r.applicationIdDeprecated(data), Check: r.testCheck(data), }, }) @@ -101,7 +101,7 @@ data "azuread_application" "test" { `, ApplicationResource{}.complete(data)) } -func (ApplicationDataSource) applicationId(data acceptance.TestData) string { +func (ApplicationDataSource) applicationIdDeprecated(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s diff --git a/internal/services/applications/application_federated_identity_credential_resource.go b/internal/services/applications/application_federated_identity_credential_resource.go index 7fdafd9635..adf74516ae 100644 --- a/internal/services/applications/application_federated_identity_credential_resource.go +++ b/internal/services/applications/application_federated_identity_credential_resource.go @@ -61,6 +61,7 @@ func applicationFederatedIdentityCredentialResource() *pluginsdk.Resource { Computed: true, ForceNew: true, ExactlyOneOf: []string{"application_id", "application_object_id"}, + Deprecated: "The `application_object_id` property has been replaced with the `application_id` property and will be removed in version 3.0 of the AzureAD provider", ValidateFunc: validation.Any(validation.IsUUID, parse.ValidateApplicationID), }, diff --git a/internal/services/serviceprincipals/service_principal_data_source.go b/internal/services/serviceprincipals/service_principal_data_source.go index 86417b3b3e..db9f0479d1 100644 --- a/internal/services/serviceprincipals/service_principal_data_source.go +++ b/internal/services/serviceprincipals/service_principal_data_source.go @@ -30,30 +30,40 @@ func servicePrincipalData() *pluginsdk.Resource { Schema: map[string]*pluginsdk.Schema{ "object_id": { - Description: "The object ID of the service principal", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ExactlyOneOf: []string{"application_id", "display_name", "object_id"}, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + Description: "The object ID of the service principal", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ExactlyOneOf: []string{"client_id", "application_id", "display_name", "object_id"}, + ValidateFunc: validation.IsUUID, }, "display_name": { - Description: "The display name of the application associated with this service principal", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ExactlyOneOf: []string{"application_id", "display_name", "object_id"}, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Description: "The display name of the application associated with this service principal", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ExactlyOneOf: []string{"client_id", "application_id", "display_name", "object_id"}, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "client_id": { + Description: "The client ID of the application associated with this service principal", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ExactlyOneOf: []string{"client_id", "application_id", "display_name", "object_id"}, + ValidateFunc: validation.IsUUID, }, "application_id": { - Description: "The application ID (client ID) of the application associated with this service principal", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ExactlyOneOf: []string{"application_id", "display_name", "object_id"}, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + Description: "The application ID (client ID) of the application associated with this service principal", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ExactlyOneOf: []string{"client_id", "application_id", "display_name", "object_id"}, + ValidateFunc: validation.IsUUID, + Deprecated: "The `application_id` property has been replaced with the `client_id` property and will be removed in version 3.0 of the AzureAD provider", }, "account_enabled": { @@ -331,9 +341,15 @@ func servicePrincipalDataSourceRead(ctx context.Context, d *pluginsdk.ResourceDa return tf.ErrorDiagF(nil, "No service principal found matching display name: %q", displayName) } } else { - applicationId := d.Get("application_id").(string) + var clientId string + if v := d.Get("client_id").(string); v != "" { + clientId = v + } else { + clientId = d.Get("application_id").(string) + } + query := odata.Query{ - Filter: fmt.Sprintf("appId eq '%s'", applicationId), + Filter: fmt.Sprintf("appId eq '%s'", clientId), } result, _, err := client.List(ctx, query) @@ -349,14 +365,14 @@ func servicePrincipalDataSourceRead(ctx context.Context, d *pluginsdk.ResourceDa continue } - if *sp.AppId == applicationId { + if *sp.AppId == clientId { servicePrincipal = &sp break } } if servicePrincipal == nil { - return tf.ErrorDiagF(nil, "No service principal found for application ID: %q", applicationId) + return tf.ErrorDiagF(nil, "No service principal found for application ID: %q", clientId) } } @@ -383,6 +399,7 @@ func servicePrincipalDataSourceRead(ctx context.Context, d *pluginsdk.ResourceDa tf.Set(d, "app_roles", helpers.ApplicationFlattenAppRoles(servicePrincipal.AppRoles)) tf.Set(d, "application_id", servicePrincipal.AppId) tf.Set(d, "application_tenant_id", servicePrincipal.AppOwnerOrganizationId) + tf.Set(d, "client_id", servicePrincipal.AppId) tf.Set(d, "description", servicePrincipal.Description) tf.Set(d, "display_name", servicePrincipal.DisplayName) tf.Set(d, "feature_tags", helpers.ApplicationFlattenFeatures(servicePrincipal.Tags, false)) diff --git a/internal/services/serviceprincipals/service_principal_data_source_test.go b/internal/services/serviceprincipals/service_principal_data_source_test.go index 7fa8e93dc0..b5bc786139 100644 --- a/internal/services/serviceprincipals/service_principal_data_source_test.go +++ b/internal/services/serviceprincipals/service_principal_data_source_test.go @@ -15,13 +15,25 @@ import ( type ServicePrincipalDataSource struct{} -func TestAccServicePrincipalDataSource_byApplicationId(t *testing.T) { +func TestAccServicePrincipalDataSource_byClientId(t *testing.T) { data := acceptance.BuildTestData(t, "data.azuread_service_principal", "test") r := ServicePrincipalDataSource{} data.DataSourceTest(t, []acceptance.TestStep{ { - Config: r.byApplicationId(data), + Config: r.byClientId(data), + Check: r.testCheckFunc(data), + }, + }) +} + +func TestAccServicePrincipalDataSource_byDeprecatedApplicationId(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azuread_service_principal", "test") + r := ServicePrincipalDataSource{} + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: r.byDeprecatedApplicationId(data), Check: r.testCheckFunc(data), }, }) @@ -98,7 +110,17 @@ func (ServicePrincipalDataSource) testCheckFunc(data acceptance.TestData) accept ) } -func (ServicePrincipalDataSource) byApplicationId(data acceptance.TestData) string { +func (ServicePrincipalDataSource) byClientId(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +data "azuread_service_principal" "test" { + client_id = azuread_service_principal.test.client_id +} +`, ServicePrincipalResource{}.complete(data)) +} + +func (ServicePrincipalDataSource) byDeprecatedApplicationId(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s diff --git a/internal/services/serviceprincipals/service_principal_resource.go b/internal/services/serviceprincipals/service_principal_resource.go index d2270d8877..17d107ac0c 100644 --- a/internal/services/serviceprincipals/service_principal_resource.go +++ b/internal/services/serviceprincipals/service_principal_resource.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-sdk/sdk/odata" "github.com/hashicorp/go-uuid" "github.com/hashicorp/terraform-provider-azuread/internal/clients" @@ -47,12 +48,25 @@ func servicePrincipalResource() *pluginsdk.Resource { }), Schema: map[string]*pluginsdk.Schema{ + "client_id": { + Description: "The client ID of the application for which to create a service principal", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, // TODO remove Computed in v3.0 + ForceNew: true, + ExactlyOneOf: []string{"client_id", "application_id"}, + ValidateFunc: validation.IsUUID, + }, + "application_id": { - Description: "The application ID (client ID) of the application for which to create a service principal", - Type: pluginsdk.TypeString, - Required: true, - ForceNew: true, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + Description: "The application ID (client ID) of the application for which to create a service principal", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ExactlyOneOf: []string{"client_id", "application_id"}, + ValidateFunc: validation.IsUUID, + Deprecated: "The `application_id` property has been replaced with the `client_id` property and will be removed in version 3.0 of the AzureAD provider", }, "account_enabled": { @@ -67,8 +81,8 @@ func servicePrincipalResource() *pluginsdk.Resource { Type: pluginsdk.TypeSet, Optional: true, Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringIsNotEmpty, }, }, @@ -176,8 +190,8 @@ func servicePrincipalResource() *pluginsdk.Resource { Type: pluginsdk.TypeSet, Optional: true, Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringIsNotEmpty, }, }, @@ -187,8 +201,8 @@ func servicePrincipalResource() *pluginsdk.Resource { Optional: true, Set: pluginsdk.HashString, Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsUUID, }, }, @@ -299,10 +313,10 @@ func servicePrincipalResource() *pluginsdk.Resource { Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "relay_state": { - Description: "The relative URI the service provider would redirect to after completion of the single sign-on flow", - Type: pluginsdk.TypeString, - Optional: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Description: "The relative URI the service provider would redirect to after completion of the single sign-on flow", + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, }, }, }, @@ -355,17 +369,22 @@ func servicePrincipalResourceCreate(ctx context.Context, d *pluginsdk.ResourceDa callerId := meta.(*clients.Client).ObjectID tenantId := meta.(*clients.Client).TenantID - appId := d.Get("application_id").(string) + var clientId string + if v := d.Get("client_id").(string); v != "" { + clientId = v + } else { + clientId = d.Get("application_id").(string) + } var servicePrincipal *msgraph.ServicePrincipal var err error if d.Get("use_existing").(bool) { // Assume that a service principal already exists and try to look for it, whilst retrying to defeat eventual consistency - servicePrincipal, err = findByAppIdWithTimeout(ctx, 5*time.Minute, client, appId) + servicePrincipal, err = findByClientIdWithTimeout(ctx, 5*time.Minute, client, clientId) } else { // Otherwise perform a single List operation to check for an existing service principal - servicePrincipal, err = findByAppId(ctx, client, appId) + servicePrincipal, err = findByClientId(ctx, client, clientId) } if err != nil { return tf.ErrorDiagF(err, "Could not list existing service principals") @@ -400,15 +419,15 @@ func servicePrincipalResourceCreate(ctx context.Context, d *pluginsdk.ResourceDa tempDescription := fmt.Sprintf("TERRAFORM_UPDATE_%s", uuid) properties := msgraph.ServicePrincipal{ - AccountEnabled: utils.Bool(d.Get("account_enabled").(bool)), + AccountEnabled: pointer.To(d.Get("account_enabled").(bool)), AlternativeNames: tf.ExpandStringSlicePtr(d.Get("alternative_names").(*pluginsdk.Set).List()), - AppId: utils.String(d.Get("application_id").(string)), - AppRoleAssignmentRequired: utils.Bool(d.Get("app_role_assignment_required").(bool)), - Description: utils.NullableString(tempDescription), - LoginUrl: utils.NullableString(d.Get("login_url").(string)), - Notes: utils.NullableString(d.Get("notes").(string)), + AppId: pointer.To(clientId), + AppRoleAssignmentRequired: pointer.To(d.Get("app_role_assignment_required").(bool)), + Description: tf.NullableString(tempDescription), + LoginUrl: tf.NullableString(d.Get("login_url").(string)), + Notes: tf.NullableString(d.Get("notes").(string)), NotificationEmailAddresses: tf.ExpandStringSlicePtr(d.Get("notification_email_addresses").(*pluginsdk.Set).List()), - PreferredSingleSignOnMode: utils.NullableString(d.Get("preferred_single_sign_on_mode").(string)), + PreferredSingleSignOnMode: tf.NullableString(d.Get("preferred_single_sign_on_mode").(string)), SamlSingleSignOnSettings: expandSamlSingleSignOn(d.Get("saml_single_sign_on").([]interface{})), Tags: &tags, } @@ -424,7 +443,7 @@ func servicePrincipalResourceCreate(ctx context.Context, d *pluginsdk.ResourceDa } // @odata.id returned by API cannot be relied upon, so construct our own - callerObject.ODataId = (*odata.Id)(utils.String(fmt.Sprintf("%s/v1.0/%s/directoryObjects/%s", + callerObject.ODataId = (*odata.Id)(pointer.To(fmt.Sprintf("%s/v1.0/%s/directoryObjects/%s", client.BaseClient.Endpoint, tenantId, callerId))) ownersFirst20 := msgraph.Owners{*callerObject} @@ -446,7 +465,7 @@ func servicePrincipalResourceCreate(ctx context.Context, d *pluginsdk.ResourceDa } ownerObject := msgraph.DirectoryObject{ - ODataId: (*odata.Id)(utils.String(fmt.Sprintf("%s/v1.0/%s/directoryObjects/%s", + ODataId: (*odata.Id)(pointer.To(fmt.Sprintf("%s/v1.0/%s/directoryObjects/%s", client.BaseClient.Endpoint, tenantId, ownerId))), Id: &ownerId, } @@ -479,7 +498,7 @@ func servicePrincipalResourceCreate(ctx context.Context, d *pluginsdk.ResourceDa DirectoryObject: msgraph.DirectoryObject{ Id: servicePrincipal.ID(), }, - Description: utils.NullableString(d.Get("description").(string)), + Description: tf.NullableString(d.Get("description").(string)), }) if err != nil { if status == http.StatusNotFound { @@ -521,16 +540,16 @@ func servicePrincipalResourceUpdate(ctx context.Context, d *pluginsdk.ResourceDa properties := msgraph.ServicePrincipal{ DirectoryObject: msgraph.DirectoryObject{ - Id: utils.String(d.Id()), + Id: pointer.To(d.Id()), }, AlternativeNames: tf.ExpandStringSlicePtr(d.Get("alternative_names").(*pluginsdk.Set).List()), - AccountEnabled: utils.Bool(d.Get("account_enabled").(bool)), - AppRoleAssignmentRequired: utils.Bool(d.Get("app_role_assignment_required").(bool)), - Description: utils.NullableString(d.Get("description").(string)), - LoginUrl: utils.NullableString(d.Get("login_url").(string)), - Notes: utils.NullableString(d.Get("notes").(string)), + AccountEnabled: pointer.To(d.Get("account_enabled").(bool)), + AppRoleAssignmentRequired: pointer.To(d.Get("app_role_assignment_required").(bool)), + Description: tf.NullableString(d.Get("description").(string)), + LoginUrl: tf.NullableString(d.Get("login_url").(string)), + Notes: tf.NullableString(d.Get("notes").(string)), NotificationEmailAddresses: tf.ExpandStringSlicePtr(d.Get("notification_email_addresses").(*pluginsdk.Set).List()), - PreferredSingleSignOnMode: utils.NullableString(d.Get("preferred_single_sign_on_mode").(string)), + PreferredSingleSignOnMode: tf.NullableString(d.Get("preferred_single_sign_on_mode").(string)), SamlSingleSignOnSettings: expandSamlSingleSignOn(d.Get("saml_single_sign_on").([]interface{})), Tags: &tags, } @@ -554,7 +573,7 @@ func servicePrincipalResourceUpdate(ctx context.Context, d *pluginsdk.ResourceDa newOwners := make(msgraph.Owners, 0) for _, ownerId := range ownersToAdd { newOwners = append(newOwners, msgraph.DirectoryObject{ - ODataId: (*odata.Id)(utils.String(fmt.Sprintf("%s/v1.0/%s/directoryObjects/%s", + ODataId: (*odata.Id)(pointer.To(fmt.Sprintf("%s/v1.0/%s/directoryObjects/%s", client.BaseClient.Endpoint, tenantId, ownerId))), Id: &ownerId, }) @@ -608,6 +627,7 @@ func servicePrincipalResourceRead(ctx context.Context, d *pluginsdk.ResourceData tf.Set(d, "app_roles", helpers.ApplicationFlattenAppRoles(servicePrincipal.AppRoles)) tf.Set(d, "application_id", servicePrincipal.AppId) tf.Set(d, "application_tenant_id", servicePrincipal.AppOwnerOrganizationId) + tf.Set(d, "client_id", servicePrincipal.AppId) tf.Set(d, "description", servicePrincipal.Description) tf.Set(d, "display_name", servicePrincipal.DisplayName) tf.Set(d, "feature_tags", helpers.ApplicationFlattenFeatures(servicePrincipal.Tags, false)) @@ -664,11 +684,11 @@ func servicePrincipalResourceDelete(ctx context.Context, d *pluginsdk.ResourceDa client.BaseClient.DisableRetries = true if _, status, err := client.Get(ctx, servicePrincipalId, odata.Query{}); err != nil { if status == http.StatusNotFound { - return utils.Bool(false), nil + return pointer.To(false), nil } return nil, err } - return utils.Bool(true), nil + return pointer.To(true), nil }); err != nil { return tf.ErrorDiagF(err, "Waiting for deletion of group with object ID %q", servicePrincipalId) } diff --git a/internal/services/serviceprincipals/service_principal_resource_test.go b/internal/services/serviceprincipals/service_principal_resource_test.go index d4850a7610..febc875bd3 100644 --- a/internal/services/serviceprincipals/service_principal_resource_test.go +++ b/internal/services/serviceprincipals/service_principal_resource_test.go @@ -10,12 +10,12 @@ import ( "os" "testing" + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-sdk/sdk/odata" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-azuread/internal/acceptance" "github.com/hashicorp/terraform-provider-azuread/internal/acceptance/check" "github.com/hashicorp/terraform-provider-azuread/internal/clients" - "github.com/hashicorp/terraform-provider-azuread/internal/utils" ) type ServicePrincipalResource struct{} @@ -313,6 +313,21 @@ func TestAccServicePrincipal_fromApplicationTemplate(t *testing.T) { }) } +func TestAccServicePrincipal_deprecatedId(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_service_principal", "test") + r := ServicePrincipalResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.deprecatedId(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("use_existing"), + }) +} + func (r ServicePrincipalResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { client := clients.ServicePrincipals.ServicePrincipalsClient client.BaseClient.DisableRetries = true @@ -325,7 +340,7 @@ func (r ServicePrincipalResource) Exists(ctx context.Context, clients *clients.C } return nil, fmt.Errorf("failed to retrieve Service Principal with object ID %q: %+v", state.ID, err) } - return utils.Bool(servicePrincipal.ID() != nil && *servicePrincipal.ID() == state.ID), nil + return pointer.To(servicePrincipal.ID() != nil && *servicePrincipal.ID() == state.ID), nil } func (ServicePrincipalResource) basic(data acceptance.TestData) string { @@ -337,7 +352,7 @@ resource "azuread_application" "test" { } resource "azuread_service_principal" "test" { - application_id = azuread_application.test.application_id + client_id = azuread_application.test.client_id } `, data.RandomInteger) } @@ -415,7 +430,7 @@ func (r ServicePrincipalResource) complete(data acceptance.TestData) string { %[1]s resource "azuread_service_principal" "test" { - application_id = azuread_application.test.application_id + client_id = azuread_application.test.client_id account_enabled = false alternative_names = ["foo", "bar"] @@ -449,7 +464,7 @@ func (r ServicePrincipalResource) featureTags(data acceptance.TestData) string { %[1]s resource "azuread_service_principal" "test" { - application_id = azuread_application.test.application_id + client_id = azuread_application.test.client_id account_enabled = false alternative_names = ["foo", "bar"] @@ -483,7 +498,7 @@ func (r ServicePrincipalResource) noFeatureTags(data acceptance.TestData) string %[1]s resource "azuread_service_principal" "test" { - application_id = azuread_application.test.application_id + client_id = azuread_application.test.client_id account_enabled = false alternative_names = ["foo", "bar"] @@ -546,8 +561,8 @@ resource "azuread_application" "test" { } resource "azuread_service_principal" "test" { - application_id = azuread_application.test.application_id - owners = [] + client_id = azuread_application.test.client_id + owners = [] } `, r.templateThreeUsers(data), data.RandomInteger) } @@ -561,7 +576,7 @@ resource "azuread_application" "test" { } resource "azuread_service_principal" "test" { - application_id = azuread_application.test.application_id + client_id = azuread_application.test.client_id owners = [ azuread_user.testA.object_id, ] @@ -578,7 +593,7 @@ resource "azuread_application" "test" { } resource "azuread_service_principal" "test" { - application_id = azuread_application.test.application_id + client_id = azuread_application.test.client_id owners = [ azuread_user.testA.object_id, azuread_user.testB.object_id, @@ -604,8 +619,8 @@ resource "azuread_application" "owner" { } resource "azuread_service_principal" "owner" { - count = 27 - application_id = azuread_application.owner[count.index].application_id + count = 27 + client_id = azuread_application.owner[count.index].client_id } resource "azuread_user" "owner" { @@ -620,7 +635,7 @@ resource "azuread_application" "test" { } resource "azuread_service_principal" "test" { - application_id = azuread_application.test.application_id + client_id = azuread_application.test.client_id owners = flatten([ data.azuread_client_config.test.object_id, @@ -636,8 +651,8 @@ func (ServicePrincipalResource) useExisting(_ acceptance.TestData) string { provider "azuread" {} resource "azuread_service_principal" "msgraph" { - application_id = "00000003-0000-0000-c000-000000000000" # Microsoft Graph - use_existing = true + client_id = "00000003-0000-0000-c000-000000000000" # Microsoft Graph + use_existing = true } ` } @@ -655,9 +670,9 @@ resource "azuread_application" "test" { } resource "azuread_service_principal" "test" { - application_id = azuread_application.test.application_id - owners = [data.azuread_client_config.test.object_id] - use_existing = true + client_id = azuread_application.test.client_id + owners = [data.azuread_client_config.test.object_id] + use_existing = true } `, data.RandomInteger, testApplicationTemplateId) } @@ -679,15 +694,29 @@ resource "azuread_application" "testC" { } resource "azuread_service_principal" "testA" { - application_id = azuread_application.testA.application_id + client_id = azuread_application.testA.client_id } resource "azuread_service_principal" "testB" { - application_id = azuread_application.testB.application_id + client_id = azuread_application.testB.client_id } resource "azuread_service_principal" "testC" { - application_id = azuread_application.testC.application_id + client_id = azuread_application.testC.client_id +} +`, data.RandomInteger) +} + +func (ServicePrincipalResource) deprecatedId(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azuread" {} + +resource "azuread_application" "test" { + display_name = "acctestServicePrincipal-%[1]d" +} + +resource "azuread_service_principal" "test" { + application_id = azuread_application.test.application_id } `, data.RandomInteger) } diff --git a/internal/services/serviceprincipals/service_principals_data_source.go b/internal/services/serviceprincipals/service_principals_data_source.go index 48f6495a38..03cfa41e88 100644 --- a/internal/services/serviceprincipals/service_principals_data_source.go +++ b/internal/services/serviceprincipals/service_principals_data_source.go @@ -30,16 +30,29 @@ func servicePrincipalsDataSource() *pluginsdk.Resource { }, Schema: map[string]*pluginsdk.Schema{ + "client_ids": { + Description: "The client IDs of the applications associated with the service principals", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + ExactlyOneOf: []string{"client_ids", "application_ids", "display_names", "object_ids", "return_all"}, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsUUID, + }, + }, + "application_ids": { Description: "The application IDs (client IDs) of the applications associated with the service principals", Type: pluginsdk.TypeList, Optional: true, Computed: true, - ExactlyOneOf: []string{"application_ids", "display_names", "object_ids", "return_all"}, + ExactlyOneOf: []string{"client_ids", "application_ids", "display_names", "object_ids", "return_all"}, Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsUUID, }, + Deprecated: "The `application_ids` property has been replaced with the `client_ids` property and will be removed in version 3.0 of the AzureAD provider", }, "display_names": { @@ -47,10 +60,10 @@ func servicePrincipalsDataSource() *pluginsdk.Resource { Type: pluginsdk.TypeList, Optional: true, Computed: true, - ExactlyOneOf: []string{"application_ids", "display_names", "object_ids", "return_all"}, + ExactlyOneOf: []string{"client_ids", "application_ids", "display_names", "object_ids", "return_all"}, Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringIsNotEmpty, }, }, @@ -59,10 +72,10 @@ func servicePrincipalsDataSource() *pluginsdk.Resource { Type: pluginsdk.TypeList, Optional: true, Computed: true, - ExactlyOneOf: []string{"application_ids", "display_names", "object_ids", "return_all"}, + ExactlyOneOf: []string{"client_ids", "application_ids", "display_names", "object_ids", "return_all"}, Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + Type: pluginsdk.TypeString, + ValidateFunc: validation.IsUUID, }, }, @@ -80,7 +93,7 @@ func servicePrincipalsDataSource() *pluginsdk.Resource { Optional: true, Default: false, ConflictsWith: []string{"ignore_missing"}, - ExactlyOneOf: []string{"application_ids", "display_names", "object_ids", "return_all"}, + ExactlyOneOf: []string{"client_ids", "application_ids", "display_names", "object_ids", "return_all"}, }, "service_principals": { @@ -105,6 +118,7 @@ func servicePrincipalsDataSource() *pluginsdk.Resource { Description: "The application ID (client ID) for the associated application", Type: pluginsdk.TypeString, Computed: true, + Deprecated: "The `application_id` attribute has been replaced by the `client_id` attribute and will be removed in version 3.0 of the AzureAD provider", }, "application_tenant_id": { @@ -113,6 +127,12 @@ func servicePrincipalsDataSource() *pluginsdk.Resource { Computed: true, }, + "client_id": { + Description: "The application ID (client ID) for the associated application", + Type: pluginsdk.TypeString, + Computed: true, + }, + "display_name": { Description: "The display name of the application associated with this service principal", Type: pluginsdk.TypeString, @@ -183,6 +203,12 @@ func servicePrincipalsDataSourceRead(ctx context.Context, d *pluginsdk.ResourceD ignoreMissing := d.Get("ignore_missing").(bool) returnAll := d.Get("return_all").(bool) + var clientIdsToSearch []string + if v, ok := d.Get("client_ids").([]interface{}); ok && len(v) > 0 { + clientIdsToSearch = tf.ExpandStringSlice(v) + } else if v, ok := d.Get("application_ids").([]interface{}); ok && len(v) > 0 { + clientIdsToSearch = tf.ExpandStringSlice(v) + } if returnAll { result, _, err := client.List(ctx, odata.Query{}) if err != nil { @@ -196,9 +222,9 @@ func servicePrincipalsDataSourceRead(ctx context.Context, d *pluginsdk.ResourceD } servicePrincipals = append(servicePrincipals, *result...) - } else if applicationIds, ok := d.Get("application_ids").([]interface{}); ok && len(applicationIds) > 0 { - expectedCount = len(applicationIds) - for _, v := range applicationIds { + } else if len(clientIdsToSearch) > 0 { + expectedCount = len(clientIdsToSearch) + for _, v := range clientIdsToSearch { query := odata.Query{ Filter: fmt.Sprintf("appId eq '%s'", v), } @@ -273,7 +299,7 @@ func servicePrincipalsDataSourceRead(ctx context.Context, d *pluginsdk.ResourceD return tf.ErrorDiagF(fmt.Errorf("Expected: %d, Actual: %d", expectedCount, len(servicePrincipals)), "Unexpected number of service principals returned") } - applicationIds := make([]string, 0) + clientIds := make([]string, 0) displayNames := make([]string, 0) objectIds := make([]string, 0) spList := make([]map[string]interface{}, 0) @@ -285,7 +311,7 @@ func servicePrincipalsDataSourceRead(ctx context.Context, d *pluginsdk.ResourceD objectIds = append(objectIds, *s.ID()) displayNames = append(displayNames, *s.DisplayName) if s.AppId != nil { - applicationIds = append(applicationIds, *s.AppId) + clientIds = append(clientIds, *s.AppId) } servicePrincipalNames := make([]string, 0) @@ -304,6 +330,7 @@ func servicePrincipalsDataSourceRead(ctx context.Context, d *pluginsdk.ResourceD sp["app_role_assignment_required"] = s.AppRoleAssignmentRequired sp["application_id"] = s.AppId sp["application_tenant_id"] = s.AppOwnerOrganizationId + sp["client_id"] = s.AppId sp["object_id"] = s.ID() sp["preferred_single_sign_on_mode"] = s.PreferredSingleSignOnMode sp["saml_metadata_url"] = s.SamlMetadataUrl @@ -321,7 +348,8 @@ func servicePrincipalsDataSourceRead(ctx context.Context, d *pluginsdk.ResourceD } d.SetId("serviceprincipals#" + base64.URLEncoding.EncodeToString(h.Sum(nil))) - tf.Set(d, "application_ids", applicationIds) + tf.Set(d, "application_ids", clientIds) + tf.Set(d, "client_ids", clientIds) tf.Set(d, "display_names", displayNames) tf.Set(d, "object_ids", objectIds) tf.Set(d, "service_principals", spList) diff --git a/internal/services/serviceprincipals/service_principals_data_source_test.go b/internal/services/serviceprincipals/service_principals_data_source_test.go index a1b4b1db3c..e131d764df 100644 --- a/internal/services/serviceprincipals/service_principals_data_source_test.go +++ b/internal/services/serviceprincipals/service_principals_data_source_test.go @@ -13,13 +13,27 @@ import ( type ServicePrincipalsDataSource struct{} -func TestAccServicePrincipalsDataSource_byApplicationIds(t *testing.T) { +func TestAccServicePrincipalsDataSource_byClientIds(t *testing.T) { data := acceptance.BuildTestData(t, "data.azuread_service_principals", "test") data.DataSourceTest(t, []acceptance.TestStep{{ - Config: ServicePrincipalsDataSource{}.byApplicationIds(data), + Config: ServicePrincipalsDataSource{}.byClientIds(data), Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).Key("application_ids.#").HasValue("2"), + check.That(data.ResourceName).Key("client_ids.#").HasValue("2"), + check.That(data.ResourceName).Key("display_names.#").HasValue("2"), + check.That(data.ResourceName).Key("object_ids.#").HasValue("2"), + check.That(data.ResourceName).Key("service_principals.#").HasValue("2"), + ), + }}) +} + +func TestAccServicePrincipalsDataSource_byClientIdsWithIgnoreMissing(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azuread_service_principals", "test") + + data.DataSourceTest(t, []acceptance.TestStep{{ + Config: ServicePrincipalsDataSource{}.byClientIdsWithIgnoreMissing(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("client_ids.#").HasValue("2"), check.That(data.ResourceName).Key("display_names.#").HasValue("2"), check.That(data.ResourceName).Key("object_ids.#").HasValue("2"), check.That(data.ResourceName).Key("service_principals.#").HasValue("2"), @@ -27,11 +41,11 @@ func TestAccServicePrincipalsDataSource_byApplicationIds(t *testing.T) { }}) } -func TestAccServicePrincipalsDataSource_byApplicationIdsWithIgnoreMissing(t *testing.T) { +func TestAccServicePrincipalsDataSource_byDeprecatedApplicationIds(t *testing.T) { data := acceptance.BuildTestData(t, "data.azuread_service_principals", "test") data.DataSourceTest(t, []acceptance.TestStep{{ - Config: ServicePrincipalsDataSource{}.byApplicationIdsWithIgnoreMissing(data), + Config: ServicePrincipalsDataSource{}.byDeprecatedApplicationIds(data), Check: acceptance.ComposeTestCheckFunc( check.That(data.ResourceName).Key("application_ids.#").HasValue("2"), check.That(data.ResourceName).Key("display_names.#").HasValue("2"), @@ -180,33 +194,46 @@ data "azuread_service_principals" "test" { `, ServicePrincipalResource{}.threeServicePrincipalsABC(data)) } -func (ServicePrincipalsDataSource) byApplicationIds(data acceptance.TestData) string { +func (ServicePrincipalsDataSource) byClientIds(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s data "azuread_service_principals" "test" { - application_ids = [ - azuread_service_principal.testA.application_id, - azuread_service_principal.testB.application_id, + client_ids = [ + azuread_service_principal.testA.client_id, + azuread_service_principal.testB.client_id, ] } `, ServicePrincipalResource{}.threeServicePrincipalsABC(data)) } -func (ServicePrincipalsDataSource) byApplicationIdsWithIgnoreMissing(data acceptance.TestData) string { +func (ServicePrincipalsDataSource) byClientIdsWithIgnoreMissing(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s data "azuread_service_principals" "test" { ignore_missing = true + client_ids = [ + azuread_service_principal.testA.client_id, + "e0000000-0000-0000-0000-000000000000", + azuread_service_principal.testB.client_id, + ] +} +`, ServicePrincipalResource{}.threeServicePrincipalsABC(data), data.RandomInteger) +} + +func (ServicePrincipalsDataSource) byDeprecatedApplicationIds(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +data "azuread_service_principals" "test" { application_ids = [ azuread_service_principal.testA.application_id, - "e0000000-0000-0000-0000-000000000000", azuread_service_principal.testB.application_id, ] } -`, ServicePrincipalResource{}.threeServicePrincipalsABC(data), data.RandomInteger) +`, ServicePrincipalResource{}.threeServicePrincipalsABC(data)) } func (ServicePrincipalsDataSource) noNames() string { diff --git a/internal/services/serviceprincipals/serviceprincipals.go b/internal/services/serviceprincipals/serviceprincipals.go index 5d2694507e..22db232205 100644 --- a/internal/services/serviceprincipals/serviceprincipals.go +++ b/internal/services/serviceprincipals/serviceprincipals.go @@ -46,7 +46,7 @@ func flattenSamlSingleSignOn(in *msgraph.SamlSingleSignOnSettings) []map[string] }} } -func findByAppId(ctx context.Context, client *msgraph.ServicePrincipalsClient, appId string) (*msgraph.ServicePrincipal, error) { +func findByClientId(ctx context.Context, client *msgraph.ServicePrincipalsClient, appId string) (*msgraph.ServicePrincipal, error) { var servicePrincipal *msgraph.ServicePrincipal result, _, err := client.List(ctx, odata.Query{Filter: fmt.Sprintf("appId eq '%s'", appId)}) @@ -65,7 +65,7 @@ func findByAppId(ctx context.Context, client *msgraph.ServicePrincipalsClient, a return servicePrincipal, nil } -func findByAppIdWithTimeout(ctx context.Context, timeout time.Duration, client *msgraph.ServicePrincipalsClient, appId string) (*msgraph.ServicePrincipal, error) { +func findByClientIdWithTimeout(ctx context.Context, timeout time.Duration, client *msgraph.ServicePrincipalsClient, appId string) (*msgraph.ServicePrincipal, error) { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, timeout) defer cancel()