Skip to content
Merged
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 @@ -191,6 +191,12 @@ def add_connstr_props_argument(context):
validator=validate_connstr_props)


def add_no_recreate_arguments(context):
context.argument('no_recreate', options_list=['--no-recreate'],
arg_type=get_three_state_flag(), default=False,
help='Skip executing creation operation when no updates to an existing connection.')


def add_target_type_argument(context, source):
TARGET_TYPES = [
elem.value for elem in SUPPORTED_AUTH_TYPE.get(source).keys()]
Expand Down Expand Up @@ -321,6 +327,7 @@ def load_arguments(self, _): # pylint: disable=too-many-statements
add_customized_keys_argument(c)
add_opt_out_argument(c)
add_connstr_props_argument(c)
add_no_recreate_arguments(c)

with self.argument_context('{} connection update {}'.format(source.value, target.value)) as c:
add_client_type_argument(c, source, target)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
)
# from azure.cli.core._profile import Profile
from ._resource_config import (
SOURCE_RESOURCES_USERTOKEN,
TARGET_RESOURCES_USERTOKEN,
RESOURCE
)
from azure.cli.core import get_default_cli
Expand Down Expand Up @@ -126,8 +124,9 @@ def set_user_token_header(client, cli_ctx):

def set_user_token_by_source_and_target(client, cli_ctx, source, target):
'''Set user token header to work around OBO according to source and target'''
if source in SOURCE_RESOURCES_USERTOKEN or target in TARGET_RESOURCES_USERTOKEN:
return set_user_token_header(client, cli_ctx)
# deprecated
# if source in SOURCE_RESOURCES_USERTOKEN or target in TARGET_RESOURCES_USERTOKEN:
# return set_user_token_header(client, cli_ctx)
return client


Expand Down Expand Up @@ -566,10 +565,49 @@ def get_aks_resource_name(linker):
not (linker["targetService"]["resourceProperties"] is not None and
linker["targetService"]["resourceProperties"].get("connectAsKubernetesCsiDriver")):
service_account_name = f'sc-account-{linker["authInfo"].get("clientId")}'
return [secret_name, service_account_name]
return [secret_name]
return {'secret': secret_name, 'serviceAccount': service_account_name}
return {'secret': secret_name}
Comment thread
xfz11 marked this conversation as resolved.


def get_aks_resource_secret_name(connection_name):
valid_name = re.sub(r'[^a-zA-Z0-9]', '', connection_name, flags=re.IGNORECASE)
return f'sc-{valid_name}-secret'


def compare_properties_changed(new_props, existing_props):
"""
Deep comparison function that checks if there are meaningful differences
between new properties and existing properties, ignoring None values.
Returns True if there are differences that require an update.
"""
def has_meaningful_changes(new_value, existing_value):
if new_value is None or new_value == "":
return False

if isinstance(new_value, dict) and isinstance(existing_value, dict):
for key, value in new_value.items():
if key == "mysql-identity-id" or key == 'user_object_id':
continue
existing_props_key = key
if '_' in key:
existing_props_key = to_camel_case(key)
existing_nested = existing_value.get(existing_props_key) if existing_value else None
if has_meaningful_changes(value, existing_nested):
return True
return False
if isinstance(new_value, dict) and existing_value is None:
for value in new_value.values():
if value is not None and value != "":
return True
return False

return new_value != existing_value

return has_meaningful_changes(new_props, existing_props)


def to_camel_case(snake_str):
if not snake_str or '_' not in snake_str:
return snake_str
components = snake_str.split('_')
return components[0] + ''.join(word.capitalize() for word in components[1:])
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
ValidationError,
AzureResponseError
)
from azure.core.exceptions import ResourceNotFoundError
from ._resource_config import (
CLIENT_TYPE,
SUPPORTED_AUTH_TYPE,
Expand All @@ -31,6 +32,7 @@
)
from ._addon_factory import AddonFactory
from ._utils import (
compare_properties_changed,
set_user_token_by_source_and_target,
set_user_token_header,
auto_register,
Expand All @@ -43,7 +45,7 @@
get_secret_type_for_update
)
from ._credential_free import is_passwordless_command
# pylint: disable=unused-argument,unsubscriptable-object,unsupported-membership-test,too-many-statements,too-many-locals
# pylint: disable=unused-argument,unsubscriptable-object,unsupported-membership-test,too-many-statements,too-many-locals,broad-exception-caught


logger = get_logger(__name__)
Expand Down Expand Up @@ -317,7 +319,8 @@ def connection_create(cmd, client, # pylint: disable=too-many-locals,too-many-s
target_app_name=None, # Resource.ContainerApp
connstr_props=None, # Resource.FabricSql
fabric_workspace_uuid=None,
fabric_sql_db_uuid=None
fabric_sql_db_uuid=None,
no_recreate=False,
):
auth_action = 'optOutAllAuth' if (opt_out_list is not None and
OPT_OUT_OPTION.AUTHENTICATION.value in opt_out_list) else None
Expand Down Expand Up @@ -370,12 +373,13 @@ def connection_create(cmd, client, # pylint: disable=too-many-locals,too-many-s
app_config_id=app_config_id,
enable_appconfig_extension=enable_appconfig_extension,
server=server, database=database,
connstr_props=connstr_props
connstr_props=connstr_props,
no_recreate=no_recreate,
)


# The function is used in extension, new feature must be added in the end for backward compatibility
def connection_create_func(cmd, client, # pylint: disable=too-many-locals,too-many-statements
def connection_create_func(cmd, client, # pylint: disable=too-many-locals,too-many-statements,too-many-branches
connection_name=None, client_type=None,
source_resource_group=None, source_id=None,
target_resource_group=None, target_id=None,
Expand Down Expand Up @@ -406,6 +410,7 @@ def connection_create_func(cmd, client, # pylint: disable=too-many-locals,too-m
target_app_name=None, # Resource.ContainerApp
enable_appconfig_extension=False,
connstr_props=None, # Resource.FabricSql
no_recreate=False,
**kwargs,
):
if not source_id:
Expand Down Expand Up @@ -530,6 +535,20 @@ def connection_create_func(cmd, client, # pylint: disable=too-many-locals,too-m
'manually and then create the connection.'.format(str(e)))

validate_service_state(parameters)
if no_recreate:
try:
linker = todict(client.get(resource_uri=source_id, linker_name=connection_name))
if linker is not None and linker.get('provisioningState') == 'Accepted':
logger.warning('Connection provisioningState is Accepted, please retry later to avoid conflict.')
return linker
if linker is not None and linker.get('provisioningState') == 'Succeeded' and \
not compare_properties_changed(parameters, linker):
logger.warning(
'Connection exists and no property to be updated, skip the update operation.')
return linker
except ResourceNotFoundError:
logger.debug('No existing connection, continue to create it.')

if enable_mi_for_db_linker and auth_action != 'optOutAllAuth':
new_auth_info = enable_mi_for_db_linker(
cmd, source_id, target_id, auth_info, client_type, connection_name, connstr_props)
Expand Down