diff --git a/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md b/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md index 3f8b4a7113bd..d9186f7887b6 100644 --- a/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md +++ b/sdk/sql/azure-resourcemanager-sql/CHANGELOG.md @@ -4,6 +4,9 @@ ### Features Added +- Supported `withAzureActiveDirectoryOnlyAuthentication` and `withExternalActiveDirectoryAdministrator` for `SqlServer`. +- Supported `withManagedIdentity` for `SqlDatabase` import and export. + ### Breaking Changes ### Bugs Fixed @@ -363,21 +366,21 @@ ### Breaking Changes -- Removed `NEW` from `SecurityAlertPolicyState`. The constant is non-functional. +- Removed `NEW` from `SecurityAlertPolicyState`. The constant is non-functional. - Removed `withPolicyNew` method from `SqlDatabaseThreatDetectionPolicy` since `NEW` is no longer supported in `SecurityAlertPolicyState`. - Removed `nextResetTime` and `resourceName` methods from `ServerMetric` and `SqlDatabase`. The methods are non-functional. - Removed `listMetricsDefinitions` and `listMetrics` methods from `SqlDatabase`. Metrics in SQL have been replaced by the Azure monitor shoebox metrics API. Not in SQL any more. - Removed `listServiceTierAdvisors` method from `SqlDatabase`. It's no longer supported. - Removed class `ElasticPoolDatabaseActivity`. It's removed from service definition. -- Removed `listDatabaseActivities`, `listDatabaseMetricDefinitions` and `listDatabaseMetrics` methods from `SqlElasticPool`. -- Removed `elasticPoolName` and `serviceLevelObjective` methods from `SqlRestorableDroppedDatabase`. +- Removed `listDatabaseActivities`, `listDatabaseMetricDefinitions` and `listDatabaseMetrics` methods from `SqlElasticPool`. +- Removed `elasticPoolName` and `serviceLevelObjective` methods from `SqlRestorableDroppedDatabase`. - Removed `getServiceObjective`, `listRecommendedElasticPools`, `listServiceObjectives` methods from `SqlServer`. - Removed `withCreationDate` and `withThumbprint` from SqlServerKeyOperations. The properties are no longer mutable. - Renamed class from `TransparentDataEncryptionInner` to `LogicalDatabaseTransparentDataEncryptionInner`. - Removed class `TransparentDataEncryptionActivity`. The class is no longer functional. - Removed `listActivities` from `TransparentDataEncryption` since `TransparentDataEncryptionActivity` is removed. - Renamed `TransparentDataEncryptionStatus` to `TransparentDataEncryptionState`. -- Removed `location`, `requestedDatabaseDtuCap`, `requestedDatabaseDtuGuarantee`, `requestedDatabaseDtuMax`, `requestedDatabaseDtuMin`, +- Removed `location`, `requestedDatabaseDtuCap`, `requestedDatabaseDtuGuarantee`, `requestedDatabaseDtuMax`, `requestedDatabaseDtuMin`, `requestedDtu`, `requestedDtuGuarantee` and `requestedElasticPoolName`, `requestedStorageLimitInGB` and `requestedStorageLimitInMB` methods from `ElasticPoolActivity`. The properties are no longer functional. - Renamed class from `ElasticPoolActivityInner` to `ElasticPoolOperationInner`. - Removed `readReplicaCount` and `withReadReplicaCount` from `DatabaseUpdate`. The property is non-functional. diff --git a/sdk/sql/azure-resourcemanager-sql/assets.json b/sdk/sql/azure-resourcemanager-sql/assets.json index 74882f72acc8..ac2251c2df50 100644 --- a/sdk/sql/azure-resourcemanager-sql/assets.json +++ b/sdk/sql/azure-resourcemanager-sql/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "java", "TagPrefix": "java/sql/azure-resourcemanager-sql", - "Tag": "java/sql/azure-resourcemanager-sql_2dccdeacd3" + "Tag": "java/sql/azure-resourcemanager-sql_a84e1b066f" } diff --git a/sdk/sql/azure-resourcemanager-sql/pom.xml b/sdk/sql/azure-resourcemanager-sql/pom.xml index 7a8efaf5b219..523e118f5156 100644 --- a/sdk/sql/azure-resourcemanager-sql/pom.xml +++ b/sdk/sql/azure-resourcemanager-sql/pom.xml @@ -41,6 +41,9 @@ 0.10 + --add-reads com.azure.resourcemanager.sql=com.azure.resourcemanager.authorization + --add-reads com.azure.resourcemanager.sql=com.azure.resourcemanager.msi + --add-opens com.azure.resourcemanager.sql/com.azure.resourcemanager.sql=ALL-UNNAMED --add-opens com.azure.resourcemanager.resources/com.azure.resourcemanager.resources=ALL-UNNAMED --add-opens com.azure.resourcemanager.resources/com.azure.resourcemanager.resources.fluentcore.arm=ALL-UNNAMED @@ -70,6 +73,18 @@ azure-resourcemanager-storage 2.55.5 + + com.azure.resourcemanager + azure-resourcemanager-msi + 2.53.8 + test + + + com.azure.resourcemanager + azure-resourcemanager-authorization + 2.53.9 + test + org.junit.jupiter junit-jupiter-engine diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java index 55f1c3eb6992..b6d1dfadb82c 100644 --- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java +++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseExportRequestImpl.java @@ -3,6 +3,7 @@ package com.azure.resourcemanager.sql.implementation; import com.azure.core.management.exception.ManagementException; +import com.azure.core.util.CoreUtils; import com.azure.resourcemanager.resources.fluentcore.dag.FunctionalTaskItem; import com.azure.resourcemanager.resources.fluentcore.model.Creatable; import com.azure.resourcemanager.resources.fluentcore.model.Indexable; @@ -66,26 +67,37 @@ public SqlDatabaseExportRequestImpl exportTo(String storageUri) { private Mono getOrCreateStorageAccountContainer(final StorageAccount storageAccount, final String containerName, final String fileName, final FunctionalTaskItem.Context context) { final SqlDatabaseExportRequestImpl self = this; + self.inner.withStorageUri( + String.format("%s%s/%s", storageAccount.endPoints().primary().blob(), containerName, fileName)); + BlobContainers blobContainers = this.sqlServerManager.storageManager().blobContainers(); + Mono container + = blobContainers.getAsync(parent().resourceGroupName(), storageAccount.name(), containerName) + .map(blobContainer -> (Indexable) blobContainer) + .onErrorResume(error -> { + if (error instanceof ManagementException) { + if (((ManagementException) error).getResponse().getStatusCode() == 404) { + return blobContainers.defineContainer(containerName) + .withExistingStorageAccount(parent().resourceGroupName(), storageAccount.name()) + .withPublicAccess(PublicAccess.NONE) + .createAsync() + .map(blobContainer -> (Indexable) blobContainer); + } + } + return Mono.error(error); + }); + + if (!storageAccount.isSharedKeyAccessAllowed() + // self.inner.storageKey could be set before, e.g. with managed identity ID + || !CoreUtils.isNullOrEmpty(self.inner.storageKey())) { + return container; + } + return storageAccount.getKeysAsync() .flatMap(storageAccountKeys -> Mono.justOrEmpty(storageAccountKeys.stream().findFirst())) .flatMap(storageAccountKey -> { - self.inner.withStorageUri( - String.format("%s%s/%s", storageAccount.endPoints().primary().blob(), containerName, fileName)); self.inner.withStorageKeyType(StorageKeyType.STORAGE_ACCESS_KEY); self.inner.withStorageKey(storageAccountKey.value()); - BlobContainers blobContainers = this.sqlServerManager.storageManager().blobContainers(); - return blobContainers.getAsync(parent().resourceGroupName(), storageAccount.name(), containerName) - .onErrorResume(error -> { - if (error instanceof ManagementException) { - if (((ManagementException) error).getResponse().getStatusCode() == 404) { - return blobContainers.defineContainer(containerName) - .withExistingStorageAccount(parent().resourceGroupName(), storageAccount.name()) - .withPublicAccess(PublicAccess.NONE) - .createAsync(); - } - } - return Mono.error(error); - }); + return container; }); } @@ -158,6 +170,20 @@ public SqlDatabaseExportRequestImpl withActiveDirectoryLoginAndPassword(String a return this.withLoginAndPassword(administratorLogin, administratorPassword); } + @Override + public SqlDatabaseExportRequestImpl withManagedIdentity(String managedIdentityResourceId) { + Objects.requireNonNull(managedIdentityResourceId); + this.inner.withAuthenticationType(AuthenticationType.MANAGED_IDENTITY.toString()); + this.inner.withAdministratorLogin(managedIdentityResourceId); + // No administrator password is required for managed identity authentication. + this.inner.withAdministratorLoginPassword(null); + + // Use the same MI for storage account access. + this.inner.withStorageKeyType(StorageKeyType.MANAGED_IDENTITY); + this.inner.withStorageKey(managedIdentityResourceId); + return this; + } + SqlDatabaseExportRequestImpl withLoginAndPassword(String administratorLogin, String administratorPassword) { this.inner.withAdministratorLogin(administratorLogin); this.inner.withAdministratorLoginPassword(administratorPassword); diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseForElasticPoolImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseForElasticPoolImpl.java index 301e9aeb3fc1..5ad5ce0c85e4 100644 --- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseForElasticPoolImpl.java +++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseForElasticPoolImpl.java @@ -108,6 +108,12 @@ public SqlDatabaseForElasticPoolImpl withActiveDirectoryLoginAndPassword(String return this; } + @Override + public SqlDatabaseForElasticPoolImpl withManagedIdentity(String managedIdentityResourceId) { + this.sqlDatabase.withManagedIdentity(managedIdentityResourceId); + return this; + } + @Override public SqlDatabaseForElasticPoolImpl fromRestorePoint(RestorePoint restorePoint) { this.sqlDatabase.fromRestorePoint(restorePoint); diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImpl.java index 075702b38c3b..74fdc4b5379e 100644 --- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImpl.java +++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImpl.java @@ -6,6 +6,7 @@ import com.azure.core.http.rest.PagedFlux; import com.azure.core.http.rest.PagedIterable; import com.azure.core.management.Region; +import com.azure.core.util.CoreUtils; import com.azure.resourcemanager.resources.fluentcore.arm.ResourceId; import com.azure.resourcemanager.resources.fluentcore.arm.ResourceUtils; import com.azure.resourcemanager.resources.fluentcore.arm.models.implementation.ExternalChildResourceImpl; @@ -717,8 +718,11 @@ public SqlDatabaseImpl importFrom(final StorageAccount storageAccount, final Str .flatMap(storageAccountKey -> { self.importRequestInner.withStorageUri( String.format("%s%s/%s", storageAccount.endPoints().primary().blob(), containerName, fileName)); - self.importRequestInner.withStorageKeyType(StorageKeyType.STORAGE_ACCESS_KEY); - self.importRequestInner.withStorageKey(storageAccountKey.value()); + if (storageAccount.isSharedKeyAccessAllowed() + && CoreUtils.isNullOrEmpty(self.importRequestInner.storageKey())) { + self.importRequestInner.withStorageKeyType(StorageKeyType.STORAGE_ACCESS_KEY); + self.importRequestInner.withStorageKey(storageAccountKey.value()); + } return context.voidMono(); })); return this; @@ -756,6 +760,20 @@ public SqlDatabaseImpl withActiveDirectoryLoginAndPassword(String administratorL return this; } + @Override + public SqlDatabaseImpl withManagedIdentity(String managedIdentityResourceId) { + Objects.requireNonNull(managedIdentityResourceId); + this.importRequestInner.withAuthenticationType(AuthenticationType.MANAGED_IDENTITY.toString()); + this.importRequestInner.withAdministratorLogin(managedIdentityResourceId); + // No administrator password is required for managed identity authentication. + this.importRequestInner.withAdministratorLoginPassword(null); + + // Use the same MI for storage account access. + this.importRequestInner.withStorageKeyType(StorageKeyType.MANAGED_IDENTITY); + this.importRequestInner.withStorageKey(managedIdentityResourceId); + return this; + } + @Override public SqlDatabaseImpl fromRestorePoint(RestorePoint restorePoint) { return fromRestorePoint(restorePoint, restorePoint.earliestRestoreDate()); diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java index 4865c33a2586..ad9c4a23f888 100644 --- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java +++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlDatabaseImportRequestImpl.java @@ -2,6 +2,7 @@ // Licensed under the MIT License. package com.azure.resourcemanager.sql.implementation; +import com.azure.core.util.CoreUtils; import com.azure.resourcemanager.resources.fluentcore.dag.FunctionalTaskItem; import com.azure.resourcemanager.resources.fluentcore.model.Indexable; import com.azure.resourcemanager.resources.fluentcore.model.implementation.ExecutableImpl; @@ -55,11 +56,18 @@ public Mono executeWorkAsync() { private Mono getOrCreateStorageAccountContainer(final StorageAccount storageAccount, final String containerName, final String fileName, final FunctionalTaskItem.Context context) { final SqlDatabaseImportRequestImpl self = this; + self.inner.withStorageUri( + String.format("%s%s/%s", storageAccount.endPoints().primary().blob(), containerName, fileName)); + + if (!storageAccount.isSharedKeyAccessAllowed() + // self.inner.storageKey could be set before, e.g. with managed identity ID + || !CoreUtils.isNullOrEmpty(self.inner.storageKey())) { + return context.voidMono(); + } + return storageAccount.getKeysAsync() .flatMap(storageAccountKeys -> Mono.justOrEmpty(storageAccountKeys.stream().findFirst())) .flatMap(storageAccountKey -> { - self.inner.withStorageUri( - String.format("%s%s/%s", storageAccount.endPoints().primary().blob(), containerName, fileName)); self.inner.withStorageKeyType(StorageKeyType.STORAGE_ACCESS_KEY); self.inner.withStorageKey(storageAccountKey.value()); return context.voidMono(); @@ -117,6 +125,20 @@ public SqlDatabaseImportRequestImpl withActiveDirectoryLoginAndPassword(String a return this.withLoginAndPassword(administratorLogin, administratorPassword); } + @Override + public SqlDatabaseImportRequestImpl withManagedIdentity(String managedIdentityResourceId) { + Objects.requireNonNull(managedIdentityResourceId); + this.inner.withAuthenticationType(AuthenticationType.MANAGED_IDENTITY.toString()); + this.inner.withAdministratorLogin(managedIdentityResourceId); + // No administrator password is required for managed identity authentication. + this.inner.withAdministratorLoginPassword(null); + + // Use the same MI for storage account access. + this.inner.withStorageKeyType(StorageKeyType.MANAGED_IDENTITY); + this.inner.withStorageKey(managedIdentityResourceId); + return this; + } + SqlDatabaseImportRequestImpl withLoginAndPassword(String administratorLogin, String administratorPassword) { this.inner.withAdministratorLogin(administratorLogin); this.inner.withAdministratorLoginPassword(administratorPassword); diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java index dd6a86864e17..927f72ecd469 100644 --- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java +++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/implementation/SqlServerImpl.java @@ -12,6 +12,7 @@ import com.azure.resourcemanager.resources.fluentcore.arm.models.implementation.GroupableResourceImpl; import com.azure.resourcemanager.resources.fluentcore.dag.FunctionalTaskItem; import com.azure.resourcemanager.resources.fluentcore.utils.PagedConverter; +import com.azure.resourcemanager.resources.fluentcore.utils.ResourceManagerUtils; import com.azure.resourcemanager.sql.SqlServerManager; import com.azure.resourcemanager.sql.fluent.models.RestorableDroppedDatabaseInner; import com.azure.resourcemanager.sql.fluent.models.ServerAutomaticTuningInner; @@ -21,8 +22,11 @@ import com.azure.resourcemanager.sql.models.AdministratorName; import com.azure.resourcemanager.sql.models.AdministratorType; import com.azure.resourcemanager.sql.models.IdentityType; +import com.azure.resourcemanager.sql.models.PrincipalType; import com.azure.resourcemanager.sql.models.ResourceIdentity; +import com.azure.resourcemanager.sql.models.ServerExternalAdministrator; import com.azure.resourcemanager.sql.models.ServerMetric; +import com.azure.resourcemanager.sql.models.ServerNetworkAccessFlag; import com.azure.resourcemanager.sql.models.SqlDatabaseOperations; import com.azure.resourcemanager.sql.models.SqlElasticPoolOperations; import com.azure.resourcemanager.sql.models.SqlEncryptionProtectorOperations; @@ -31,18 +35,20 @@ import com.azure.resourcemanager.sql.models.SqlFirewallRuleOperations; import com.azure.resourcemanager.sql.models.SqlRestorableDroppedDatabase; import com.azure.resourcemanager.sql.models.SqlServer; -import com.azure.resourcemanager.sql.models.ServerNetworkAccessFlag; import com.azure.resourcemanager.sql.models.SqlServerAutomaticTuning; import com.azure.resourcemanager.sql.models.SqlServerDnsAliasOperations; import com.azure.resourcemanager.sql.models.SqlServerKeyOperations; import com.azure.resourcemanager.sql.models.SqlServerSecurityAlertPolicyOperations; import com.azure.resourcemanager.sql.models.SqlVirtualNetworkRule; import com.azure.resourcemanager.sql.models.SqlVirtualNetworkRuleOperations; +import com.azure.resourcemanager.sql.models.UserIdentity; import reactor.core.publisher.Mono; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Objects; import java.util.UUID; /** Implementation for SqlServer and its parent interfaces. */ @@ -321,6 +327,12 @@ public void removeActiveDirectoryAdministrator() { .block(); } + @Override + public boolean isAzureActiveDirectoryOnlyAuthenticationEnabled() { + return this.innerModel().administrators() != null + && ResourceManagerUtils.toPrimitiveBoolean(this.innerModel().administrators().azureADOnlyAuthentication()); + } + @Override public SqlServerAutomaticTuning getServerAutomaticTuning() { ServerAutomaticTuningInner serverAutomaticTuningInner @@ -358,6 +370,37 @@ public SqlServerImpl withAdministratorPassword(String administratorLoginPassword return this; } + @Override + public SqlServerImpl withAzureActiveDirectoryOnlyAuthentication() { + if (this.innerModel().administrators() == null) { + this.innerModel() + .withAdministrators( + new ServerExternalAdministrator().withAdministratorType(AdministratorType.ACTIVE_DIRECTORY) + .withTenantId(UUID.fromString(this.manager().tenantId()))); + } + this.innerModel().administrators().withAzureADOnlyAuthentication(true); + return this; + } + + @Override + public SqlServerImpl withExternalActiveDirectoryAdministrator(String adminLogin, String sid, + PrincipalType principalType) { + if (this.innerModel().administrators() == null) { + this.innerModel() + .withAdministrators( + new ServerExternalAdministrator().withAdministratorType(AdministratorType.ACTIVE_DIRECTORY) + .withTenantId(UUID.fromString(this.manager().tenantId()))); + } else { + this.innerModel().administrators().withAdministratorType(AdministratorType.ACTIVE_DIRECTORY); + } + this.innerModel() + .administrators() + .withLogin(adminLogin) + .withSid(UUID.fromString(sid)) + .withPrincipalType(principalType); + return this; + } + @Override public SqlServerImpl withoutAccessFromAzureServices() { allowAzureServicesAccess = false; @@ -484,10 +527,50 @@ public SqlServerImpl withoutDatabase(String databaseName) { @Override public SqlServerImpl withSystemAssignedManagedServiceIdentity() { - this.innerModel().withIdentity(new ResourceIdentity().withType(IdentityType.SYSTEM_ASSIGNED)); + initIdentity(IdentityType.SYSTEM_ASSIGNED); + return this; + } + + @Override + public SqlServerImpl withPrimaryUserAssignedManagedServiceIdentity(String identityResourceId) { + Objects.requireNonNull(identityResourceId, "'identityResourceId' cannot be null."); + initIdentity(IdentityType.USER_ASSIGNED); + this.innerModel().identity().userAssignedIdentities().put(identityResourceId, new UserIdentity()); + this.innerModel().withPrimaryUserAssignedIdentityId(identityResourceId); return this; } + /** + * Initializes or merges the identity on the inner model so that the requested + * {@code desiredType} is present. When both SYSTEM_ASSIGNED and USER_ASSIGNED + * are requested (in any order), the resulting type is SYSTEM_ASSIGNED_USER_ASSIGNED. + * Existing user-assigned identities are preserved across calls. + */ + private void initIdentity(IdentityType desiredType) { + ResourceIdentity existing = this.innerModel().identity(); + if (existing == null || existing.type() == null || existing.type() == IdentityType.NONE) { + ResourceIdentity identity = new ResourceIdentity().withType(desiredType); + if (desiredType == IdentityType.USER_ASSIGNED + || desiredType == IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED) { + identity.withUserAssignedIdentities(new HashMap<>()); + } + this.innerModel().withIdentity(identity); + } else if (existing.type() == desiredType || existing.type() == IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED) { + // Already has the desired type (or already combined) — nothing to change. + if (existing.userAssignedIdentities() == null + && (existing.type() == IdentityType.USER_ASSIGNED + || existing.type() == IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED)) { + existing.withUserAssignedIdentities(new HashMap<>()); + } + } else { + // One of SYSTEM_ASSIGNED + USER_ASSIGNED → combine + existing.withType(IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED); + if (existing.userAssignedIdentities() == null) { + existing.withUserAssignedIdentities(new HashMap<>()); + } + } + } + @Override public SqlServerImpl enablePublicNetworkAccess() { this.innerModel().withPublicNetworkAccess(ServerNetworkAccessFlag.ENABLED); diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/AuthenticationType.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/AuthenticationType.java index dce6c2bd6f67..4ae7d2de471d 100644 --- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/AuthenticationType.java +++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/AuthenticationType.java @@ -9,7 +9,10 @@ public enum AuthenticationType { SQL("SQL"), /** Enum value ADPassword. */ - ADPASSWORD("ADPassword"); + ADPASSWORD("ADPassword"), + + /** Enum value ManagedIdentity. */ + MANAGED_IDENTITY("ManagedIdentity"); /** The actual serialized value for a AuthenticationType instance. */ private final String value; diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java index c45a29cf3756..ba9d730a1a55 100644 --- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java +++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabase.java @@ -503,6 +503,26 @@ interface WithAuthentication { */ SqlDatabase.DefinitionStages.WithAttachAllOptions withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword); + + /** + * Specifies the user-assigned managed identity (UAMI) used to authenticate to the SQL database for import. + * + *

