|  | 
| 13 | 13 |     CLIError, | 
| 14 | 14 |     sdk_no_wait, | 
| 15 | 15 | ) | 
|  | 16 | +from azure.cli.core.azclierror import ( | 
|  | 17 | +    ResourceNotFoundError, | 
|  | 18 | +    ValidationError, | 
|  | 19 | +    AzCLIError | 
|  | 20 | +) | 
| 16 | 21 | 
 | 
| 17 | 22 | from azure.mgmt.sql.models import ( | 
| 18 | 23 |     AdministratorName, | 
|  | 
| 58 | 63 |     SensitivityLabelSource, | 
| 59 | 64 |     ServerAzureADOnlyAuthentication, | 
| 60 | 65 |     ServerConnectionPolicy, | 
|  | 66 | +    ServerCreateMode, | 
| 61 | 67 |     ServerExternalAdministrator, | 
| 62 | 68 |     ServerInfo, | 
| 63 | 69 |     ServerKey, | 
|  | 
| 92 | 98 |     get_sql_replication_links_operations, | 
| 93 | 99 |     get_sql_elastic_pools_operations, | 
| 94 | 100 |     get_sql_databases_operations, | 
|  | 101 | +    get_sql_deleted_servers_operations, | 
| 95 | 102 | ) | 
| 96 | 103 | 
 | 
| 97 | 104 | 
 | 
| @@ -179,6 +186,58 @@ def _is_serverless_slo(sku_name): | 
| 179 | 186 |     return "_S_" in sku_name | 
| 180 | 187 | 
 | 
| 181 | 188 | 
 | 
|  | 189 | +def _check_server_exists(client, resource_group_name, server_name): | 
|  | 190 | +    ''' | 
|  | 191 | +    Checks if a server already exists and raises ValidationError if it does. | 
|  | 192 | +    Returns False if server doesn't exist (ResourceNotFound/NotFound), | 
|  | 193 | +    raises the original exception for other errors. | 
|  | 194 | +    ''' | 
|  | 195 | +    try: | 
|  | 196 | +        existing_server = client.get(resource_group_name, server_name) | 
|  | 197 | +        if existing_server: | 
|  | 198 | +            raise ValidationError( | 
|  | 199 | +                f'Server "{server_name}" already exists in resource group "{resource_group_name}". ' | 
|  | 200 | +                'Cannot restore to an existing server name.', | 
|  | 201 | +                'Please specify a different server name for the restore operation.') | 
|  | 202 | +    except ValidationError: | 
|  | 203 | +        # Re-raise ValidationError as-is | 
|  | 204 | +        raise | 
|  | 205 | +    except Exception as ex: | 
|  | 206 | +        # If server doesn't exist, that's what we want - continue with restore | 
|  | 207 | +        if 'ResourceNotFound' not in str(ex) and 'NotFound' not in str(ex): | 
|  | 208 | +            # Re-raise if it's not a "not found" error | 
|  | 209 | +            raise | 
|  | 210 | +    return False | 
|  | 211 | + | 
|  | 212 | + | 
|  | 213 | +def _validate_deleted_server_exists(cmd, location, server_name): | 
|  | 214 | +    """ | 
|  | 215 | +    Validates that a deleted server exists and can be restored. | 
|  | 216 | +    """ | 
|  | 217 | +    try: | 
|  | 218 | +        deleted_servers_client = get_sql_deleted_servers_operations(cmd.cli_ctx, None) | 
|  | 219 | +        deleted_server = deleted_servers_client.get(location, server_name) | 
|  | 220 | + | 
|  | 221 | +        if deleted_server: | 
|  | 222 | +            logger.info('Found deleted server: %s', deleted_server.name) | 
|  | 223 | +            return True | 
|  | 224 | + | 
|  | 225 | +    except Exception as ex: | 
|  | 226 | +        # If server doesn't exist in deleted servers, raise appropriate error | 
|  | 227 | +        if 'ResourceNotFound' in str(ex) or 'NotFound' in str(ex): | 
|  | 228 | +            raise ResourceNotFoundError( | 
|  | 229 | +                f'No deleted server found with name "{server_name}" in location "{location}".', | 
|  | 230 | +                'Please verify the server was deleted with soft delete enabled ' | 
|  | 231 | +                'and is within the retention period.') from ex | 
|  | 232 | +        # Handle any other unexpected errors | 
|  | 233 | +        raise AzCLIError(f'Failed to validate deleted server "{server_name}": {str(ex)}') from ex | 
|  | 234 | + | 
|  | 235 | +    # This shouldn't be reached if the API call succeeds but returns None | 
|  | 236 | +    raise ResourceNotFoundError( | 
|  | 237 | +        f'No deleted server found with name "{server_name}" in location "{location}".', | 
|  | 238 | +        'Please verify the server was deleted with soft delete enabled and is within the retention period.') | 
|  | 239 | + | 
|  | 240 | + | 
| 182 | 241 | def _get_default_server_version(location_capabilities): | 
| 183 | 242 |     ''' | 
| 184 | 243 |     Gets the default server version capability from the full location | 
| @@ -4369,6 +4428,8 @@ def server_create( | 
| 4369 | 4428 |         external_admin_principal_type=None, | 
| 4370 | 4429 |         external_admin_sid=None, | 
| 4371 | 4430 |         external_admin_name=None, | 
|  | 4431 | +        enable_soft_delete=None, | 
|  | 4432 | +        soft_delete_retention_days=None, | 
| 4372 | 4433 |         **kwargs): | 
| 4373 | 4434 |     ''' | 
| 4374 | 4435 |     Creates a server. | 
| @@ -4414,13 +4475,58 @@ def server_create( | 
| 4414 | 4475 |         azure_ad_only_authentication=ad_only, | 
| 4415 | 4476 |         tenant_id=tenant_id) | 
| 4416 | 4477 | 
 | 
