Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,19 @@
RM_URI_OVERRIDE = 'AZURE_CLI_RDBMS_RM_URI'
SUB_ID_OVERRIDE = 'AZURE_CLI_RDBMS_SUB_ID'

# Pin the flexible server management API version. The SDK default advanced to a newer
# preview; keep the CLI on the version the existing commands were built and recorded against.
MYSQL_FLEXIBLE_API_VERSION = '2025-06-01-preview'
# Fabric mirroring operations are only available from this newer API version.
MYSQL_FLEXIBLE_FABRIC_MIRRORING_API_VERSION = '2025-12-01-preview'

def get_mysql_flexible_management_client(cli_ctx, **_):

def get_mysql_flexible_management_client(cli_ctx, api_version=None, **_):
from os import getenv
from azure.mgmt.mysqlflexibleservers import MySQLManagementClient

api_version = api_version or MYSQL_FLEXIBLE_API_VERSION

# Allow overriding resource manager URI using environment variable
# for testing purposes. Subscription id is also determined by environment
# variable.
Expand All @@ -32,9 +40,10 @@ def get_mysql_flexible_management_client(cli_ctx, **_):
return MySQLManagementClient(
subscription_id=getenv(SUB_ID_OVERRIDE),
base_url=rm_uri_override,
credential=credentials)
credential=credentials,
api_version=api_version)
# Normal production scenario.
return get_mgmt_service_client(cli_ctx, MySQLManagementClient)
return get_mgmt_service_client(cli_ctx, MySQLManagementClient, api_version=api_version)


def get_mysql_flexible_management_client_by_sub(cli_ctx, subscription_id, **_):
Expand All @@ -54,9 +63,11 @@ def get_mysql_flexible_management_client_by_sub(cli_ctx, subscription_id, **_):
return MySQLManagementClient(
subscription_id=subscription_id,
base_url=rm_uri_override,
credential=credentials)
credential=credentials,
api_version=MYSQL_FLEXIBLE_API_VERSION)
# Normal production scenario.
return get_mgmt_service_client(cli_ctx, MySQLManagementClient, subscription_id=subscription_id)
return get_mgmt_service_client(cli_ctx, MySQLManagementClient, subscription_id=subscription_id,
api_version=MYSQL_FLEXIBLE_API_VERSION)


def get_mysql_management_client(cli_ctx, **_):
Expand Down Expand Up @@ -163,6 +174,11 @@ def private_dns_link_client_factory(cli_ctx, subscription_id=None):
subscription_id=subscription_id).virtual_network_links


def cf_mysql_flexible_fabric_mirroring_settings(cli_ctx, _):
return get_mysql_flexible_management_client(
cli_ctx, api_version=MYSQL_FLEXIBLE_FABRIC_MIRRORING_API_VERSION).fabric_mirroring_settings