The SQL server must have the specified UAMI assigned (and typically set as its primary identity), the + * UAMI must be granted the appropriate role on the source storage account (e.g. {@code Storage Blob Data + * Contributor}), and it must be mapped to a database user with the required privileges. When this method + * is used, no administrator password is sent to the service.

+ * + *

This method is for user-assigned managed identity. To use the server's system-assigned managed + * identity, enable SAMI on the server and pass the SAMI's resource identifier as well. The signed-in + * user's object ID is not a managed identity and cannot be used here.

+ * + * @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use + * @return next definition stage + */ + default SqlDatabase.DefinitionStages.WithAttachAllOptions + withManagedIdentity(String managedIdentityResourceId) { + throw new UnsupportedOperationException("[withManagedIdentity] is not supported in " + getClass()); + } } /** @@ -581,6 +601,26 @@ interface WithAuthenticationAfterElasticPool { */ SqlDatabase.DefinitionStages.WithAttachFinal withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword); + + /** + * Specifies the user-assigned managed identity (UAMI) used to authenticate to the SQL database for import. + * + *

The SQL server must have the specified UAMI assigned (and typically set as its primary identity), the + * UAMI must be granted the appropriate role on the source storage account (e.g. {@code Storage Blob Data + * Contributor}), and it must be mapped to a database user with the required privileges. When this method + * is used, no administrator password is sent to the service.

+ * + *

This method is for user-assigned managed identity. To use the server's system-assigned managed + * identity, enable SAMI on the server and pass the SAMI's resource identifier as well. The signed-in + * user's object ID is not a managed identity and cannot be used here.

+ * + * @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use + * @return next definition stage + */ + default SqlDatabase.DefinitionStages.WithAttachFinal + withManagedIdentity(String managedIdentityResourceId) { + throw new UnsupportedOperationException("[withManagedIdentity] is not supported in " + getClass()); + } } /** diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java index fd40a3b16d64..0a4d71cdcc57 100644 --- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java +++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseExportRequest.java @@ -97,6 +97,26 @@ interface WithAuthenticationTypeAndLoginPassword { */ SqlDatabaseExportRequest.DefinitionStages.WithExecute withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword); + + /** + * Sets the user-assigned managed identity (UAMI) used to authenticate to the SQL database for export. + * + *

The SQL server must have the specified UAMI assigned (and typically set as its primary identity), the + * UAMI must be granted the appropriate role on the target storage account (e.g. {@code Storage Blob Data + * Contributor}), and it must be mapped to a database user with the required privileges. When this method + * is used, no administrator password is sent to the service.

+ * + *

This method is for user-assigned managed identity. System-assigned managed identity is not supported. + * See Limitations

+ * + * @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use + * for both SQL and storage access + * @return next definition stage + */ + default SqlDatabaseExportRequest.DefinitionStages.WithExecute + withManagedIdentity(String managedIdentityResourceId) { + throw new UnsupportedOperationException("[withManagedIdentity] is not supported in " + getClass()); + } } /** diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java index 21eac792c766..356b4baaa732 100644 --- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java +++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseImportRequest.java @@ -86,6 +86,26 @@ interface WithAuthenticationTypeAndLoginPassword { */ SqlDatabaseImportRequest.DefinitionStages.WithExecute withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword); + + /** + * Specifies the user-assigned managed identity (UAMI) used to authenticate to the SQL database for import. + * + *

The SQL server must have the specified UAMI assigned (and typically set as its primary identity), the + * UAMI must be granted the appropriate role on the source storage account (e.g. {@code Storage Blob Data + * Contributor}), and it must be mapped to a database user with the required privileges. When this method + * is used, no administrator password is sent to the service.

+ * + *

This method is for user-assigned managed identity. System-assigned managed identity is not supported. + * See Limitations

+ * + * @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use + * for both SQL and storage access + * @return next definition stage + */ + default SqlDatabaseImportRequest.DefinitionStages.WithExecute + withManagedIdentity(String managedIdentityResourceId) { + throw new UnsupportedOperationException("[withManagedIdentity] is not supported in " + getClass()); + } } /** diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java index 75edc555b91a..a1dd28fa1016 100644 --- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java +++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlDatabaseOperations.java @@ -187,6 +187,26 @@ interface WithAuthentication { */ SqlDatabaseOperations.DefinitionStages.WithEditionDefaults withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword); + + /** + * Specifies the user-assigned managed identity (UAMI) used to authenticate to the SQL database for import. + * + *

The SQL server must have the specified UAMI assigned (and typically set as its primary identity), the + * UAMI must be granted the appropriate role on the source storage account (e.g. {@code Storage Blob Data + * Contributor}), and it must be mapped to a database user with the required privileges. When this method + * is used, no administrator password is sent to the service.

+ * + *

This method is for user-assigned managed identity. To use the server's system-assigned managed + * identity, enable SAMI on the server and pass the SAMI's resource identifier as well. The signed-in + * user's object ID is not a managed identity and cannot be used here.

+ * + * @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use + * @return next definition stage + */ + default SqlDatabaseOperations.DefinitionStages.WithEditionDefaults + withManagedIdentity(String managedIdentityResourceId) { + throw new UnsupportedOperationException("[withManagedIdentity] is not supported in " + getClass()); + } } /** The SQL Database definition to import a BACPAC file as the source database. */ @@ -253,6 +273,26 @@ interface WithAuthenticationAfterElasticPool { */ SqlDatabaseOperations.DefinitionStages.WithCreateAfterElasticPoolOptions withActiveDirectoryLoginAndPassword(String administratorLogin, String administratorPassword); + + /** + * Specifies the user-assigned managed identity (UAMI) used to authenticate to the SQL database for import. + * + *

The SQL server must have the specified UAMI assigned (and typically set as its primary identity), the + * UAMI must be granted the appropriate role on the source storage account (e.g. {@code Storage Blob Data + * Contributor}), and it must be mapped to a database user with the required privileges. When this method + * is used, no administrator password is sent to the service.

+ * + *

This method is for user-assigned managed identity. System-assigned managed identity is not supported. + * See Limitations

+ * + * @param managedIdentityResourceId the Azure resource ID of the user-assigned managed identity to use + * for both SQL and storage access + * @return next definition stage + */ + default SqlDatabaseOperations.DefinitionStages.WithCreateAfterElasticPoolOptions + withManagedIdentity(String managedIdentityResourceId) { + throw new UnsupportedOperationException("[withManagedIdentity] is not supported in " + getClass()); + } } /** The SQL Database definition to set a restorable dropped database as the source database. */ diff --git a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java index 64d5188526ac..b270e0bbd331 100644 --- a/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java +++ b/sdk/sql/azure-resourcemanager-sql/src/main/java/com/azure/resourcemanager/sql/models/SqlServer.java @@ -157,6 +157,16 @@ public interface SqlServer /** Removes the Active Directory administrator from this server. */ void removeActiveDirectoryAdministrator(); + /** + * Checks whether Azure Active Directory (AAD) only authentication enabled. + * + * @return true if Azure Active Directory (AAD) only authentication enabled + */ + default boolean isAzureActiveDirectoryOnlyAuthenticationEnabled() { + throw new UnsupportedOperationException( + "[isAzureActiveDirectoryOnlyAuthenticationEnabled] is not supported in " + getClass()); + } + /** * Gets a SQL server automatic tuning state and options. * @@ -234,10 +244,9 @@ public interface SqlServer **************************************************************/ /** Container interface for all the definitions that need to be implemented. */ - interface Definition - extends DefinitionStages.Blank, DefinitionStages.WithGroup, DefinitionStages.WithAdministratorLogin, - DefinitionStages.WithAdministratorPassword, DefinitionStages.WithElasticPool, DefinitionStages.WithDatabase, - DefinitionStages.WithFirewallRule, DefinitionStages.WithPublicNetworkAccess, DefinitionStages.WithCreate { + interface Definition extends DefinitionStages.Blank, DefinitionStages.WithGroup, + DefinitionStages.WithAdministratorLogin, DefinitionStages.WithAdministratorPassword, + DefinitionStages.WithExternalActiveDirectoryAdministrator, DefinitionStages.WithCreate { } /** Grouping of all the storage account definition stages. */ @@ -251,7 +260,7 @@ interface WithGroup extends GroupableResource.DefinitionStages.WithGroupWhen Microsoft Entra-only authentication is enabled, the SQL authentication (login/password) + * administrator is not configured on the server. This is required when the target subscription or management + * group enforces a policy that mandates Microsoft Entra-only authentication on Azure SQL Server creation.

+ */ + interface WithAzureActiveDirectoryOnlyAuthentication { + /** + * Enables Microsoft Entra (Azure Active Directory) only authentication on the SQL Server. + * + *

An external Microsoft Entra administrator must be specified on the next stage.

+ * + * @return Next stage of the SQL Server definition + */ + WithExternalActiveDirectoryAdministrator withAzureActiveDirectoryOnlyAuthentication(); + } + + /** A SQL Server definition stage setting the external Microsoft Entra administrator on the server. */ + interface WithExternalActiveDirectoryAdministrator { + /** + * Sets the external Microsoft Entra (Azure Active Directory) administrator on the SQL Server. + * + * @param adminLogin the user, group, or application login name + * @param sid the user, group, or application object ID + * @param principalType the principal type (User, Group, or Application). Must be specified explicitly, since + * the service does not reliably infer it from the SID + * @return Next stage of the SQL Server definition + */ + WithCreate withExternalActiveDirectoryAdministrator(String adminLogin, String sid, + PrincipalType principalType); + } + /** A SQL Server definition setting admin user password. */ interface WithAdministratorPassword { /** @@ -297,6 +339,22 @@ interface WithSystemAssignedManagedServiceIdentity { WithCreate withSystemAssignedManagedServiceIdentity(); } + /** A SQL Server definition setting a user-assigned managed service identity as the primary identity. */ + interface WithUserAssignedManagedServiceIdentity { + /** + * Sets the specified user-assigned managed identity (UAMI) on the SQL server and marks it as the + * primary identity. If a system-assigned identity is also enabled, the resulting identity type is + * {@code SystemAssigned,UserAssigned}; otherwise it is {@code UserAssigned}. + * + * @param identityResourceId the Azure resource ID of the user-assigned managed identity + * @return Next stage of the SQL Server definition + */ + default WithCreate withPrimaryUserAssignedManagedServiceIdentity(String identityResourceId) { + throw new UnsupportedOperationException( + "[withPrimaryUserAssignedManagedServiceIdentity] is not supported in " + getClass()); + } + } + /** A SQL Server definition for specifying elastic pool. */ interface WithElasticPool { /** @@ -366,8 +424,9 @@ interface WithPublicNetworkAccess { * A SQL Server definition with sufficient inputs to create a new SQL Server in the cloud, but exposing * additional optional inputs to specify. */ - interface WithCreate extends Creatable, WithActiveDirectoryAdministrator, - WithSystemAssignedManagedServiceIdentity, WithElasticPool, WithDatabase, WithFirewallRule, + interface WithCreate + extends Creatable, WithActiveDirectoryAdministrator, WithSystemAssignedManagedServiceIdentity, + WithUserAssignedManagedServiceIdentity, WithElasticPool, WithDatabase, WithFirewallRule, WithVirtualNetworkRule, WithPublicNetworkAccess, DefinitionWithTags { } } diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java index e962324290bf..c8dadeb79038 100644 --- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java +++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerOperationsTests.java @@ -7,6 +7,8 @@ import com.azure.core.management.Region; import com.azure.core.management.exception.ManagementException; import com.azure.core.test.annotation.DoNotRecord; +import com.azure.resourcemanager.authorization.models.BuiltInRole; +import com.azure.resourcemanager.msi.models.Identity; import com.azure.resourcemanager.resources.fluentcore.model.Creatable; import com.azure.resourcemanager.resources.fluentcore.model.Indexable; import com.azure.resourcemanager.resources.fluentcore.utils.ResourceManagerUtils; @@ -24,13 +26,14 @@ import com.azure.resourcemanager.sql.models.ElasticPoolEdition; import com.azure.resourcemanager.sql.models.ElasticPoolSku; import com.azure.resourcemanager.sql.models.FailoverGroupReplicationRole; +import com.azure.resourcemanager.sql.models.IdentityType; +import com.azure.resourcemanager.sql.models.PrincipalType; import com.azure.resourcemanager.sql.models.ReadOnlyEndpointFailoverPolicy; import com.azure.resourcemanager.sql.models.ReadWriteEndpointFailoverPolicy; import com.azure.resourcemanager.sql.models.RegionCapabilities; import com.azure.resourcemanager.sql.models.ReplicationLink; +import com.azure.resourcemanager.sql.models.ReplicationState; import com.azure.resourcemanager.sql.models.SampleName; -import com.azure.resourcemanager.sql.models.SecurityAlertPolicyName; -import com.azure.resourcemanager.sql.models.SecurityAlertPolicyState; import com.azure.resourcemanager.sql.models.ServerNetworkAccessFlag; import com.azure.resourcemanager.sql.models.ServiceObjectiveName; import com.azure.resourcemanager.sql.models.Sku; @@ -40,7 +43,6 @@ import com.azure.resourcemanager.sql.models.SqlDatabaseImportExportResponse; import com.azure.resourcemanager.sql.models.SqlDatabasePremiumServiceObjective; import com.azure.resourcemanager.sql.models.SqlDatabaseStandardServiceObjective; -import com.azure.resourcemanager.sql.models.SqlDatabaseThreatDetectionPolicy; import com.azure.resourcemanager.sql.models.SqlElasticPool; import com.azure.resourcemanager.sql.models.SqlElasticPoolBasicEDTUs; import com.azure.resourcemanager.sql.models.SqlFailoverGroup; @@ -56,6 +58,7 @@ import com.azure.resourcemanager.sql.models.TransparentDataEncryption; import com.azure.resourcemanager.sql.models.TransparentDataEncryptionState; import com.azure.resourcemanager.storage.models.StorageAccount; +import com.azure.resourcemanager.test.model.AzureUser; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -85,6 +88,9 @@ public class SqlServerOperationsTests extends SqlServerTest { private static final String SQL_FIREWALLRULE_NAME = "firewallrule1"; private static final String START_IPADDRESS = "10.102.1.10"; private static final String END_IPADDRESS = "10.102.1.12"; + private static final Region DEFAULT_REGION = Region.US_WEST3; + private static final Region SECONDARY_REGION = Region.US_EAST2; + private static final Region TERTIARY_REGION = Region.EUROPE_NORTH; // Only one sync database is allowed per region per subscription // canCRUDSqlSyncMember and canCRUDSqlSyncGroup need to be in 2 different region @@ -99,13 +105,14 @@ public void canCRUDSqlSyncMember() throws Exception { final String administratorLogin = "sqladmin"; final String administratorPassword = password(); + AzureUser user = azureCliSignedInUser(); // Create SqlServer sqlPrimaryServer = sqlServerManager.sqlServers() .define(sqlServerName) - .withRegion(Region.US_WEST3) + .withRegion(DEFAULT_REGION) .withNewResourceGroup(rgName) - .withAdministratorLogin(administratorLogin) - .withAdministratorPassword(administratorPassword) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .defineDatabase(dbName) .fromSample(SampleName.ADVENTURE_WORKS_LT) .withStandardEdition(SqlDatabaseStandardServiceObjective.S0) @@ -122,6 +129,14 @@ public void canCRUDSqlSyncMember() throws Exception { SqlDatabase dbSync = sqlPrimaryServer.databases().get(dbSyncName); SqlDatabase dbMember = sqlPrimaryServer.databases().get(dbMemberName); + // SQL Data Sync requires SQL authentication for connections to the hub and member databases. Microsoft Entra (Azure AD) + // authentication isn't supported by SQL Data Sync. Because SQL authentication relies on static passwords, + // it doesn't benefit from modern protections like multifactor authentication (MFA), Conditional Access, or managed identities. + // This can increase exposure for the entire SQL instance to credential theft, brute‑force attacks, and operational + // overhead for password rotation and policy enforcement. Where possible, prefer solutions that support Microsoft + // Entra authentication or managed identities. Since SQL Data Sync is scheduled for retirement, migrate to an + // alternative that aligns with your organization's security standards. + // See https://learn.microsoft.com/azure/azure-sql/database/sql-data-sync-data-sql-server-sql-database?view=azuresql SqlSyncGroup sqlSyncGroup = dbSync.syncGroups() .define(syncGroupName) .withSyncDatabaseId(dbSource.id()) @@ -169,13 +184,14 @@ public void canCRUDSqlSyncGroup() throws Exception { final String administratorLogin = "sqladmin"; final String administratorPassword = password(); + AzureUser user = azureCliSignedInUser(); // Create SqlServer sqlPrimaryServer = sqlServerManager.sqlServers() .define(sqlServerName) - .withRegion(Region.US_EAST) + .withRegion(SECONDARY_REGION) .withNewResourceGroup(rgName) - .withAdministratorLogin(administratorLogin) - .withAdministratorPassword(administratorPassword) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .defineDatabase(dbName) .fromSample(SampleName.ADVENTURE_WORKS_LT) .withStandardEdition(SqlDatabaseStandardServiceObjective.S0) @@ -188,6 +204,14 @@ public void canCRUDSqlSyncGroup() throws Exception { SqlDatabase dbSource = sqlPrimaryServer.databases().get(dbName); SqlDatabase dbSync = sqlPrimaryServer.databases().get(dbSyncName); + // SQL Data Sync requires SQL authentication for connections to the hub and member databases. Microsoft Entra (Azure AD) + // authentication isn't supported by SQL Data Sync. Because SQL authentication relies on static passwords, + // it doesn't benefit from modern protections like multifactor authentication (MFA), Conditional Access, or managed identities. + // This can increase exposure for the entire SQL instance to credential theft, brute‑force attacks, and operational + // overhead for password rotation and policy enforcement. Where possible, prefer solutions that support Microsoft + // Entra authentication or managed identities. Since SQL Data Sync is scheduled for retirement, migrate to an + // alternative that aligns with your organization's security standards. + // See https://learn.microsoft.com/azure/azure-sql/database/sql-data-sync-data-sql-server-sql-database?view=azuresql SqlSyncGroup sqlSyncGroup = dbSync.syncGroups() .define(syncGroupName) .withSyncDatabaseId(dbSource.id()) @@ -204,7 +228,7 @@ public void canCRUDSqlSyncGroup() throws Exception { Assertions.assertTrue(sqlServerManager.sqlServers() .syncGroups() - .listSyncDatabaseIds(Region.US_EAST) + .listSyncDatabaseIds(SECONDARY_REGION) .stream() .findAny() .isPresent()); @@ -224,16 +248,15 @@ public void canCopySqlDatabase() throws Exception { final String sqlSecondaryServerName = generateRandomResourceName("sqlsec", 22); final String epName = "epSample"; final String dbName = "dbSample"; - final String administratorLogin = "sqladmin"; - final String administratorPassword = password(); + AzureUser user = azureCliSignedInUser(); // Create SqlServer sqlPrimaryServer = sqlServerManager.sqlServers() .define(sqlPrimaryServerName) - .withRegion(Region.US_EAST2) + .withRegion(SECONDARY_REGION) .withNewResourceGroup(rgName) - .withAdministratorLogin(administratorLogin) - .withAdministratorPassword(administratorPassword) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .defineElasticPool(epName) .withPremiumPool() .attach() @@ -245,10 +268,10 @@ public void canCopySqlDatabase() throws Exception { SqlServer sqlSecondaryServer = sqlServerManager.sqlServers() .define(sqlSecondaryServerName) - .withRegion(Region.US_EAST) + .withRegion(DEFAULT_REGION) .withExistingResourceGroup(rgName) - .withAdministratorLogin(administratorLogin) - .withAdministratorPassword(administratorPassword) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .create(); SqlDatabase dbSample = sqlPrimaryServer.databases().get(dbName); @@ -271,16 +294,15 @@ public void canCRUDSqlFailoverGroup() throws Exception { final String failoverGroupName = generateRandomResourceName("fg", 22); final String failoverGroupName2 = generateRandomResourceName("fg2", 22); final String dbName = "dbSample"; - final String administratorLogin = "sqladmin"; - final String administratorPassword = password(); + AzureUser user = azureCliSignedInUser(); // Create SqlServer sqlPrimaryServer = sqlServerManager.sqlServers() .define(sqlPrimaryServerName) - .withRegion(Region.US_EAST) + .withRegion(DEFAULT_REGION) .withNewResourceGroup(rgName) - .withAdministratorLogin(administratorLogin) - .withAdministratorPassword(administratorPassword) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .defineDatabase(dbName) .fromSample(SampleName.ADVENTURE_WORKS_LT) .withStandardEdition(SqlDatabaseStandardServiceObjective.S0) @@ -289,18 +311,18 @@ public void canCRUDSqlFailoverGroup() throws Exception { SqlServer sqlSecondaryServer = sqlServerManager.sqlServers() .define(sqlSecondaryServerName) - .withRegion(Region.US_EAST2) + .withRegion(SECONDARY_REGION) .withExistingResourceGroup(rgName) - .withAdministratorLogin(administratorLogin) - .withAdministratorPassword(administratorPassword) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .create(); SqlServer sqlOtherServer = sqlServerManager.sqlServers() .define(sqlOtherServerName) - .withRegion(Region.US_SOUTH_CENTRAL) + .withRegion(TERTIARY_REGION) .withExistingResourceGroup(rgName) - .withAdministratorLogin(administratorLogin) - .withAdministratorPassword(administratorPassword) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .create(); SqlFailoverGroup failoverGroup = sqlPrimaryServer.failoverGroups() @@ -391,19 +413,16 @@ public void canCRUDSqlFailoverGroup() throws Exception { @Test public void canChangeSqlServerAndDatabaseAutomaticTuning() throws Exception { - String sqlServerAdminName = "sqladmin"; - String sqlServerAdminPassword = password(); String databaseName = "db-from-sample"; - String id = generateRandomUuid(); - String storageName = generateRandomResourceName(sqlServerName, 22); + AzureUser user = azureCliSignedInUser(); // Create SqlServer sqlServer = sqlServerManager.sqlServers() .define(sqlServerName) - .withRegion(Region.US_EAST) + .withRegion(DEFAULT_REGION) .withNewResourceGroup(rgName) - .withAdministratorLogin(sqlServerAdminName) - .withAdministratorPassword(sqlServerAdminPassword) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .defineDatabase(databaseName) .fromSample(SampleName.ADVENTURE_WORKS_LT) .withBasicEdition() @@ -469,16 +488,15 @@ public void canChangeSqlServerAndDatabaseAutomaticTuning() throws Exception { public void canCreateAndAquireServerDnsAlias() throws Exception { String sqlServerName1 = sqlServerName + "1"; String sqlServerName2 = sqlServerName + "2"; - String sqlServerAdminName = "sqladmin"; - String sqlServerAdminPassword = password(); + AzureUser user = azureCliSignedInUser(); // Create SqlServer sqlServer1 = sqlServerManager.sqlServers() .define(sqlServerName1) - .withRegion(Region.US_EAST) + .withRegion(DEFAULT_REGION) .withNewResourceGroup(rgName) - .withAdministratorLogin(sqlServerAdminName) - .withAdministratorPassword(sqlServerAdminPassword) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .create(); Assertions.assertNotNull(sqlServer1); @@ -497,10 +515,10 @@ public void canCreateAndAquireServerDnsAlias() throws Exception { SqlServer sqlServer2 = sqlServerManager.sqlServers() .define(sqlServerName2) - .withRegion(Region.US_EAST) + .withRegion(DEFAULT_REGION) .withNewResourceGroup(rgName) - .withAdministratorLogin(sqlServerAdminName) - .withAdministratorPassword(sqlServerAdminPassword) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .create(); Assertions.assertNotNull(sqlServer2); @@ -523,11 +541,10 @@ public void canCreateAndAquireServerDnsAlias() throws Exception { @DoNotRecord(skipInPlayback = true) public void canGetSqlServerCapabilitiesAndCreateIdentity() throws Exception { // LiveOnly because "test timing out after latest test proxy update" - String sqlServerAdminName = "sqladmin"; - String sqlServerAdminPassword = password(); String databaseName = "db-from-sample"; + AzureUser user = azureCliSignedInUser(); - RegionCapabilities regionCapabilities = sqlServerManager.sqlServers().getCapabilitiesByRegion(Region.US_EAST); + RegionCapabilities regionCapabilities = sqlServerManager.sqlServers().getCapabilitiesByRegion(DEFAULT_REGION); Assertions.assertNotNull(regionCapabilities); Assertions.assertNotNull(regionCapabilities.supportedCapabilitiesByServerVersion().get("12.0")); Assertions.assertTrue( @@ -539,10 +556,10 @@ public void canGetSqlServerCapabilitiesAndCreateIdentity() throws Exception { // Create SqlServer sqlServer = sqlServerManager.sqlServers() .define(sqlServerName) - .withRegion(Region.US_EAST) + .withRegion(DEFAULT_REGION) .withNewResourceGroup(rgName) - .withAdministratorLogin(sqlServerAdminName) - .withAdministratorPassword(sqlServerAdminPassword) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .withSystemAssignedManagedServiceIdentity() .defineDatabase(databaseName) .fromSample(SampleName.ADVENTURE_WORKS_LT) @@ -576,56 +593,60 @@ public void canGetSqlServerCapabilitiesAndCreateIdentity() throws Exception { @Test @DoNotRecord(skipInPlayback = true) // The test makes calls to the Azure Storage data plane APIs which are not mocked at this time. - public void canCRUDSqlServerWithImportDatabase() throws Exception { - // Create - - String sqlServerAdminName = "sqladmin"; - String sqlServerAdminPassword = password(); - String id = generateRandomUuid(); + public void canCRUDSqlServerWithImportDatabase() { String storageName = generateRandomResourceName(sqlServerName, 22); + String uamiName = generateRandomResourceName("uami", 18); + + resourceManager.resourceGroups().define(rgName).withRegion(DEFAULT_REGION).create(); + + Identity uami = msiManager.identities() + .define(uamiName) + .withRegion(DEFAULT_REGION) + .withExistingResourceGroup(rgName) + .create(); + + StorageAccount storageAccount = storageManager.storageAccounts() + .define(storageName) + .withRegion(DEFAULT_REGION) + .withExistingResourceGroup(rgName) + .disableSharedKeyAccess() + .create(); + + authorizationManager.roleAssignments() + .define(generateRandomUuid()) + .forObjectId(uami.principalId()) + .withBuiltInRole(BuiltInRole.STORAGE_BLOB_DATA_CONTRIBUTOR) + .withResourceScope(storageAccount) + .create(); + + ResourceManagerUtils.sleep(Duration.ofMinutes(1)); SqlServer sqlServer = sqlServerManager.sqlServers() .define(sqlServerName) - .withRegion(Region.US_EAST) - .withNewResourceGroup(rgName) - .withAdministratorLogin(sqlServerAdminName) - .withAdministratorPassword(sqlServerAdminPassword) - .withActiveDirectoryAdministrator("DSEng", id) + .withRegion(DEFAULT_REGION) + .withExistingResourceGroup(rgName) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(uamiName, uami.principalId(), PrincipalType.APPLICATION) + .withPrimaryUserAssignedManagedServiceIdentity(uami.id()) + .withSystemAssignedManagedServiceIdentity() .create(); + Assertions.assertTrue(sqlServer.isAzureActiveDirectoryOnlyAuthenticationEnabled()); + Assertions.assertSame(IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED, sqlServer.managedServiceIdentityType()); + SqlDatabase dbFromSample = sqlServer.databases() .define("db-from-sample") .fromSample(SampleName.ADVENTURE_WORKS_LT) .withBasicEdition() - .withTag("tag1", "value1") .create(); Assertions.assertNotNull(dbFromSample); Assertions.assertEquals(DatabaseEdition.BASIC, dbFromSample.edition()); - SqlDatabaseImportExportResponse exportedDB; - StorageAccount storageAccount = null; - try { - storageAccount - = storageManager.storageAccounts().getByResourceGroup(sqlServer.resourceGroupName(), storageName); - } catch (ManagementException e) { - Assertions.assertEquals(404, e.getResponse().getStatusCode()); - } - if (storageAccount == null) { - Creatable storageAccountCreatable = storageManager.storageAccounts() - .define(storageName) - .withRegion(sqlServer.regionName()) - .withExistingResourceGroup(sqlServer.resourceGroupName()); - - exportedDB = dbFromSample.exportTo(storageAccountCreatable, "from-sample", "dbfromsample.bacpac") - .withSqlAdministratorLoginAndPassword(sqlServerAdminName, sqlServerAdminPassword) + SqlDatabaseImportExportResponse exportedDB + = dbFromSample.exportTo(storageAccount, "from-sample", "dbfromsample.bacpac") + .withManagedIdentity(uami.id()) .execute(); - storageAccount - = storageManager.storageAccounts().getByResourceGroup(sqlServer.resourceGroupName(), storageName); - } else { - exportedDB = dbFromSample.exportTo(storageAccount, "from-sample", "dbfromsample.bacpac") - .withSqlAdministratorLoginAndPassword(sqlServerAdminName, sqlServerAdminPassword) - .execute(); - } + Assertions.assertNotNull(exportedDB); SqlDatabase dbFromImport = sqlServer.databases() .define("db-from-import") @@ -633,12 +654,24 @@ public void canCRUDSqlServerWithImportDatabase() throws Exception { .withBasicPool() .attach() .importFrom(storageAccount, "from-sample", "dbfromsample.bacpac") - .withSqlAdministratorLoginAndPassword(sqlServerAdminName, sqlServerAdminPassword) + .withManagedIdentity(uami.id()) .withTag("tag2", "value2") .create(); Assertions.assertNotNull(dbFromImport); Assertions.assertEquals("ep1", dbFromImport.elasticPoolName()); + // Test importBacpac with managed identity on an existing empty database + SqlDatabase dbForImportBacpac + = sqlServer.databases().define("db-for-import-bacpac").withBasicEdition().create(); + Assertions.assertNotNull(dbForImportBacpac); + + SqlDatabaseImportExportResponse importBacpacResponse + = dbForImportBacpac.importBacpac(storageAccount, "from-sample", "dbfromsample.bacpac") + .withManagedIdentity(uami.id()) + .execute(); + Assertions.assertNotNull(importBacpacResponse); + + dbForImportBacpac.delete(); dbFromImport.delete(); dbFromSample.delete(); sqlServer.elasticPools().delete("ep1"); @@ -648,28 +681,24 @@ public void canCRUDSqlServerWithImportDatabase() throws Exception { @Test public void canCRUDSqlServerWithFirewallRule() throws Exception { // Create - String sqlServerAdminName = "sqladmin"; - String id = azureCliSignedInUser().id(); + AzureUser user = azureCliSignedInUser(); SqlServer sqlServer = sqlServerManager.sqlServers() .define(sqlServerName) - .withRegion(Region.US_EAST) + .withRegion(DEFAULT_REGION) .withNewResourceGroup(rgName) - .withAdministratorLogin(sqlServerAdminName) - .withAdministratorPassword(password()) - .withActiveDirectoryAdministrator("DSEng", id) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator("DSEng", user.id(), PrincipalType.USER) .withoutAccessFromAzureServices() .defineFirewallRule("somefirewallrule1") .withIpAddress("0.0.0.1") .attach() .withTag("tag1", "value1") .create(); - Assertions.assertEquals(sqlServerAdminName, sqlServer.administratorLogin()); Assertions.assertEquals("v12.0", sqlServer.kind()); Assertions.assertEquals("12.0", sqlServer.version()); sqlServer = sqlServerManager.sqlServers().getByResourceGroup(rgName, sqlServerName); - Assertions.assertEquals(sqlServerAdminName, sqlServer.administratorLogin()); Assertions.assertEquals("v12.0", sqlServer.kind()); Assertions.assertEquals("12.0", sqlServer.version()); @@ -679,15 +708,16 @@ public void canCRUDSqlServerWithFirewallRule() throws Exception { Assertions.assertNotNull(sqlADAdmin.id()); Assertions.assertEquals(AdministratorType.ACTIVE_DIRECTORY, sqlADAdmin.administratorType()); - sqlADAdmin = sqlServer.setActiveDirectoryAdministrator("DSEngAll", id); + sqlADAdmin = sqlServer.setActiveDirectoryAdministrator("DSEngAll", user.id()); Assertions.assertNotNull(sqlADAdmin); Assertions.assertEquals("DSEngAll", sqlADAdmin.signInName()); Assertions.assertNotNull(sqlADAdmin.id()); Assertions.assertEquals(AdministratorType.ACTIVE_DIRECTORY, sqlADAdmin.administratorType()); - sqlServer.removeActiveDirectoryAdministrator(); - final SqlServer finalSqlServer = sqlServer; - validateResourceNotFound(() -> finalSqlServer.getActiveDirectoryAdministrator()); + // Cannot remove AD admin when AzureADOnlyAuthentication is enabled. + // Verify it is still present instead. + sqlADAdmin = sqlServer.getActiveDirectoryAdministrator(); + Assertions.assertNotNull(sqlADAdmin); SqlFirewallRule firewallRule = sqlServerManager.sqlServers().firewallRules().getBySqlServer(rgName, sqlServerName, "somefirewallrule1"); @@ -760,7 +790,11 @@ public void canCRUDSqlServer() throws Exception { Assertions.assertEquals(CheckNameAvailabilityReason.ALREADY_EXISTS.toString(), checkNameResult.unavailabilityReason()); - sqlServer.update().withAdministratorPassword("P@ssword~2").apply(); + // Server is created with Microsoft Entra-only authentication, so SQL admin password update is not applicable. + // Exercise an update path using system-assigned managed identity instead. + sqlServer.update().withSystemAssignedManagedServiceIdentity().apply(); + Assertions.assertTrue(sqlServer.isManagedServiceIdentityEnabled()); + Assertions.assertNotNull(sqlServer.systemAssignedManagedServiceIdentityPrincipalId()); // List PagedIterable sqlServers = sqlServerManager.sqlServers().listByResourceGroup(rgName); @@ -787,14 +821,15 @@ public void canUseCoolShortcutsForResourceCreation() throws Exception { String elasticPool2Name = "elasticPool2"; String elasticPool3Name = "elasticPool3"; String elasticPool1Name = SQL_ELASTIC_POOL_NAME; + AzureUser user = azureCliSignedInUser(); // Create SqlServer sqlServer = sqlServerManager.sqlServers() .define(sqlServerName) - .withRegion(Region.US_EAST) + .withRegion(DEFAULT_REGION) .withNewResourceGroup(rgName) - .withAdministratorLogin("userName") - .withAdministratorPassword("Password~1") + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .withoutAccessFromAzureServices() .defineDatabase(SQL_DATABASE_NAME) .attach() @@ -893,9 +928,19 @@ public void canUseCoolShortcutsForResourceCreation() throws Exception { } @Test - public void canCRUDSqlDatabase() throws Exception { + public void canCRUDSqlDatabase() { + AzureUser user = azureCliSignedInUser(); + // Create - SqlServer sqlServer = createSqlServer(); + SqlServer sqlServer = sqlServerManager.sqlServers() + .define(sqlServerName) + .withRegion(DEFAULT_REGION) + .withNewResourceGroup(rgName) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) + .withSystemAssignedManagedServiceIdentity() + .create(); + Mono resourceStream = sqlServer.databases() .define(SQL_DATABASE_NAME) .withStandardEdition(SqlDatabaseStandardServiceObjective.S0) @@ -906,35 +951,42 @@ public void canCRUDSqlDatabase() throws Exception { validateSqlDatabase(sqlDatabase, SQL_DATABASE_NAME); Assertions.assertTrue(sqlServer.databases().list().size() > 0); - // Test security alert policy settings. - - final String storageAccountName = generateRandomResourceName("sqlsa", 20); - StorageAccount storageAccount = storageManager.storageAccounts() - .define(storageAccountName) - .withRegion(Region.US_EAST) - .withExistingResourceGroup(rgName) - .create(); - String accountKey = storageAccount.getKeys().get(0).value(); - String blobEntrypoint = storageAccount.endPoints().primary().blob(); - - List disabledAlerts = Collections.singletonList("Sql_Injection"); - - sqlDatabase.defineThreatDetectionPolicy(SecurityAlertPolicyName.fromString("myPolicy")) - .withPolicyEnabled() - .withStorageEndpoint(blobEntrypoint) - .withStorageAccountAccessKey(accountKey) - .withAlertsFilter(disabledAlerts) - .create(); - - sqlDatabase.refresh(); - - SqlDatabaseThreatDetectionPolicy alertPolicy = sqlDatabase.getThreatDetectionPolicy(); - Assertions.assertNotNull(alertPolicy); - Assertions.assertEquals(SecurityAlertPolicyState.ENABLED, alertPolicy.currentState()); - Assertions.assertEquals(alertPolicy.disabledAlertList(), disabledAlerts); - Assertions.assertTrue(alertPolicy.isDefaultSecurityAlertPolicy()); - - // Done testing security alert policy + // final String storageAccountName = generateRandomResourceName("sqlsa", 20); + // Legacy threat detection policy doesn't support MI. The new Advanced Threat Protection(ATP) does not require + // storage account any more. See Oury Ba's answer: + // https://learn.microsoft.com/answers/questions/2276392/how-to-create-microsoft-sql-servers-securityalertp + // StorageAccount storageAccount = storageManager.storageAccounts() + // .define(storageAccountName) + // .withRegion(DEFAULT_REGION) + // .withExistingResourceGroup(rgName) + // .disableSharedKeyAccess() + // .create(); + // + // authorizationManager.roleAssignments() + // .define(generateRandomUuid()) + // .forObjectId(sqlServer.systemAssignedManagedServiceIdentityPrincipalId()) + // .withBuiltInRole(BuiltInRole.STORAGE_BLOB_DATA_CONTRIBUTOR) + // .withResourceScope(storageAccount) + // .create(); + // String blobEntrypoint = storageAccount.endPoints().primary().blob(); + // + // List disabledAlerts = Collections.singletonList("Sql_Injection"); + // + // sqlDatabase.defineThreatDetectionPolicy(SecurityAlertPolicyName.fromString("myPolicy")) + // .withPolicyEnabled() + // .withStorageEndpoint(blobEntrypoint) + // // use system-assigned MI + // .withStorageAccountAccessKey(null) + // .withAlertsFilter(disabledAlerts) + // .create(); + // + // sqlDatabase.refresh(); + // + // SqlDatabaseThreatDetectionPolicy alertPolicy = sqlDatabase.getThreatDetectionPolicy(); + // Assertions.assertNotNull(alertPolicy); + // Assertions.assertEquals(SecurityAlertPolicyState.ENABLED, alertPolicy.currentState()); + // Assertions.assertEquals(alertPolicy.disabledAlertList(), disabledAlerts); + // Assertions.assertTrue(alertPolicy.isDefaultSecurityAlertPolicy()); // Test transparent data encryption settings. TransparentDataEncryption transparentDataEncryption = sqlDatabase.getTransparentDataEncryption(); @@ -1024,6 +1076,13 @@ public void canManageReplicationLinks() throws Exception { .define(SQL_DATABASE_NAME) .withSourceDatabase(databaseInServer1.id()) .withMode(CreateMode.ONLINE_SECONDARY) + // Explicitly pin the secondary to Standard S0 (DTU-based) to match the primary. + // Without an explicit edition, Azure SQL applies the region's default tier —currently + // GeneralPurpose / GP_S_Gen5 (serverless, vCore-based), which counts against the + // subscription's vCore quota and can fail with + // "subscription would exceed the allowed vCore quota of 500" when the quota is near + // its limit (e.g. due to leftover resources from earlier test runs). + .withStandardEdition(SqlDatabaseStandardServiceObjective.S0) .create(); ResourceManagerUtils.sleep(Duration.ofSeconds(2)); List replicationLinksInDb1 @@ -1050,9 +1109,23 @@ public void canManageReplicationLinks() throws Exception { replicationLinksInDb1.get(0).forceFailoverAllowDataLoss(); replicationLinksInDb1.get(0).refresh(); - ResourceManagerUtils.sleep(Duration.ofSeconds(30)); + // Wait until the relationship has entered and then left SUSPENDED after + // forceFailoverAllowDataLoss; otherwise delete() fails with + // 409 GeoReplicationCannotBecomePrimaryDuringUndo. + ReplicationLink secondaryLink = replicationLinksInDb2.get(0); + int maxAttempts = 300; + boolean sawSuspended = false; + while (maxAttempts-- > 0) { + secondaryLink.refresh(); + if (secondaryLink.replicationState() == ReplicationState.SUSPENDED) { + sawSuspended = true; + } else if (sawSuspended) { + break; + } + ResourceManagerUtils.sleep(Duration.ofSeconds(10)); + } - replicationLinksInDb2.get(0).delete(); + secondaryLink.delete(); Assertions.assertEquals(databaseInServer2.listReplicationLinks().size(), 0); sqlServer1.databases().delete(databaseInServer1.name()); @@ -1077,7 +1150,12 @@ public void canDoOperationsOnDataWarehouse() throws Exception { Mono resourceStream = sqlServer.databases() .define(SQL_DATABASE_NAME) .withCollation(COLLATION) - .withSku(DatabaseSku.DATAWAREHOUSE_DW1000C) + // Use DW100c (the smallest DataWarehouse SKU) instead of DW1000c. + // DW1000c provisions ~5 compute nodes and consumes a large share of the subscription's + // vCore quota (500). The test only exercises pause/resume/list operations, none of + // which require a large warehouse, so DW100c is sufficient and avoids the + // "subscription would exceed the allowed vCore quota of 500" failure. + .withSku(DatabaseSku.DATAWAREHOUSE_DW100C) .createAsync(); SqlDatabase sqlDatabase = resourceStream.block(); @@ -1447,12 +1525,13 @@ private SqlServer createSqlServer() { } private SqlServer createSqlServer(String sqlServerName) { + AzureUser user = azureCliSignedInUser(); return sqlServerManager.sqlServers() .define(sqlServerName) - .withRegion(Region.US_EAST) + .withRegion(DEFAULT_REGION) .withNewResourceGroup(rgName) - .withAdministratorLogin("userName") - .withAdministratorPassword("P@ssword~1") + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .create(); } @@ -1474,7 +1553,7 @@ private void validateSqlFirewallRule(SqlFirewallRule sqlFirewallRule, String fir Assertions.assertEquals(END_IPADDRESS, sqlFirewallRule.endIpAddress()); Assertions.assertEquals(rgName, sqlFirewallRule.resourceGroupName()); Assertions.assertEquals(sqlServerName, sqlFirewallRule.sqlServerName()); - Assertions.assertEquals(Region.US_EAST, sqlFirewallRule.region()); + Assertions.assertEquals(DEFAULT_REGION, sqlFirewallRule.region()); } private static void validateListSqlElasticPool(List sqlElasticPools) { @@ -1517,7 +1596,6 @@ private void validateSqlServer(SqlServer sqlServer) { Assertions.assertEquals(rgName, sqlServer.resourceGroupName()); Assertions.assertNotNull(sqlServer.fullyQualifiedDomainName()); // Assertions.assertEquals(ServerVersion.ONE_TWO_FULL_STOP_ZERO, sqlServer.version()); - Assertions.assertEquals("userName", sqlServer.administratorLogin()); } private void validateSqlDatabase(SqlDatabase sqlDatabase, String databaseName) { @@ -1537,46 +1615,48 @@ private void validateSqlDatabaseWithElasticPool(SqlDatabase sqlDatabase, String @DoNotRecord(skipInPlayback = true) public void testRandomSku() { // LiveOnly because "test timing out after latest test proxy update" - // "M" series is not supported in this region + // "M" series is not supported in this region; "Fsv2" series has been deprecated + List excludedFamilies = Arrays.asList("M", "Fsv2"); List capabilityStatusList = Arrays.asList(CapabilityStatus.AVAILABLE, CapabilityStatus.DEFAULT); List databaseSkus = DatabaseSku.getAll() .stream() - .filter(sku -> !"M".equals(sku.toSku().family())) + .filter(sku -> !excludedFamilies.contains(sku.toSku().family())) .collect(Collectors.toCollection(LinkedList::new)); Collections.shuffle(databaseSkus); List elasticPoolSkus = ElasticPoolSku.getAll() .stream() - .filter(sku -> !"M".equals(sku.toSku().family())) + .filter(sku -> !excludedFamilies.contains(sku.toSku().family())) .collect(Collectors.toCollection(LinkedList::new)); Collections.shuffle(elasticPoolSkus); sqlServerManager.sqlServers() - .getCapabilitiesByRegion(Region.US_EAST) + .getCapabilitiesByRegion(DEFAULT_REGION) .supportedCapabilitiesByServerVersion() .forEach((x, serverVersionCapability) -> { serverVersionCapability.supportedEditions().forEach(edition -> { edition.supportedServiceLevelObjectives() .stream() .filter(serviceObjective -> !capabilityStatusList.contains(serviceObjective.status()) - || "M".equals(serviceObjective.sku().family())) + || excludedFamilies.contains(serviceObjective.sku().family())) .forEach(serviceObjective -> databaseSkus.remove(DatabaseSku.fromSku(serviceObjective.sku()))); }); serverVersionCapability.supportedElasticPoolEditions().forEach(edition -> { edition.supportedElasticPoolPerformanceLevels() .stream() .filter(performance -> !capabilityStatusList.contains(performance.status()) - || "M".equals(performance.sku().family())) + || excludedFamilies.contains(performance.sku().family())) .forEach(performance -> elasticPoolSkus.remove(ElasticPoolSku.fromSku(performance.sku()))); }); }); + AzureUser user = azureCliSignedInUser(); SqlServer sqlServer = sqlServerManager.sqlServers() .define(sqlServerName) - .withRegion(Region.US_EAST) + .withRegion(DEFAULT_REGION) .withNewResourceGroup(rgName) - .withAdministratorLogin("userName") - .withAdministratorPassword(password()) + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .create(); // Too many elastic pools defined will hit sql server DTU quota limits. @@ -1603,7 +1683,7 @@ public void generateSku() throws IOException { StringBuilder databaseSkuBuilder = new StringBuilder(); StringBuilder elasticPoolSkuBuilder = new StringBuilder(); sqlServerManager.sqlServers() - .getCapabilitiesByRegion(Region.US_EAST) + .getCapabilitiesByRegion(DEFAULT_REGION) .supportedCapabilitiesByServerVersion() .forEach((x, serverVersionCapability) -> { serverVersionCapability.supportedEditions().forEach(edition -> { @@ -1640,7 +1720,7 @@ public void generateSku() throws IOException { Files.write(new File("src/main/java/com/azure/resourcemanager/sql/models/ElasticPoolSku.java").toPath(), elasticPoolSku.getBytes(StandardCharsets.UTF_8)); - sqlServerManager.resourceManager().resourceGroups().define(rgName).withRegion(Region.US_EAST).create(); // for deletion + sqlServerManager.resourceManager().resourceGroups().define(rgName).withRegion(DEFAULT_REGION).create(); // for deletion } private byte[] readAllBytes(InputStream inputStream) throws IOException { @@ -1688,13 +1768,14 @@ private void addStaticSkuDefinition(StringBuilder builder, String edition, Strin @Test public void canCreateAndUpdatePublicNetworkAccess() { + AzureUser user = azureCliSignedInUser(); // Create SqlServer sqlServer = sqlServerManager.sqlServers() .define(sqlServerName) - .withRegion(Region.US_EAST) + .withRegion(DEFAULT_REGION) .withNewResourceGroup(rgName) - .withAdministratorLogin("userName") - .withAdministratorPassword("P@ssword~1") + .withAzureActiveDirectoryOnlyAuthentication() + .withExternalActiveDirectoryAdministrator(user.userPrincipalName(), user.id(), PrincipalType.USER) .withoutAccessFromAzureServices() .disablePublicNetworkAccess() .create(); diff --git a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java index d0e69fa5a50b..96df532f8dc3 100644 --- a/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java +++ b/sdk/sql/azure-resourcemanager-sql/src/test/java/com/azure/resourcemanager/sql/SqlServerTest.java @@ -10,6 +10,8 @@ import com.azure.core.http.policy.HttpPipelinePolicy; import com.azure.core.http.policy.RetryPolicy; import com.azure.core.management.profile.AzureProfile; +import com.azure.resourcemanager.authorization.AuthorizationManager; +import com.azure.resourcemanager.msi.MsiManager; import com.azure.resourcemanager.resources.fluentcore.utils.HttpPipelineProvider; import com.azure.resourcemanager.resources.fluentcore.utils.ResourceManagerUtils; import com.azure.resourcemanager.resources.ResourceManager; @@ -26,6 +28,8 @@ public abstract class SqlServerTest extends ResourceManagerTestProxyTestBase { protected ResourceManager resourceManager; protected SqlServerManager sqlServerManager; protected StorageManager storageManager; + protected MsiManager msiManager; + protected AuthorizationManager authorizationManager; protected String rgName = ""; protected String sqlServerName = ""; @@ -45,6 +49,8 @@ protected void initializeClients(HttpPipeline httpPipeline, AzureProfile profile internalContext.setIdentifierFunction(name -> new TestIdentifierProvider(testResourceNamer)); sqlServerManager = buildManager(SqlServerManager.class, httpPipeline, profile); storageManager = buildManager(StorageManager.class, httpPipeline, profile); + msiManager = buildManager(MsiManager.class, httpPipeline, profile); + authorizationManager = buildManager(AuthorizationManager.class, httpPipeline, profile); resourceManager = sqlServerManager.resourceManager(); setInternalContext(internalContext, sqlServerManager); }