|  | 4478 | +    kwargs['create_mode'] = ServerCreateMode.NORMAL | 
|  | 4479 | + | 
|  | 4480 | +    if enable_soft_delete is not None: | 
|  | 4481 | +        if soft_delete_retention_days is not None: | 
|  | 4482 | +            kwargs['retention_days'] = soft_delete_retention_days | 
|  | 4483 | +        else: | 
|  | 4484 | +            kwargs['retention_days'] = 7 | 
|  | 4485 | + | 
| 4417 | 4486 |     # Create | 
| 4418 | 4487 |     return sdk_no_wait(no_wait, client.begin_create_or_update, | 
| 4419 | 4488 |                        server_name=server_name, | 
| 4420 | 4489 |                        resource_group_name=resource_group_name, | 
| 4421 | 4490 |                        parameters=kwargs) | 
| 4422 | 4491 | 
 | 
| 4423 | 4492 | 
 | 
|  | 4493 | +def server_restore( | 
|  | 4494 | +        cmd, | 
|  | 4495 | +        client, | 
|  | 4496 | +        resource_group_name, | 
|  | 4497 | +        server_name, | 
|  | 4498 | +        location, | 
|  | 4499 | +        no_wait=False, | 
|  | 4500 | +        **kwargs): | 
|  | 4501 | +    ''' | 
|  | 4502 | +    Restores a deleted server. | 
|  | 4503 | +    ''' | 
|  | 4504 | +    # Validate that we have enough information to perform the restore | 
|  | 4505 | +    if not server_name or not resource_group_name or not location: | 
|  | 4506 | +        logger.info('Not all required parameters were provided. ' | 
|  | 4507 | +                    'Please specify server name, resource group name, and location.') | 
|  | 4508 | +        return None | 
|  | 4509 | + | 
|  | 4510 | +    # Check if server already exists | 
|  | 4511 | +    _check_server_exists(client, resource_group_name, server_name) | 
|  | 4512 | + | 
|  | 4513 | +    # Validate deleted server exists | 
|  | 4514 | +    _validate_deleted_server_exists(cmd, location, server_name) | 
|  | 4515 | + | 
|  | 4516 | +    # Set required parameters for restore | 
|  | 4517 | +    kwargs['location'] = location | 
|  | 4518 | +    kwargs['create_mode'] = ServerCreateMode.RESTORE | 
|  | 4519 | + | 
|  | 4520 | +    logger.info('Attempting to restore server "%s" in location "%s"', | 
|  | 4521 | +                server_name, location) | 
|  | 4522 | + | 
|  | 4523 | +    # Create/restore the server | 
|  | 4524 | +    return sdk_no_wait(no_wait, client.begin_create_or_update, | 
|  | 4525 | +                       server_name=server_name, | 
|  | 4526 | +                       resource_group_name=resource_group_name, | 
|  | 4527 | +                       parameters=kwargs) | 
|  | 4528 | + | 
|  | 4529 | + | 
| 4424 | 4530 | def server_list( | 
| 4425 | 4531 |         client, | 
| 4426 | 4532 |         resource_group_name=None, | 
| @@ -4469,7 +4575,9 @@ def server_update( | 
| 4469 | 4575 |         key_id=None, | 
| 4470 | 4576 |         federated_client_id=None, | 
| 4471 | 4577 |         identity_type=None, | 
| 4472 |  | -        user_assigned_identity_id=None): | 
|  | 4578 | +        user_assigned_identity_id=None, | 
|  | 4579 | +        enable_soft_delete=None, | 
|  | 4580 | +        soft_delete_retention_days=None): | 
| 4473 | 4581 |     ''' | 
| 4474 | 4582 |     Updates a server. Custom update function to apply parameters to instance. | 
| 4475 | 4583 |     ''' | 
| @@ -4507,6 +4615,17 @@ def server_update( | 
| 4507 | 4615 |     instance.key_id = (key_id or instance.key_id) | 
| 4508 | 4616 |     instance.federated_client_id = (federated_client_id or instance.federated_client_id) | 
| 4509 | 4617 | 
 | 
|  | 4618 | +    # Handle soft delete parameters | 
|  | 4619 | +    if enable_soft_delete is not None: | 
|  | 4620 | +        if soft_delete_retention_days is not None: | 
|  | 4621 | +            instance.retention_days = soft_delete_retention_days | 
|  | 4622 | +        elif enable_soft_delete: | 
|  | 4623 | +            # Set default to 7 days when enabling soft delete without specifying retention | 
|  | 4624 | +            instance.retention_days = 7 | 
|  | 4625 | +        else: | 
|  | 4626 | +            # When disabling soft delete, clear retention days | 
|  | 4627 | +            instance.retention_days = 0 | 
|  | 4628 | + | 
| 4510 | 4629 |     return instance | 
| 4511 | 4630 | 
 | 
| 4512 | 4631 | 
 | 
|  | 
0 commit comments