# Operations for single server
def cf_mysql_servers(cli_ctx, _):
return get_mysql_management_client(cli_ctx).servers
Expand Down
30 changes: 30 additions & 0 deletions src/azure-cli/azure/cli/command_modules/mysql/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -929,3 +929,33 @@
- name: Create a export backup for 'testsvr' with backup name 'testbackup'.
text: az mysql flexible-server export create -g testgroup -n testsvr -b testbackup -u destsasuri
"""

helps['mysql flexible-server mirroring'] = """
type: group
short-summary: Manage Microsoft Fabric Mirroring for Azure Database for MySQL Flexible Server.
long-summary: |
Fabric Mirroring allows continuous data replication from MySQL Flexible Server into Microsoft Fabric for analytics and reporting.
"""

helps['mysql flexible-server mirroring enable'] = """
type: command
short-summary: Enable Fabric Mirroring for a MySQL Flexible Server.
long-summary: |
Configures the server for Fabric Mirroring.
Requires a User Assigned Managed Identity (UAMI) resource ID with appropriate permissions.
examples:
- name: Enable Fabric Mirroring for server 'testsvr' using a User Assigned Managed Identity.
text: >
az mysql flexible-server mirroring enable -g testgroup -n testsvr \
--identity-resource-id /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<name>
"""

helps['mysql flexible-server mirroring disable'] = """
type: command
short-summary: Disable Fabric Mirroring for a MySQL Flexible Server.
long-summary: |
Removes Fabric Mirroring configuration from the server.
examples:
- name: Disable Fabric Mirroring for server 'testsvr'.
text: az mysql flexible-server mirroring disable -g testgroup -n testsvr
"""
13 changes: 13 additions & 0 deletions src/azure-cli/azure/cli/command_modules/mysql/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,12 @@ def load_arguments(self, _): # pylint: disable=too-many-statements, too-many-
c.argument('configuration_list', action=AddArgs, nargs='*', options_list=['--args'], required=True, help='List of the configuration key-value pair.')
c.argument('source', options_list=['--source'], required=False, help='Source of the configuration.')

with self.argument_context('mysql flexible-server parameter list') as c:
c.argument('tags', help='The tags of the server configuration.')
c.argument('keyword', help='The keyword of the server configuration.')
c.argument('page', help='The page of the server configuration.')
c.argument('page_size', help='The page size of the server configuration.')

# firewall-rule
for scope in ['create', 'delete', 'list', 'show', 'update']:
argument_context_string = 'mysql flexible-server firewall-rule {}'.format(scope)
Expand Down Expand Up @@ -726,3 +732,10 @@ def load_arguments(self, _): # pylint: disable=too-many-statements, too-many-
elif scope == "check-name-availability":
c.argument('migration_name', arg_type=migration_id_arg_type, options_list=['--migration-name'],
help='Name of the migration.')

with self.argument_context('mysql flexible-server mirroring enable') as c:
c.argument('server_name', id_part=None, arg_type=server_name_arg_type)
c.argument('identity_resource_id', options_list=['--identity-resource-id'], help='Resource ID of the User Assigned Managed Identity (UAMI) used for Fabric Mirroring. Example: /subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identity-name}')

with self.argument_context('mysql flexible-server mirroring disable') as c:
c.argument('server_name', id_part=None, arg_type=server_name_arg_type)
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ def transform_backup(result):
result.name = result.name[62:]

if hasattr(result, 'system_data'):
delattr(result, 'system_data')
# Setting to None omits system_data from the serialized output (matches prior behavior).
result.system_data = None

return result

Expand Down
19 changes: 16 additions & 3 deletions src/azure-cli/azure/cli/command_modules/mysql/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from azure.cli.core.commands import CliCommandType
from azure.cli.command_modules.mysql._client_factory import (
cf_mysql_advanced_threat_protection,
Expand All @@ -17,7 +16,8 @@
cf_mysql_flexible_backups,
cf_mysql_flexible_adadmin,
cf_mysql_flexible_export,
cf_mysql_flexible_maintenances)
cf_mysql_flexible_maintenances,
cf_mysql_flexible_fabric_mirroring_settings)
from ._transformers import (
table_transform_output,
table_transform_output_list_servers,
Expand Down Expand Up @@ -95,6 +95,11 @@ def load_command_table(self, _):
client_factory=cf_mysql_flexible_maintenances
)

mysql_flexible_fabric_mirroring_settings_sdk = CliCommandType(
operations_tmpl='azure.mgmt.mysqlflexibleservers.operations#FabricMirroringSettingsOperations.{}',
client_factory=cf_mysql_flexible_fabric_mirroring_settings
)

# MERU COMMANDS
mysql_custom = CliCommandType(operations_tmpl='azure.cli.command_modules.mysql.custom#{}')

Expand Down Expand Up @@ -192,9 +197,10 @@ def load_command_table(self, _):
g.custom_command('download', 'flexible_server_log_download')

with self.command_group('mysql flexible-server backup', mysql_flexible_long_running_backup_sdk,
custom_command_type=mysql_custom,
client_factory=cf_mysql_flexible_backup) as g:
g.command('create', 'begin_create')
g.command('delete', 'begin_delete')
g.custom_command('delete', 'flexible_backup_delete')

with self.command_group('mysql flexible-server backup', mysql_flexible_long_running_backups_sdk,
client_factory=cf_mysql_flexible_backups) as g:
Expand Down Expand Up @@ -236,3 +242,10 @@ def load_command_table(self, _):
g.custom_command('reschedule', 'flexible_server_maintenance_reschedule')
g.custom_command('list', 'flexible_server_maintenance_list')
g.custom_show_command('show', 'flexible_server_maintenance_show')

with self.command_group('mysql flexible-server mirroring',
mysql_flexible_fabric_mirroring_settings_sdk,
custom_command_type=mysql_custom,
client_factory=cf_mysql_flexible_fabric_mirroring_settings) as g:
g.custom_command('enable', 'flexible_server_mirroring_enable')
g.custom_command('disable', 'flexible_server_mirroring_disable')
61 changes: 59 additions & 2 deletions src/azure-cli/azure/cli/command_modules/mysql/custom.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
Expand Down Expand Up @@ -277,7 +278,8 @@ def flexible_server_log_list(client, resource_group_name, server_name, filename_
if max_file_size is not None and f.size_in_kb > max_file_size:
continue

del f.created_time
# Setting to None omits created_time from the serialized output (matches prior behavior).
f.created_time = None
files.append(f)

return files
Expand Down Expand Up @@ -1321,7 +1323,17 @@ def flexible_parameter_update_batch(client, server_name, resource_group_name, so
value=configurations
)

return client.begin_batch_update(resource_group_name, server_name, parameters)
# The batch update is a long-running operation whose final response body is empty with the
# current SDK, so surface the operation status to keep the command output meaningful.
poller = client.begin_batch_update(resource_group_name, server_name, parameters)
poller.result()
return {'status': poller.status()}


def flexible_backup_delete(client, resource_group_name, server_name, backup_name):
# Invoke the operation as a bound method so the SDK api-version validation decorator
# receives the client as its first positional argument.
return client.begin_delete(resource_group_name, server_name, backup_name)


# Replica commands
Expand Down Expand Up @@ -2031,6 +2043,51 @@ def migrate_firewall_rules_from_single_to_flex(db_context, cmd, source_server_id
firewall_rule_name=rule.name)


def _fm_settings_payload(state, uami=None):
"""
Build the ARM request body for a FabricMirroringSetting ('Default').
The 1.1.0b3 SDK model is nested: properties.state / properties.identityResourceId.
"""
# Build the ARM request body. In SDK 1.1.0b3 the model is nested
# (properties.state / properties.identityResourceId), NOT flattened. A plain dict is
# accepted by the (typespec-generated) SDK and avoids the model-name/flatten mismatch.
props = {'state': state}
if uami:
props['identityResourceId'] = uami
return {'properties': props}


def flexible_server_mirroring_enable(cmd, client, resource_group_name, server_name, identity_resource_id):
"""
'Enable' translates to PUT the 'Default' FabricMirroringSettings with state=Enabled and UAMI.
The Swagger limits settings name to 'Default'.
"""
if not identity_resource_id:
raise RequiredArgumentMissingError(
"Parameter --identity-resource-id is required when enabling fabric mirroring."
)

payload = _fm_settings_payload(state='Enabled', uami=identity_resource_id)

# Long-running operation
poller = (getattr(client, 'begin_create_or_update', None) or getattr(client, 'begin_put'))(
resource_group_name, server_name, 'Default', payload
)
return resolve_poller(poller, cmd.cli_ctx, 'Enable Fabric mirroring')


def flexible_server_mirroring_disable(cmd, client, resource_group_name, server_name):
"""
'Disable' translates to PUT the 'Default' FabricMirroringSettings with state=Disabled.
identityResourceId can be omitted when disabling<97>service will deactivate/clean bindings.
"""
payload = _fm_settings_payload(state='Disabled', uami=None)
poller = (getattr(client, 'begin_create_or_update', None) or getattr(client, 'begin_put'))(
resource_group_name, server_name, 'Default', payload
)
return resolve_poller(poller, cmd.cli_ctx, 'Disable Fabric mirroring')


# pylint: disable=too-many-instance-attributes, too-few-public-methods
class DbContext:
def __init__(self, cmd=None, azure_sdk=None, logging_name=None, cf_firewall=None, cf_db=None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,31 @@ mysql flexible-server export create:
mysql flexible-server import stop-replication:
rule_exclusions:
- missing_command_test_coverage

mysql flexible-server mirroring enable:
parameters:
server_name:
rule_exclusions:
- missing_parameter_test_coverage

mysql flexible-server mirroring disable:
parameters:
server_name:
rule_exclusions:
- missing_parameter_test_coverage

mysql flexible-server parameter list:
parameters:
tags:
rule_exclusions:
- missing_parameter_test_coverage
keyword:
rule_exclusions:
- missing_parameter_test_coverage
page:
rule_exclusions:
- missing_parameter_test_coverage
page_size:
rule_exclusions:
- missing_parameter_test_coverage
...
Original file line number Diff line number Diff line change
Expand Up @@ -2374,3 +2374,42 @@ def _test_flexible_server_export_create_mgmt(self, database_engine, resource_gro

# deletion of single server created
self.cmd('{} flexible-server delete -g {} -n {} --yes'.format(database_engine, resource_group, server), checks=NoneCheck())


class MySQLFabricMirroringEnableDisableTest(ScenarioTest):
"""Scenario tests for Fabric Mirroring enable/disable commands."""

@AllowLargeResponse()
@ResourceGroupPreparer(location='eastus2euap')
@live_only()
def test_mysql_fabric_mirroring_enable_disable(self, resource_group):
# Arrange
server_name = self.create_random_name(SERVER_NAME_PREFIX, SERVER_NAME_MAX_LENGTH)
identity_name = self.create_random_name('identity', 24)
location = 'eastus2euap'

# Create server (GeneralPurpose tier required for Fabric Mirroring, using Standard_D2ads_v5 valid in eastus2euap)
create_result = self.cmd('mysql flexible-server create -l {} -g {} -n {} --public-access {} --tier GeneralPurpose --sku-name {}'
.format(location, resource_group, server_name, '0.0.0.0', 'Standard_D2ads_v5')).get_output_in_json()

# Verify server was created by checking the host contains the server name
self.assertIn(server_name, create_result['host'])

# Set binlog_row_image to 'noblob' as required for Fabric Mirroring
self.cmd('mysql flexible-server parameter set -g {} -s {} -n binlog_row_image -v noblob'
.format(resource_group, server_name))

# Create User Assigned Managed Identity
identity_result = self.cmd('identity create -g {} -n {}'.format(resource_group, identity_name)).get_output_in_json()
identity_id = identity_result['id']

# Enable Fabric Mirroring
self.cmd('mysql flexible-server mirroring enable -g {} -n {} --identity-resource-id {}'
.format(resource_group, server_name, identity_id))

# Disable Fabric Mirroring
self.cmd('mysql flexible-server mirroring disable -g {} -n {}'
.format(resource_group, server_name))

# Cleanup
self.cmd('mysql flexible-server delete -g {} -n {} --yes'.format(resource_group, server_name))
2 changes: 1 addition & 1 deletion src/azure-cli/requirements.py3.Darwin.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ azure-mgmt-policyinsights==1.1.0b4
azure-mgmt-postgresqlflexibleservers==3.0.0b2
azure-mgmt-privatedns==1.0.0
azure-mgmt-rdbms==10.2.0b17
azure-mgmt-mysqlflexibleservers==1.1.0b2
azure-mgmt-mysqlflexibleservers==1.1.0b3
azure-mgmt-recoveryservices==4.0.0
azure-mgmt-recoveryservicesbackup==9.2.0
azure-mgmt-redhatopenshift~=3.0.0
Expand Down
2 changes: 1 addition & 1 deletion src/azure-cli/requirements.py3.Linux.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ azure-mgmt-policyinsights==1.1.0b4
azure-mgmt-postgresqlflexibleservers==3.0.0b2
azure-mgmt-privatedns==1.0.0
azure-mgmt-rdbms==10.2.0b17
azure-mgmt-mysqlflexibleservers==1.1.0b2
azure-mgmt-mysqlflexibleservers==1.1.0b3
azure-mgmt-recoveryservices==4.0.0
azure-mgmt-recoveryservicesbackup==9.2.0
azure-mgmt-redhatopenshift~=3.0.0
Expand Down
2 changes: 1 addition & 1 deletion src/azure-cli/requirements.py3.windows.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ azure-mgmt-policyinsights==1.1.0b4
azure-mgmt-postgresqlflexibleservers==3.0.0b2
azure-mgmt-privatedns==1.0.0
azure-mgmt-rdbms==10.2.0b17
azure-mgmt-mysqlflexibleservers==1.1.0b2
azure-mgmt-mysqlflexibleservers==1.1.0b3
azure-mgmt-recoveryservices==4.0.0
azure-mgmt-recoveryservicesbackup==9.2.0
azure-mgmt-redhatopenshift~=3.0.0
Expand Down
2 changes: 1 addition & 1 deletion src/azure-cli/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
'azure-mgmt-postgresqlflexibleservers==3.0.0b2',
'azure-mgmt-privatedns~=1.0.0',
'azure-mgmt-rdbms==10.2.0b17',
'azure-mgmt-mysqlflexibleservers==1.1.0b2',
'azure-mgmt-mysqlflexibleservers==1.1.0b3',
'azure-mgmt-recoveryservicesbackup~=9.2.0',
'azure-mgmt-recoveryservices~=4.0.0',
'azure-mgmt-redhatopenshift~=3.0.0',
Expand Down
Loading