From 06f5f1ed6930708f04464e977e3a6e935cf0a936 Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Mon, 27 Oct 2025 09:49:50 -0700 Subject: [PATCH 01/25] Add stub code --- src/change-state/HISTORY.rst | 8 + src/change-state/README.md | 5 + .../azext_change_state/__init__.py | 42 + src/change-state/azext_change_state/_help.py | 11 + .../azext_change_state/_params.py | 13 + .../azext_change_state/aaz/__init__.py | 6 + .../azext_change_state/aaz/latest/__init__.py | 10 + .../aaz/latest/change_safety/__cmd_group.py | 23 + .../aaz/latest/change_safety/__init__.py | 11 + .../change_safety/change_state/__cmd_group.py | 23 + .../change_safety/change_state/__init__.py | 15 + .../change_safety/change_state/_create.py | 1030 +++++++++++++++++ .../change_safety/change_state/_delete.py | 248 ++++ .../change_safety/change_state/_show.py | 610 ++++++++++ .../change_safety/change_state/_update.py | 1012 ++++++++++++++++ .../azext_change_state/azext_metadata.json | 4 + .../azext_change_state/commands.py | 15 + src/change-state/azext_change_state/custom.py | 14 + .../azext_change_state/tests/__init__.py | 6 + .../tests/latest/__init__.py | 6 + .../tests/latest/test_change_state.py | 13 + src/change-state/setup.cfg | 1 + src/change-state/setup.py | 49 + 23 files changed, 3175 insertions(+) create mode 100644 src/change-state/HISTORY.rst create mode 100644 src/change-state/README.md create mode 100644 src/change-state/azext_change_state/__init__.py create mode 100644 src/change-state/azext_change_state/_help.py create mode 100644 src/change-state/azext_change_state/_params.py create mode 100644 src/change-state/azext_change_state/aaz/__init__.py create mode 100644 src/change-state/azext_change_state/aaz/latest/__init__.py create mode 100644 src/change-state/azext_change_state/aaz/latest/change_safety/__cmd_group.py create mode 100644 src/change-state/azext_change_state/aaz/latest/change_safety/__init__.py create mode 100644 src/change-state/azext_change_state/aaz/latest/change_safety/change_state/__cmd_group.py create mode 100644 src/change-state/azext_change_state/aaz/latest/change_safety/change_state/__init__.py create mode 100644 src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_create.py create mode 100644 src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_delete.py create mode 100644 src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_show.py create mode 100644 src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_update.py create mode 100644 src/change-state/azext_change_state/azext_metadata.json create mode 100644 src/change-state/azext_change_state/commands.py create mode 100644 src/change-state/azext_change_state/custom.py create mode 100644 src/change-state/azext_change_state/tests/__init__.py create mode 100644 src/change-state/azext_change_state/tests/latest/__init__.py create mode 100644 src/change-state/azext_change_state/tests/latest/test_change_state.py create mode 100644 src/change-state/setup.cfg create mode 100644 src/change-state/setup.py diff --git a/src/change-state/HISTORY.rst b/src/change-state/HISTORY.rst new file mode 100644 index 00000000000..abbff5a61a7 --- /dev/null +++ b/src/change-state/HISTORY.rst @@ -0,0 +1,8 @@ +.. :changelog: + +Release History +=============== + +1.0.0b1 +++++++ +* Initial release. \ No newline at end of file diff --git a/src/change-state/README.md b/src/change-state/README.md new file mode 100644 index 00000000000..9e4e42b3250 --- /dev/null +++ b/src/change-state/README.md @@ -0,0 +1,5 @@ +# Azure CLI ChangeState Extension # +This is an extension to Azure CLI to manage ChangeState resources. + +## How to use ## +Please add commands usage here. \ No newline at end of file diff --git a/src/change-state/azext_change_state/__init__.py b/src/change-state/azext_change_state/__init__.py new file mode 100644 index 00000000000..a2755a92cdb --- /dev/null +++ b/src/change-state/azext_change_state/__init__.py @@ -0,0 +1,42 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +from azure.cli.core import AzCommandsLoader +from azext_change_state._help import helps # pylint: disable=unused-import + + +class ChangeStateCommandsLoader(AzCommandsLoader): + + def __init__(self, cli_ctx=None): + from azure.cli.core.commands import CliCommandType + custom_command_type = CliCommandType( + operations_tmpl='azext_change_state.custom#{}') + super().__init__(cli_ctx=cli_ctx, + custom_command_type=custom_command_type) + + def load_command_table(self, args): + from azext_change_state.commands import load_command_table + from azure.cli.core.aaz import load_aaz_command_table + try: + from . import aaz + except ImportError: + aaz = None + if aaz: + load_aaz_command_table( + loader=self, + aaz_pkg_name=aaz.__name__, + args=args + ) + load_command_table(self, args) + return self.command_table + + def load_arguments(self, command): + from azext_change_state._params import load_arguments + load_arguments(self, command) + + +COMMAND_LOADER_CLS = ChangeStateCommandsLoader diff --git a/src/change-state/azext_change_state/_help.py b/src/change-state/azext_change_state/_help.py new file mode 100644 index 00000000000..126d5d00714 --- /dev/null +++ b/src/change-state/azext_change_state/_help.py @@ -0,0 +1,11 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: disable=line-too-long +# pylint: disable=too-many-lines + +from knack.help_files import helps # pylint: disable=unused-import diff --git a/src/change-state/azext_change_state/_params.py b/src/change-state/azext_change_state/_params.py new file mode 100644 index 00000000000..cfcec717c9c --- /dev/null +++ b/src/change-state/azext_change_state/_params.py @@ -0,0 +1,13 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: disable=too-many-lines +# pylint: disable=too-many-statements + + +def load_arguments(self, _): # pylint: disable=unused-argument + pass diff --git a/src/change-state/azext_change_state/aaz/__init__.py b/src/change-state/azext_change_state/aaz/__init__.py new file mode 100644 index 00000000000..5757aea3175 --- /dev/null +++ b/src/change-state/azext_change_state/aaz/__init__.py @@ -0,0 +1,6 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- diff --git a/src/change-state/azext_change_state/aaz/latest/__init__.py b/src/change-state/azext_change_state/aaz/latest/__init__.py new file mode 100644 index 00000000000..f6acc11aa4e --- /dev/null +++ b/src/change-state/azext_change_state/aaz/latest/__init__.py @@ -0,0 +1,10 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/__cmd_group.py b/src/change-state/azext_change_state/aaz/latest/change_safety/__cmd_group.py new file mode 100644 index 00000000000..c6dd5b296ba --- /dev/null +++ b/src/change-state/azext_change_state/aaz/latest/change_safety/__cmd_group.py @@ -0,0 +1,23 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "change-safety", +) +class __CMDGroup(AAZCommandGroup): + """Manage Change Safety + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/__init__.py b/src/change-state/azext_change_state/aaz/latest/change_safety/__init__.py new file mode 100644 index 00000000000..5a9d61963d6 --- /dev/null +++ b/src/change-state/azext_change_state/aaz/latest/change_safety/__init__.py @@ -0,0 +1,11 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/__cmd_group.py b/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/__cmd_group.py new file mode 100644 index 00000000000..00d6ce9e052 --- /dev/null +++ b/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/__cmd_group.py @@ -0,0 +1,23 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "change-safety change-state", +) +class __CMDGroup(AAZCommandGroup): + """Manage Change State + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/__init__.py b/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/__init__.py new file mode 100644 index 00000000000..a3db3e36481 --- /dev/null +++ b/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/__init__.py @@ -0,0 +1,15 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * +from ._create import * +from ._delete import * +from ._show import * +from ._update import * diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_create.py b/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_create.py new file mode 100644 index 00000000000..35c38872ced --- /dev/null +++ b/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_create.py @@ -0,0 +1,1030 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "change-safety change-state create", +) +class Create(AAZCommand): + """Create a ChangeState + """ + + _aaz_info = { + "version": "2025-09-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.change_state_name = AAZStrArg( + options=["-n", "--name", "--change-state-name"], + help="The name of the ChangeState resource.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg() + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.additional_data = AAZObjectArg( + options=["--additional-data"], + arg_group="Properties", + help="Additional metadata for the change required for various orchestration tools.", + blank={}, + ) + _args_schema.anticipated_end_time = AAZDateTimeArg( + options=["--anticipated-end-time"], + arg_group="Properties", + help="Expected completion time when the change should be finished, in ISO 8601 format.", + fmt=AAZDateTimeFormat( + protocol="iso", + ), + ) + _args_schema.anticipated_start_time = AAZDateTimeArg( + options=["--anticipated-start-time"], + arg_group="Properties", + help="Expected start time when the change execution should begin, in ISO 8601 format.", + fmt=AAZDateTimeFormat( + protocol="iso", + ), + ) + _args_schema.change_type = AAZStrArg( + options=["--change-type"], + arg_group="Properties", + help="Describes the nature of the change.", + enum={"AppDeployment": "AppDeployment", "Config": "Config", "ManualTouch": "ManualTouch", "PolicyDeployment": "PolicyDeployment"}, + ) + _args_schema.comments = AAZStrArg( + options=["--comments"], + arg_group="Properties", + help="Comments about the last update to the changeState resource.", + fmt=AAZStrArgFormat( + max_length=2000, + ), + ) + _args_schema.description = AAZStrArg( + options=["--description"], + arg_group="Properties", + help="Brief description about the change.", + fmt=AAZStrArgFormat( + max_length=2000, + ), + ) + _args_schema.links = AAZListArg( + options=["--links"], + arg_group="Properties", + help="Collection of related links for the change.", + ) + _args_schema.orchestration_tool = AAZStrArg( + options=["--orchestration-tool"], + arg_group="Properties", + help="Tool used for deployment orchestration of this change.", + ) + _args_schema.parameters = AAZDictArg( + options=["--parameters"], + arg_group="Properties", + help="Schema of parameters that will be provided for each stageProgression.", + ) + _args_schema.release_label = AAZStrArg( + options=["--release-label"], + arg_group="Properties", + help="Label for the release associated with this change.", + ) + _args_schema.rollout_type = AAZStrArg( + options=["--rollout-type"], + arg_group="Properties", + help="Describes the type of the rollout used for the change.", + enum={"Emergency": "Emergency", "Hotfix": "Hotfix", "Normal": "Normal"}, + ) + _args_schema.stage_map = AAZObjectArg( + options=["--stage-map"], + arg_group="Properties", + help="Reference to the StageMap, defining progression.", + ) + + links = cls._args_schema.links + links.Element = AAZObjectArg() + + _element = cls._args_schema.links.Element + _element.description = AAZStrArg( + options=["description"], + help="Description or note about the link.", + fmt=AAZStrArgFormat( + max_length=2000, + ), + ) + _element.name = AAZStrArg( + options=["name"], + help="name of the link.", + required=True, + ) + _element.uri = AAZStrArg( + options=["uri"], + help="URL or comma separated URLs for the link.", + required=True, + ) + + parameters = cls._args_schema.parameters + parameters.Element = AAZObjectArg() + + _element = cls._args_schema.parameters.Element + _element.array = AAZObjectArg( + options=["array"], + ) + _element.metadata = AAZDictArg( + options=["metadata"], + help="user-specified parameter metadata", + ) + _element.number = AAZObjectArg( + options=["number"], + ) + _element.object = AAZObjectArg( + options=["object"], + ) + _element.string = AAZObjectArg( + options=["string"], + ) + + array = cls._args_schema.parameters.Element.array + array.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + ) + array.default_value = AAZListArg( + options=["default-value"], + help="Default value for the parameter.", + ) + + allowed_values = cls._args_schema.parameters.Element.array.allowed_values + allowed_values.Element = AAZAnyTypeArg() + + default_value = cls._args_schema.parameters.Element.array.default_value + default_value.Element = AAZAnyTypeArg() + + metadata = cls._args_schema.parameters.Element.metadata + metadata.Element = AAZStrArg() + + number = cls._args_schema.parameters.Element.number + number.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + ) + number.default_value = AAZIntArg( + options=["default-value"], + help="Default value for the parameter.", + ) + + allowed_values = cls._args_schema.parameters.Element.number.allowed_values + allowed_values.Element = AAZIntArg() + + object = cls._args_schema.parameters.Element.object + object.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + ) + object.default_value = AAZObjectArg( + options=["default-value"], + help="Default value for the parameter.", + blank={}, + ) + + allowed_values = cls._args_schema.parameters.Element.object.allowed_values + allowed_values.Element = AAZDictArg() + + _element = cls._args_schema.parameters.Element.object.allowed_values.Element + _element.Element = AAZAnyTypeArg() + + string = cls._args_schema.parameters.Element.string + string.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + ) + string.default_value = AAZStrArg( + options=["default-value"], + help="Default value for the parameter.", + ) + + allowed_values = cls._args_schema.parameters.Element.string.allowed_values + allowed_values.Element = AAZStrArg() + + stage_map = cls._args_schema.stage_map + stage_map.parameters = AAZDictArg( + options=["parameters"], + help="Key value pairs of parameter names & their values for the stageMap referenced by the resourceId field.", + ) + stage_map.resource_id = AAZStrArg( + options=["resource-id"], + help="ARM resource ID for the nested stagemap resource.", + ) + + parameters = cls._args_schema.stage_map.parameters + parameters.Element = AAZAnyTypeArg() + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) + if condition_0: + self.ChangeStatesCreateOrUpdateAtSubscriptionLevel(ctx=self.ctx)() + if condition_1: + self.ChangeStatesCreateOrUpdate(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class ChangeStatesCreateOrUpdateAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200, 201]: + return self.on_200_201(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + typ=AAZObjectType, + typ_kwargs={"flags": {"required": True, "client_flatten": True}} + ) + _builder.set_prop("properties", AAZObjectType) + + properties = _builder.get(".properties") + if properties is not None: + properties.set_prop("additionalData", AAZObjectType, ".additional_data") + properties.set_prop("anticipatedEndTime", AAZStrType, ".anticipated_end_time", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("anticipatedStartTime", AAZStrType, ".anticipated_start_time", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("changeType", AAZStrType, ".change_type", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("comments", AAZStrType, ".comments") + properties.set_prop("description", AAZStrType, ".description") + properties.set_prop("links", AAZListType, ".links") + properties.set_prop("orchestrationTool", AAZStrType, ".orchestration_tool") + properties.set_prop("parameters", AAZDictType, ".parameters") + properties.set_prop("releaseLabel", AAZStrType, ".release_label") + properties.set_prop("rolloutType", AAZStrType, ".rollout_type", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("stageMap", AAZObjectType, ".stage_map") + + links = _builder.get(".properties.links") + if links is not None: + links.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.links[]") + if _elements is not None: + _elements.set_prop("description", AAZStrType, ".description") + _elements.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}}) + _elements.set_prop("uri", AAZStrType, ".uri", typ_kwargs={"flags": {"required": True}}) + + parameters = _builder.get(".properties.parameters") + if parameters is not None: + parameters.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.parameters{}") + if _elements is not None: + _elements.set_prop("metadata", AAZDictType, ".metadata") + _elements.set_const("type", "array", AAZStrType, ".array", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "number", AAZStrType, ".number", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "object", AAZStrType, ".object", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "string", AAZStrType, ".string", typ_kwargs={"flags": {"required": True}}) + _elements.discriminate_by("type", "array") + _elements.discriminate_by("type", "number") + _elements.discriminate_by("type", "object") + _elements.discriminate_by("type", "string") + + metadata = _builder.get(".properties.parameters{}.metadata") + if metadata is not None: + metadata.set_elements(AAZStrType, ".") + + disc_array = _builder.get(".properties.parameters{}{type:array}") + if disc_array is not None: + disc_array.set_prop("allowedValues", AAZListType, ".array.allowed_values") + disc_array.set_prop("defaultValue", AAZListType, ".array.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:array}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZAnyType, ".") + + default_value = _builder.get(".properties.parameters{}{type:array}.defaultValue") + if default_value is not None: + default_value.set_elements(AAZAnyType, ".") + + disc_number = _builder.get(".properties.parameters{}{type:number}") + if disc_number is not None: + disc_number.set_prop("allowedValues", AAZListType, ".number.allowed_values") + disc_number.set_prop("defaultValue", AAZIntType, ".number.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:number}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZIntType, ".") + + disc_object = _builder.get(".properties.parameters{}{type:object}") + if disc_object is not None: + disc_object.set_prop("allowedValues", AAZListType, ".object.allowed_values") + disc_object.set_prop("defaultValue", AAZObjectType, ".object.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:object}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZDictType, ".") + + _elements = _builder.get(".properties.parameters{}{type:object}.allowedValues[]") + if _elements is not None: + _elements.set_elements(AAZAnyType, ".") + + disc_string = _builder.get(".properties.parameters{}{type:string}") + if disc_string is not None: + disc_string.set_prop("allowedValues", AAZListType, ".string.allowed_values") + disc_string.set_prop("defaultValue", AAZStrType, ".string.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:string}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZStrType, ".") + + stage_map = _builder.get(".properties.stageMap") + if stage_map is not None: + stage_map.set_prop("parameters", AAZDictType, ".parameters") + stage_map.set_prop("resourceId", AAZStrType, ".resource_id") + + parameters = _builder.get(".properties.stageMap.parameters") + if parameters is not None: + parameters.set_elements(AAZAnyType, ".") + + return self.serialize_content(_content_value) + + def on_200_201(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200_201 + ) + + _schema_on_200_201 = None + + @classmethod + def _build_schema_on_200_201(cls): + if cls._schema_on_200_201 is not None: + return cls._schema_on_200_201 + + cls._schema_on_200_201 = AAZObjectType() + + _schema_on_200_201 = cls._schema_on_200_201 + _schema_on_200_201.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200_201.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200_201.properties = AAZObjectType() + _schema_on_200_201.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200_201.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200_201.properties + properties.additional_data = AAZObjectType( + serialized_name="additionalData", + ) + properties.anticipated_end_time = AAZStrType( + serialized_name="anticipatedEndTime", + flags={"required": True}, + ) + properties.anticipated_start_time = AAZStrType( + serialized_name="anticipatedStartTime", + flags={"required": True}, + ) + properties.change_definition = AAZObjectType( + serialized_name="changeDefinition", + flags={"required": True}, + ) + properties.change_type = AAZStrType( + serialized_name="changeType", + flags={"required": True}, + ) + properties.comments = AAZStrType() + properties.description = AAZStrType() + properties.links = AAZListType() + properties.orchestration_tool = AAZStrType( + serialized_name="orchestrationTool", + ) + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.release_label = AAZStrType( + serialized_name="releaseLabel", + ) + properties.rollout_type = AAZStrType( + serialized_name="rolloutType", + flags={"required": True}, + ) + properties.stage_map = AAZObjectType( + serialized_name="stageMap", + ) + properties.stage_map_snapshot = AAZListType( + serialized_name="stageMapSnapshot", + flags={"read_only": True}, + ) + properties.status = AAZStrType( + flags={"read_only": True}, + ) + + change_definition = cls._schema_on_200_201.properties.change_definition + change_definition.details = AAZObjectType( + flags={"required": True}, + ) + change_definition.kind = AAZStrType( + flags={"required": True}, + ) + change_definition.name = AAZStrType( + flags={"required": True}, + ) + + links = cls._schema_on_200_201.properties.links + links.Element = AAZObjectType() + + _element = cls._schema_on_200_201.properties.links.Element + _element.description = AAZStrType() + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.uri = AAZStrType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200_201.properties.parameters + parameters.Element = AAZObjectType() + + _element = cls._schema_on_200_201.properties.parameters.Element + _element.metadata = AAZDictType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + metadata = cls._schema_on_200_201.properties.parameters.Element.metadata + metadata.Element = AAZStrType() + + disc_array = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "array") + disc_array.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_array.default_value = AAZListType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values.Element = AAZAnyType() + + default_value = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value.Element = AAZAnyType() + + disc_number = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "number") + disc_number.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_number.default_value = AAZIntType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values.Element = AAZIntType() + + disc_object = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "object") + disc_object.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_object.default_value = AAZObjectType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values.Element = AAZDictType() + + _element = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element.Element = AAZAnyType() + + disc_string = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "string") + disc_string.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_string.default_value = AAZStrType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values.Element = AAZStrType() + + stage_map = cls._schema_on_200_201.properties.stage_map + stage_map.parameters = AAZDictType() + stage_map.resource_id = AAZStrType( + serialized_name="resourceId", + ) + + parameters = cls._schema_on_200_201.properties.stage_map.parameters + parameters.Element = AAZAnyType() + + stage_map_snapshot = cls._schema_on_200_201.properties.stage_map_snapshot + stage_map_snapshot.Element = AAZAnyType() + + system_data = cls._schema_on_200_201.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200_201 + + class ChangeStatesCreateOrUpdate(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200, 201]: + return self.on_200_201(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + typ=AAZObjectType, + typ_kwargs={"flags": {"required": True, "client_flatten": True}} + ) + _builder.set_prop("properties", AAZObjectType) + + properties = _builder.get(".properties") + if properties is not None: + properties.set_prop("additionalData", AAZObjectType, ".additional_data") + properties.set_prop("anticipatedEndTime", AAZStrType, ".anticipated_end_time", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("anticipatedStartTime", AAZStrType, ".anticipated_start_time", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("changeType", AAZStrType, ".change_type", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("comments", AAZStrType, ".comments") + properties.set_prop("description", AAZStrType, ".description") + properties.set_prop("links", AAZListType, ".links") + properties.set_prop("orchestrationTool", AAZStrType, ".orchestration_tool") + properties.set_prop("parameters", AAZDictType, ".parameters") + properties.set_prop("releaseLabel", AAZStrType, ".release_label") + properties.set_prop("rolloutType", AAZStrType, ".rollout_type", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("stageMap", AAZObjectType, ".stage_map") + + links = _builder.get(".properties.links") + if links is not None: + links.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.links[]") + if _elements is not None: + _elements.set_prop("description", AAZStrType, ".description") + _elements.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}}) + _elements.set_prop("uri", AAZStrType, ".uri", typ_kwargs={"flags": {"required": True}}) + + parameters = _builder.get(".properties.parameters") + if parameters is not None: + parameters.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.parameters{}") + if _elements is not None: + _elements.set_prop("metadata", AAZDictType, ".metadata") + _elements.set_const("type", "array", AAZStrType, ".array", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "number", AAZStrType, ".number", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "object", AAZStrType, ".object", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "string", AAZStrType, ".string", typ_kwargs={"flags": {"required": True}}) + _elements.discriminate_by("type", "array") + _elements.discriminate_by("type", "number") + _elements.discriminate_by("type", "object") + _elements.discriminate_by("type", "string") + + metadata = _builder.get(".properties.parameters{}.metadata") + if metadata is not None: + metadata.set_elements(AAZStrType, ".") + + disc_array = _builder.get(".properties.parameters{}{type:array}") + if disc_array is not None: + disc_array.set_prop("allowedValues", AAZListType, ".array.allowed_values") + disc_array.set_prop("defaultValue", AAZListType, ".array.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:array}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZAnyType, ".") + + default_value = _builder.get(".properties.parameters{}{type:array}.defaultValue") + if default_value is not None: + default_value.set_elements(AAZAnyType, ".") + + disc_number = _builder.get(".properties.parameters{}{type:number}") + if disc_number is not None: + disc_number.set_prop("allowedValues", AAZListType, ".number.allowed_values") + disc_number.set_prop("defaultValue", AAZIntType, ".number.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:number}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZIntType, ".") + + disc_object = _builder.get(".properties.parameters{}{type:object}") + if disc_object is not None: + disc_object.set_prop("allowedValues", AAZListType, ".object.allowed_values") + disc_object.set_prop("defaultValue", AAZObjectType, ".object.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:object}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZDictType, ".") + + _elements = _builder.get(".properties.parameters{}{type:object}.allowedValues[]") + if _elements is not None: + _elements.set_elements(AAZAnyType, ".") + + disc_string = _builder.get(".properties.parameters{}{type:string}") + if disc_string is not None: + disc_string.set_prop("allowedValues", AAZListType, ".string.allowed_values") + disc_string.set_prop("defaultValue", AAZStrType, ".string.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:string}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZStrType, ".") + + stage_map = _builder.get(".properties.stageMap") + if stage_map is not None: + stage_map.set_prop("parameters", AAZDictType, ".parameters") + stage_map.set_prop("resourceId", AAZStrType, ".resource_id") + + parameters = _builder.get(".properties.stageMap.parameters") + if parameters is not None: + parameters.set_elements(AAZAnyType, ".") + + return self.serialize_content(_content_value) + + def on_200_201(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200_201 + ) + + _schema_on_200_201 = None + + @classmethod + def _build_schema_on_200_201(cls): + if cls._schema_on_200_201 is not None: + return cls._schema_on_200_201 + + cls._schema_on_200_201 = AAZObjectType() + + _schema_on_200_201 = cls._schema_on_200_201 + _schema_on_200_201.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200_201.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200_201.properties = AAZObjectType() + _schema_on_200_201.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200_201.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200_201.properties + properties.additional_data = AAZObjectType( + serialized_name="additionalData", + ) + properties.anticipated_end_time = AAZStrType( + serialized_name="anticipatedEndTime", + flags={"required": True}, + ) + properties.anticipated_start_time = AAZStrType( + serialized_name="anticipatedStartTime", + flags={"required": True}, + ) + properties.change_definition = AAZObjectType( + serialized_name="changeDefinition", + flags={"required": True}, + ) + properties.change_type = AAZStrType( + serialized_name="changeType", + flags={"required": True}, + ) + properties.comments = AAZStrType() + properties.description = AAZStrType() + properties.links = AAZListType() + properties.orchestration_tool = AAZStrType( + serialized_name="orchestrationTool", + ) + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.release_label = AAZStrType( + serialized_name="releaseLabel", + ) + properties.rollout_type = AAZStrType( + serialized_name="rolloutType", + flags={"required": True}, + ) + properties.stage_map = AAZObjectType( + serialized_name="stageMap", + ) + properties.stage_map_snapshot = AAZListType( + serialized_name="stageMapSnapshot", + flags={"read_only": True}, + ) + properties.status = AAZStrType( + flags={"read_only": True}, + ) + + change_definition = cls._schema_on_200_201.properties.change_definition + change_definition.details = AAZObjectType( + flags={"required": True}, + ) + change_definition.kind = AAZStrType( + flags={"required": True}, + ) + change_definition.name = AAZStrType( + flags={"required": True}, + ) + + links = cls._schema_on_200_201.properties.links + links.Element = AAZObjectType() + + _element = cls._schema_on_200_201.properties.links.Element + _element.description = AAZStrType() + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.uri = AAZStrType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200_201.properties.parameters + parameters.Element = AAZObjectType() + + _element = cls._schema_on_200_201.properties.parameters.Element + _element.metadata = AAZDictType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + metadata = cls._schema_on_200_201.properties.parameters.Element.metadata + metadata.Element = AAZStrType() + + disc_array = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "array") + disc_array.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_array.default_value = AAZListType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values.Element = AAZAnyType() + + default_value = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value.Element = AAZAnyType() + + disc_number = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "number") + disc_number.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_number.default_value = AAZIntType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values.Element = AAZIntType() + + disc_object = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "object") + disc_object.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_object.default_value = AAZObjectType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values.Element = AAZDictType() + + _element = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element.Element = AAZAnyType() + + disc_string = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "string") + disc_string.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_string.default_value = AAZStrType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values.Element = AAZStrType() + + stage_map = cls._schema_on_200_201.properties.stage_map + stage_map.parameters = AAZDictType() + stage_map.resource_id = AAZStrType( + serialized_name="resourceId", + ) + + parameters = cls._schema_on_200_201.properties.stage_map.parameters + parameters.Element = AAZAnyType() + + stage_map_snapshot = cls._schema_on_200_201.properties.stage_map_snapshot + stage_map_snapshot.Element = AAZAnyType() + + system_data = cls._schema_on_200_201.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200_201 + + +class _CreateHelper: + """Helper class for Create""" + + +__all__ = ["Create"] diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_delete.py b/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_delete.py new file mode 100644 index 00000000000..be78b96198f --- /dev/null +++ b/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_delete.py @@ -0,0 +1,248 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "change-safety change-state delete", + confirmation="Are you sure you want to perform this operation?", +) +class Delete(AAZCommand): + """Delete a ChangeState + """ + + _aaz_info = { + "version": "2025-09-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_lro_poller(self._execute_operations, None) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.change_state_name = AAZStrArg( + options=["-n", "--name", "--change-state-name"], + help="The name of the ChangeState resource.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg() + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) + if condition_0: + yield self.ChangeStatesDeleteAtSubscriptionLevel(ctx=self.ctx)() + if condition_1: + yield self.ChangeStatesDelete(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + class ChangeStatesDeleteAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [202]: + return self.client.build_lro_polling( + False, + session, + self.on_200_201, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + if session.http_response.status_code in [204]: + return self.client.build_lro_polling( + False, + session, + self.on_204, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + if session.http_response.status_code in [200, 201]: + return self.client.build_lro_polling( + False, + session, + self.on_200_201, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + **self.url_parameters + ) + + @property + def method(self): + return "DELETE" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + def on_204(self, session): + pass + + def on_200_201(self, session): + pass + + class ChangeStatesDelete(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [202]: + return self.client.build_lro_polling( + False, + session, + self.on_200_201, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + if session.http_response.status_code in [204]: + return self.client.build_lro_polling( + False, + session, + self.on_204, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + if session.http_response.status_code in [200, 201]: + return self.client.build_lro_polling( + False, + session, + self.on_200_201, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + **self.url_parameters + ) + + @property + def method(self): + return "DELETE" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + def on_204(self, session): + pass + + def on_200_201(self, session): + pass + + +class _DeleteHelper: + """Helper class for Delete""" + + +__all__ = ["Delete"] diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_show.py b/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_show.py new file mode 100644 index 00000000000..f73fcf27901 --- /dev/null +++ b/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_show.py @@ -0,0 +1,610 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "change-safety change-state show", +) +class Show(AAZCommand): + """Get a ChangeState + """ + + _aaz_info = { + "version": "2025-09-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.change_state_name = AAZStrArg( + options=["-n", "--name", "--change-state-name"], + help="The name of the ChangeState resource.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg() + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) + if condition_0: + self.ChangeStatesGetAtSubscriptionLevel(ctx=self.ctx)() + if condition_1: + self.ChangeStatesGet(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class ChangeStatesGetAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType() + _schema_on_200.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.properties + properties.additional_data = AAZObjectType( + serialized_name="additionalData", + ) + properties.anticipated_end_time = AAZStrType( + serialized_name="anticipatedEndTime", + flags={"required": True}, + ) + properties.anticipated_start_time = AAZStrType( + serialized_name="anticipatedStartTime", + flags={"required": True}, + ) + properties.change_definition = AAZObjectType( + serialized_name="changeDefinition", + flags={"required": True}, + ) + properties.change_type = AAZStrType( + serialized_name="changeType", + flags={"required": True}, + ) + properties.comments = AAZStrType() + properties.description = AAZStrType() + properties.links = AAZListType() + properties.orchestration_tool = AAZStrType( + serialized_name="orchestrationTool", + ) + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.release_label = AAZStrType( + serialized_name="releaseLabel", + ) + properties.rollout_type = AAZStrType( + serialized_name="rolloutType", + flags={"required": True}, + ) + properties.stage_map = AAZObjectType( + serialized_name="stageMap", + ) + properties.stage_map_snapshot = AAZListType( + serialized_name="stageMapSnapshot", + flags={"read_only": True}, + ) + properties.status = AAZStrType( + flags={"read_only": True}, + ) + + change_definition = cls._schema_on_200.properties.change_definition + change_definition.details = AAZObjectType( + flags={"required": True}, + ) + change_definition.kind = AAZStrType( + flags={"required": True}, + ) + change_definition.name = AAZStrType( + flags={"required": True}, + ) + + links = cls._schema_on_200.properties.links + links.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.links.Element + _element.description = AAZStrType() + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.uri = AAZStrType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200.properties.parameters + parameters.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.parameters.Element + _element.metadata = AAZDictType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + metadata = cls._schema_on_200.properties.parameters.Element.metadata + metadata.Element = AAZStrType() + + disc_array = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "array") + disc_array.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_array.default_value = AAZListType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values.Element = AAZAnyType() + + default_value = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value.Element = AAZAnyType() + + disc_number = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "number") + disc_number.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_number.default_value = AAZIntType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values.Element = AAZIntType() + + disc_object = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "object") + disc_object.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_object.default_value = AAZObjectType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values.Element = AAZDictType() + + _element = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element.Element = AAZAnyType() + + disc_string = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "string") + disc_string.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_string.default_value = AAZStrType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values.Element = AAZStrType() + + stage_map = cls._schema_on_200.properties.stage_map + stage_map.parameters = AAZDictType() + stage_map.resource_id = AAZStrType( + serialized_name="resourceId", + ) + + parameters = cls._schema_on_200.properties.stage_map.parameters + parameters.Element = AAZAnyType() + + stage_map_snapshot = cls._schema_on_200.properties.stage_map_snapshot + stage_map_snapshot.Element = AAZAnyType() + + system_data = cls._schema_on_200.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + class ChangeStatesGet(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType() + _schema_on_200.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.properties + properties.additional_data = AAZObjectType( + serialized_name="additionalData", + ) + properties.anticipated_end_time = AAZStrType( + serialized_name="anticipatedEndTime", + flags={"required": True}, + ) + properties.anticipated_start_time = AAZStrType( + serialized_name="anticipatedStartTime", + flags={"required": True}, + ) + properties.change_definition = AAZObjectType( + serialized_name="changeDefinition", + flags={"required": True}, + ) + properties.change_type = AAZStrType( + serialized_name="changeType", + flags={"required": True}, + ) + properties.comments = AAZStrType() + properties.description = AAZStrType() + properties.links = AAZListType() + properties.orchestration_tool = AAZStrType( + serialized_name="orchestrationTool", + ) + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.release_label = AAZStrType( + serialized_name="releaseLabel", + ) + properties.rollout_type = AAZStrType( + serialized_name="rolloutType", + flags={"required": True}, + ) + properties.stage_map = AAZObjectType( + serialized_name="stageMap", + ) + properties.stage_map_snapshot = AAZListType( + serialized_name="stageMapSnapshot", + flags={"read_only": True}, + ) + properties.status = AAZStrType( + flags={"read_only": True}, + ) + + change_definition = cls._schema_on_200.properties.change_definition + change_definition.details = AAZObjectType( + flags={"required": True}, + ) + change_definition.kind = AAZStrType( + flags={"required": True}, + ) + change_definition.name = AAZStrType( + flags={"required": True}, + ) + + links = cls._schema_on_200.properties.links + links.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.links.Element + _element.description = AAZStrType() + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.uri = AAZStrType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200.properties.parameters + parameters.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.parameters.Element + _element.metadata = AAZDictType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + metadata = cls._schema_on_200.properties.parameters.Element.metadata + metadata.Element = AAZStrType() + + disc_array = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "array") + disc_array.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_array.default_value = AAZListType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values.Element = AAZAnyType() + + default_value = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value.Element = AAZAnyType() + + disc_number = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "number") + disc_number.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_number.default_value = AAZIntType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values.Element = AAZIntType() + + disc_object = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "object") + disc_object.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_object.default_value = AAZObjectType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values.Element = AAZDictType() + + _element = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element.Element = AAZAnyType() + + disc_string = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "string") + disc_string.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_string.default_value = AAZStrType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values.Element = AAZStrType() + + stage_map = cls._schema_on_200.properties.stage_map + stage_map.parameters = AAZDictType() + stage_map.resource_id = AAZStrType( + serialized_name="resourceId", + ) + + parameters = cls._schema_on_200.properties.stage_map.parameters + parameters.Element = AAZAnyType() + + stage_map_snapshot = cls._schema_on_200.properties.stage_map_snapshot + stage_map_snapshot.Element = AAZAnyType() + + system_data = cls._schema_on_200.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + +class _ShowHelper: + """Helper class for Show""" + + +__all__ = ["Show"] diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_update.py b/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_update.py new file mode 100644 index 00000000000..ee1213da2a6 --- /dev/null +++ b/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_update.py @@ -0,0 +1,1012 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "change-safety change-state update", +) +class Update(AAZCommand): + """Update a ChangeState + """ + + _aaz_info = { + "version": "2025-09-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], + ] + } + + AZ_SUPPORT_GENERIC_UPDATE = True + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.change_state_name = AAZStrArg( + options=["-n", "--name", "--change-state-name"], + help="The name of the ChangeState resource.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg() + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.additional_data = AAZObjectArg( + options=["--additional-data"], + arg_group="Properties", + help="Additional metadata for the change required for various orchestration tools.", + nullable=True, + blank={}, + ) + _args_schema.anticipated_end_time = AAZDateTimeArg( + options=["--anticipated-end-time"], + arg_group="Properties", + help="Expected completion time when the change should be finished, in ISO 8601 format.", + fmt=AAZDateTimeFormat( + protocol="iso", + ), + ) + _args_schema.anticipated_start_time = AAZDateTimeArg( + options=["--anticipated-start-time"], + arg_group="Properties", + help="Expected start time when the change execution should begin, in ISO 8601 format.", + fmt=AAZDateTimeFormat( + protocol="iso", + ), + ) + _args_schema.change_type = AAZStrArg( + options=["--change-type"], + arg_group="Properties", + help="Describes the nature of the change.", + enum={"AppDeployment": "AppDeployment", "Config": "Config", "ManualTouch": "ManualTouch", "PolicyDeployment": "PolicyDeployment"}, + ) + _args_schema.comments = AAZStrArg( + options=["--comments"], + arg_group="Properties", + help="Comments about the last update to the changeState resource.", + nullable=True, + fmt=AAZStrArgFormat( + max_length=2000, + ), + ) + _args_schema.description = AAZStrArg( + options=["--description"], + arg_group="Properties", + help="Brief description about the change.", + nullable=True, + fmt=AAZStrArgFormat( + max_length=2000, + ), + ) + _args_schema.links = AAZListArg( + options=["--links"], + arg_group="Properties", + help="Collection of related links for the change.", + nullable=True, + ) + _args_schema.orchestration_tool = AAZStrArg( + options=["--orchestration-tool"], + arg_group="Properties", + help="Tool used for deployment orchestration of this change.", + nullable=True, + ) + _args_schema.parameters = AAZDictArg( + options=["--parameters"], + arg_group="Properties", + help="Schema of parameters that will be provided for each stageProgression.", + nullable=True, + ) + _args_schema.release_label = AAZStrArg( + options=["--release-label"], + arg_group="Properties", + help="Label for the release associated with this change.", + nullable=True, + ) + _args_schema.rollout_type = AAZStrArg( + options=["--rollout-type"], + arg_group="Properties", + help="Describes the type of the rollout used for the change.", + enum={"Emergency": "Emergency", "Hotfix": "Hotfix", "Normal": "Normal"}, + ) + _args_schema.stage_map = AAZObjectArg( + options=["--stage-map"], + arg_group="Properties", + help="Reference to the StageMap, defining progression.", + nullable=True, + ) + + links = cls._args_schema.links + links.Element = AAZObjectArg( + nullable=True, + ) + + _element = cls._args_schema.links.Element + _element.description = AAZStrArg( + options=["description"], + help="Description or note about the link.", + nullable=True, + fmt=AAZStrArgFormat( + max_length=2000, + ), + ) + _element.name = AAZStrArg( + options=["name"], + help="name of the link.", + ) + _element.uri = AAZStrArg( + options=["uri"], + help="URL or comma separated URLs for the link.", + ) + + parameters = cls._args_schema.parameters + parameters.Element = AAZObjectArg( + nullable=True, + ) + + _element = cls._args_schema.parameters.Element + _element.array = AAZObjectArg( + options=["array"], + ) + _element.metadata = AAZDictArg( + options=["metadata"], + help="user-specified parameter metadata", + nullable=True, + ) + _element.number = AAZObjectArg( + options=["number"], + ) + _element.object = AAZObjectArg( + options=["object"], + ) + _element.string = AAZObjectArg( + options=["string"], + ) + + array = cls._args_schema.parameters.Element.array + array.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + nullable=True, + ) + array.default_value = AAZListArg( + options=["default-value"], + help="Default value for the parameter.", + nullable=True, + ) + + allowed_values = cls._args_schema.parameters.Element.array.allowed_values + allowed_values.Element = AAZAnyTypeArg( + nullable=True, + ) + + default_value = cls._args_schema.parameters.Element.array.default_value + default_value.Element = AAZAnyTypeArg( + nullable=True, + ) + + metadata = cls._args_schema.parameters.Element.metadata + metadata.Element = AAZStrArg( + nullable=True, + ) + + number = cls._args_schema.parameters.Element.number + number.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + nullable=True, + ) + number.default_value = AAZIntArg( + options=["default-value"], + help="Default value for the parameter.", + nullable=True, + ) + + allowed_values = cls._args_schema.parameters.Element.number.allowed_values + allowed_values.Element = AAZIntArg( + nullable=True, + ) + + object = cls._args_schema.parameters.Element.object + object.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + nullable=True, + ) + object.default_value = AAZObjectArg( + options=["default-value"], + help="Default value for the parameter.", + nullable=True, + blank={}, + ) + + allowed_values = cls._args_schema.parameters.Element.object.allowed_values + allowed_values.Element = AAZDictArg( + nullable=True, + ) + + _element = cls._args_schema.parameters.Element.object.allowed_values.Element + _element.Element = AAZAnyTypeArg( + nullable=True, + ) + + string = cls._args_schema.parameters.Element.string + string.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + nullable=True, + ) + string.default_value = AAZStrArg( + options=["default-value"], + help="Default value for the parameter.", + nullable=True, + ) + + allowed_values = cls._args_schema.parameters.Element.string.allowed_values + allowed_values.Element = AAZStrArg( + nullable=True, + ) + + stage_map = cls._args_schema.stage_map + stage_map.parameters = AAZDictArg( + options=["parameters"], + help="Key value pairs of parameter names & their values for the stageMap referenced by the resourceId field.", + nullable=True, + ) + stage_map.resource_id = AAZStrArg( + options=["resource-id"], + help="ARM resource ID for the nested stagemap resource.", + nullable=True, + ) + + parameters = cls._args_schema.stage_map.parameters + parameters.Element = AAZAnyTypeArg( + nullable=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) + condition_2 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_3 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) + if condition_0: + self.ChangeStatesGetAtSubscriptionLevel(ctx=self.ctx)() + if condition_1: + self.ChangeStatesGet(ctx=self.ctx)() + self.pre_instance_update(self.ctx.vars.instance) + self.InstanceUpdateByJson(ctx=self.ctx)() + self.InstanceUpdateByGeneric(ctx=self.ctx)() + self.post_instance_update(self.ctx.vars.instance) + if condition_2: + self.ChangeStatesCreateOrUpdateAtSubscriptionLevel(ctx=self.ctx)() + if condition_3: + self.ChangeStatesCreateOrUpdate(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + @register_callback + def pre_instance_update(self, instance): + pass + + @register_callback + def post_instance_update(self, instance): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class ChangeStatesGetAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + _UpdateHelper._build_schema_change_state_read(cls._schema_on_200) + + return cls._schema_on_200 + + class ChangeStatesGet(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + _UpdateHelper._build_schema_change_state_read(cls._schema_on_200) + + return cls._schema_on_200 + + class ChangeStatesCreateOrUpdateAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200, 201]: + return self.on_200_201(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + value=self.ctx.vars.instance, + ) + + return self.serialize_content(_content_value) + + def on_200_201(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200_201 + ) + + _schema_on_200_201 = None + + @classmethod + def _build_schema_on_200_201(cls): + if cls._schema_on_200_201 is not None: + return cls._schema_on_200_201 + + cls._schema_on_200_201 = AAZObjectType() + _UpdateHelper._build_schema_change_state_read(cls._schema_on_200_201) + + return cls._schema_on_200_201 + + class ChangeStatesCreateOrUpdate(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200, 201]: + return self.on_200_201(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + value=self.ctx.vars.instance, + ) + + return self.serialize_content(_content_value) + + def on_200_201(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200_201 + ) + + _schema_on_200_201 = None + + @classmethod + def _build_schema_on_200_201(cls): + if cls._schema_on_200_201 is not None: + return cls._schema_on_200_201 + + cls._schema_on_200_201 = AAZObjectType() + _UpdateHelper._build_schema_change_state_read(cls._schema_on_200_201) + + return cls._schema_on_200_201 + + class InstanceUpdateByJson(AAZJsonInstanceUpdateOperation): + + def __call__(self, *args, **kwargs): + self._update_instance(self.ctx.vars.instance) + + def _update_instance(self, instance): + _instance_value, _builder = self.new_content_builder( + self.ctx.args, + value=instance, + typ=AAZObjectType + ) + _builder.set_prop("properties", AAZObjectType) + + properties = _builder.get(".properties") + if properties is not None: + properties.set_prop("additionalData", AAZObjectType, ".additional_data") + properties.set_prop("anticipatedEndTime", AAZStrType, ".anticipated_end_time", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("anticipatedStartTime", AAZStrType, ".anticipated_start_time", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("changeType", AAZStrType, ".change_type", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("comments", AAZStrType, ".comments") + properties.set_prop("description", AAZStrType, ".description") + properties.set_prop("links", AAZListType, ".links") + properties.set_prop("orchestrationTool", AAZStrType, ".orchestration_tool") + properties.set_prop("parameters", AAZDictType, ".parameters") + properties.set_prop("releaseLabel", AAZStrType, ".release_label") + properties.set_prop("rolloutType", AAZStrType, ".rollout_type", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("stageMap", AAZObjectType, ".stage_map") + + links = _builder.get(".properties.links") + if links is not None: + links.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.links[]") + if _elements is not None: + _elements.set_prop("description", AAZStrType, ".description") + _elements.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}}) + _elements.set_prop("uri", AAZStrType, ".uri", typ_kwargs={"flags": {"required": True}}) + + parameters = _builder.get(".properties.parameters") + if parameters is not None: + parameters.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.parameters{}") + if _elements is not None: + _elements.set_prop("metadata", AAZDictType, ".metadata") + _elements.set_const("type", "array", AAZStrType, ".array", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "number", AAZStrType, ".number", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "object", AAZStrType, ".object", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "string", AAZStrType, ".string", typ_kwargs={"flags": {"required": True}}) + _elements.discriminate_by("type", "array") + _elements.discriminate_by("type", "number") + _elements.discriminate_by("type", "object") + _elements.discriminate_by("type", "string") + + metadata = _builder.get(".properties.parameters{}.metadata") + if metadata is not None: + metadata.set_elements(AAZStrType, ".") + + disc_array = _builder.get(".properties.parameters{}{type:array}") + if disc_array is not None: + disc_array.set_prop("allowedValues", AAZListType, ".array.allowed_values") + disc_array.set_prop("defaultValue", AAZListType, ".array.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:array}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZAnyType, ".") + + default_value = _builder.get(".properties.parameters{}{type:array}.defaultValue") + if default_value is not None: + default_value.set_elements(AAZAnyType, ".") + + disc_number = _builder.get(".properties.parameters{}{type:number}") + if disc_number is not None: + disc_number.set_prop("allowedValues", AAZListType, ".number.allowed_values") + disc_number.set_prop("defaultValue", AAZIntType, ".number.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:number}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZIntType, ".") + + disc_object = _builder.get(".properties.parameters{}{type:object}") + if disc_object is not None: + disc_object.set_prop("allowedValues", AAZListType, ".object.allowed_values") + disc_object.set_prop("defaultValue", AAZObjectType, ".object.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:object}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZDictType, ".") + + _elements = _builder.get(".properties.parameters{}{type:object}.allowedValues[]") + if _elements is not None: + _elements.set_elements(AAZAnyType, ".") + + disc_string = _builder.get(".properties.parameters{}{type:string}") + if disc_string is not None: + disc_string.set_prop("allowedValues", AAZListType, ".string.allowed_values") + disc_string.set_prop("defaultValue", AAZStrType, ".string.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:string}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZStrType, ".") + + stage_map = _builder.get(".properties.stageMap") + if stage_map is not None: + stage_map.set_prop("parameters", AAZDictType, ".parameters") + stage_map.set_prop("resourceId", AAZStrType, ".resource_id") + + parameters = _builder.get(".properties.stageMap.parameters") + if parameters is not None: + parameters.set_elements(AAZAnyType, ".") + + return _instance_value + + class InstanceUpdateByGeneric(AAZGenericInstanceUpdateOperation): + + def __call__(self, *args, **kwargs): + self._update_instance_by_generic( + self.ctx.vars.instance, + self.ctx.generic_update_args + ) + + +class _UpdateHelper: + """Helper class for Update""" + + _schema_change_state_read = None + + @classmethod + def _build_schema_change_state_read(cls, _schema): + if cls._schema_change_state_read is not None: + _schema.id = cls._schema_change_state_read.id + _schema.name = cls._schema_change_state_read.name + _schema.properties = cls._schema_change_state_read.properties + _schema.system_data = cls._schema_change_state_read.system_data + _schema.type = cls._schema_change_state_read.type + return + + cls._schema_change_state_read = _schema_change_state_read = AAZObjectType() + + change_state_read = _schema_change_state_read + change_state_read.id = AAZStrType( + flags={"read_only": True}, + ) + change_state_read.name = AAZStrType( + flags={"read_only": True}, + ) + change_state_read.properties = AAZObjectType() + change_state_read.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + change_state_read.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = _schema_change_state_read.properties + properties.additional_data = AAZObjectType( + serialized_name="additionalData", + ) + properties.anticipated_end_time = AAZStrType( + serialized_name="anticipatedEndTime", + flags={"required": True}, + ) + properties.anticipated_start_time = AAZStrType( + serialized_name="anticipatedStartTime", + flags={"required": True}, + ) + properties.change_definition = AAZObjectType( + serialized_name="changeDefinition", + flags={"required": True}, + ) + properties.change_type = AAZStrType( + serialized_name="changeType", + flags={"required": True}, + ) + properties.comments = AAZStrType() + properties.description = AAZStrType() + properties.links = AAZListType() + properties.orchestration_tool = AAZStrType( + serialized_name="orchestrationTool", + ) + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.release_label = AAZStrType( + serialized_name="releaseLabel", + ) + properties.rollout_type = AAZStrType( + serialized_name="rolloutType", + flags={"required": True}, + ) + properties.stage_map = AAZObjectType( + serialized_name="stageMap", + ) + properties.stage_map_snapshot = AAZListType( + serialized_name="stageMapSnapshot", + flags={"read_only": True}, + ) + properties.status = AAZStrType( + flags={"read_only": True}, + ) + + change_definition = _schema_change_state_read.properties.change_definition + change_definition.details = AAZObjectType( + flags={"required": True}, + ) + change_definition.kind = AAZStrType( + flags={"required": True}, + ) + change_definition.name = AAZStrType( + flags={"required": True}, + ) + + links = _schema_change_state_read.properties.links + links.Element = AAZObjectType() + + _element = _schema_change_state_read.properties.links.Element + _element.description = AAZStrType() + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.uri = AAZStrType( + flags={"required": True}, + ) + + parameters = _schema_change_state_read.properties.parameters + parameters.Element = AAZObjectType() + + _element = _schema_change_state_read.properties.parameters.Element + _element.metadata = AAZDictType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + metadata = _schema_change_state_read.properties.parameters.Element.metadata + metadata.Element = AAZStrType() + + disc_array = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "array") + disc_array.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_array.default_value = AAZListType( + serialized_name="defaultValue", + ) + + allowed_values = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values.Element = AAZAnyType() + + default_value = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value.Element = AAZAnyType() + + disc_number = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "number") + disc_number.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_number.default_value = AAZIntType( + serialized_name="defaultValue", + ) + + allowed_values = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values.Element = AAZIntType() + + disc_object = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "object") + disc_object.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_object.default_value = AAZObjectType( + serialized_name="defaultValue", + ) + + allowed_values = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values.Element = AAZDictType() + + _element = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element.Element = AAZAnyType() + + disc_string = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "string") + disc_string.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_string.default_value = AAZStrType( + serialized_name="defaultValue", + ) + + allowed_values = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values.Element = AAZStrType() + + stage_map = _schema_change_state_read.properties.stage_map + stage_map.parameters = AAZDictType() + stage_map.resource_id = AAZStrType( + serialized_name="resourceId", + ) + + parameters = _schema_change_state_read.properties.stage_map.parameters + parameters.Element = AAZAnyType() + + stage_map_snapshot = _schema_change_state_read.properties.stage_map_snapshot + stage_map_snapshot.Element = AAZAnyType() + + system_data = _schema_change_state_read.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + _schema.id = cls._schema_change_state_read.id + _schema.name = cls._schema_change_state_read.name + _schema.properties = cls._schema_change_state_read.properties + _schema.system_data = cls._schema_change_state_read.system_data + _schema.type = cls._schema_change_state_read.type + + +__all__ = ["Update"] diff --git a/src/change-state/azext_change_state/azext_metadata.json b/src/change-state/azext_change_state/azext_metadata.json new file mode 100644 index 00000000000..71889bb136b --- /dev/null +++ b/src/change-state/azext_change_state/azext_metadata.json @@ -0,0 +1,4 @@ +{ + "azext.isPreview": true, + "azext.minCliCoreVersion": "2.75.0" +} \ No newline at end of file diff --git a/src/change-state/azext_change_state/commands.py b/src/change-state/azext_change_state/commands.py new file mode 100644 index 00000000000..b0d842e4993 --- /dev/null +++ b/src/change-state/azext_change_state/commands.py @@ -0,0 +1,15 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: disable=too-many-lines +# pylint: disable=too-many-statements + +# from azure.cli.core.commands import CliCommandType + + +def load_command_table(self, _): # pylint: disable=unused-argument + pass diff --git a/src/change-state/azext_change_state/custom.py b/src/change-state/azext_change_state/custom.py new file mode 100644 index 00000000000..86df1e48ef5 --- /dev/null +++ b/src/change-state/azext_change_state/custom.py @@ -0,0 +1,14 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: disable=too-many-lines +# pylint: disable=too-many-statements + +from knack.log import get_logger + + +logger = get_logger(__name__) diff --git a/src/change-state/azext_change_state/tests/__init__.py b/src/change-state/azext_change_state/tests/__init__.py new file mode 100644 index 00000000000..5757aea3175 --- /dev/null +++ b/src/change-state/azext_change_state/tests/__init__.py @@ -0,0 +1,6 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- diff --git a/src/change-state/azext_change_state/tests/latest/__init__.py b/src/change-state/azext_change_state/tests/latest/__init__.py new file mode 100644 index 00000000000..5757aea3175 --- /dev/null +++ b/src/change-state/azext_change_state/tests/latest/__init__.py @@ -0,0 +1,6 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- diff --git a/src/change-state/azext_change_state/tests/latest/test_change_state.py b/src/change-state/azext_change_state/tests/latest/test_change_state.py new file mode 100644 index 00000000000..e8fb36765e6 --- /dev/null +++ b/src/change-state/azext_change_state/tests/latest/test_change_state.py @@ -0,0 +1,13 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +from azure.cli.testsdk import * + + +class ChangeStateScenario(ScenarioTest): + # TODO: add tests here + pass diff --git a/src/change-state/setup.cfg b/src/change-state/setup.cfg new file mode 100644 index 00000000000..2fdd96e5d39 --- /dev/null +++ b/src/change-state/setup.cfg @@ -0,0 +1 @@ +#setup.cfg \ No newline at end of file diff --git a/src/change-state/setup.py b/src/change-state/setup.py new file mode 100644 index 00000000000..686df8315a1 --- /dev/null +++ b/src/change-state/setup.py @@ -0,0 +1,49 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +from codecs import open +from setuptools import setup, find_packages + + +# HISTORY.rst entry. +VERSION = '1.0.0b1' + +# The full list of classifiers is available at +# https://pypi.python.org/pypi?%3Aaction=list_classifiers +CLASSIFIERS = [ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Intended Audience :: System Administrators', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'License :: OSI Approved :: MIT License', +] + +DEPENDENCIES = [] + +with open('README.md', 'r', encoding='utf-8') as f: + README = f.read() +with open('HISTORY.rst', 'r', encoding='utf-8') as f: + HISTORY = f.read() + +setup( + name='change-state', + version=VERSION, + description='Microsoft Azure Command-Line Tools ChangeState Extension.', + long_description=README + '\n\n' + HISTORY, + license='MIT', + author='Microsoft Corporation', + author_email='azpycli@microsoft.com', + url='https://github.com/Azure/azure-cli-extensions/tree/main/src/change-state', + classifiers=CLASSIFIERS, + packages=find_packages(exclude=["tests"]), + package_data={'azext_change_state': ['azext_metadata.json']}, + install_requires=DEPENDENCIES +) From 74a3d0f89533a8504f66b63e6c1afb2019e181d3 Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Wed, 29 Oct 2025 15:26:00 -0700 Subject: [PATCH 02/25] Fix parsing --- .../azext_change_state/commands.py | 8 +- src/change-state/azext_change_state/custom.py | 349 ++++++++++++++++++ 2 files changed, 355 insertions(+), 2 deletions(-) diff --git a/src/change-state/azext_change_state/commands.py b/src/change-state/azext_change_state/commands.py index b0d842e4993..bc58e6c79b1 100644 --- a/src/change-state/azext_change_state/commands.py +++ b/src/change-state/azext_change_state/commands.py @@ -8,8 +8,12 @@ # pylint: disable=too-many-lines # pylint: disable=too-many-statements -# from azure.cli.core.commands import CliCommandType +from azext_change_state import custom def load_command_table(self, _): # pylint: disable=unused-argument - pass + """Apply custom command overrides after the AAZ-generated command table is loaded.""" + if 'change-safety change-state create' in self.command_table: + self.command_table['change-safety change-state create'] = custom.ChangeStateCreate(loader=self) + if 'change-safety change-state update' in self.command_table: + self.command_table['change-safety change-state update'] = custom.ChangeStateUpdate(loader=self) diff --git a/src/change-state/azext_change_state/custom.py b/src/change-state/azext_change_state/custom.py index 86df1e48ef5..6002d586236 100644 --- a/src/change-state/azext_change_state/custom.py +++ b/src/change-state/azext_change_state/custom.py @@ -9,6 +9,355 @@ # pylint: disable=too-many-statements from knack.log import get_logger +from azure.cli.core.aaz import has_value, AAZAnyType, AAZListArg, AAZStrArg +from azure.cli.core.aaz._arg_action import AAZArgActionOperations, AAZPromptInputOperation, _ELEMENT_APPEND_KEY +from azure.cli.core.azclierror import InvalidArgumentValueError +from azext_change_state.aaz.latest.change_safety.change_state import Create as _ChangeStateCreate, Update as _ChangeStateUpdate logger = get_logger(__name__) + + +def _inject_change_definition_into_content(content, ctx): + """Attach the computed changeDefinition payload to the serialized request content.""" + change_definition_value = getattr(ctx.vars, "change_definition", None) + if change_definition_value is None: + return content + + change_definition = change_definition_value.to_serialized_data() + if not change_definition: + return content + + if content is None: + content = {} + properties = content.setdefault("properties", {}) + properties["changeDefinition"] = change_definition + return content + + +class ChangeStateCreate(_ChangeStateCreate): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._raw_targets = [] + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + schema = super()._build_arguments_schema(*args, **kwargs) + if not hasattr(schema, "targets"): + schema.targets = AAZListArg( + options=["--targets"], + help=( + "Target definitions expressed as key=value pairs separated by commas or semicolons. " + "Example: --targets \"resourceId=,operation=delete\"" + ), + ) + schema.targets.Element = AAZStrArg() + return schema + + def _handler(self, command_args): + # Extract targets before calling parent handler so we can accept flexible input formats. + command_args = dict(command_args) if command_args else {} + raw_targets = command_args.pop('targets', None) + if raw_targets is not None: + self._raw_targets = self._extract_targets_from_arg(raw_targets) + return super()._handler(command_args) + + def _extract_targets_from_arg(self, raw_targets): + """Extract target values from the raw argument.""" + if raw_targets is None: + return [] + + if isinstance(raw_targets, AAZArgActionOperations): + elements = [] + for keys, data in raw_targets._ops: + if isinstance(data, AAZPromptInputOperation): + data = data() + normalized_value = '' + if isinstance(data, (list, tuple)): + normalized_value = ','.join(str(v) for v in data if v is not None) + elif data is not None: + normalized_value = str(data) + + idx = None + key_name = None + for key in keys: + if key == _ELEMENT_APPEND_KEY: + idx = len(elements) + elif isinstance(key, int): + idx = key + elif isinstance(key, str): + key_name = key + + if idx is None: + idx = len(elements) - 1 if elements else 0 + while len(elements) <= idx: + elements.append('') + + if key_name: + combined = f"{key_name}={normalized_value}" if normalized_value else key_name + elements[idx] = f"{elements[idx]},{combined}" if elements[idx] else combined + else: + elements[idx] = normalized_value + + return [value for value in elements if value] + + if hasattr(raw_targets, 'to_serialized_data'): + values = raw_targets.to_serialized_data() + elif isinstance(raw_targets, list): + values = raw_targets + else: + values = [raw_targets] + + return [str(v) for v in values if v is not None] + + def pre_operations(self): + super().pre_operations() + + if not self._raw_targets: + raise InvalidArgumentValueError('--targets is required and must include key=value pairs.') + + # Build and set the changeDefinition with targets + change_definition = self._build_change_definition() + self.ctx.set_var('change_definition', change_definition, schema_builder=lambda: AAZAnyType()) + + def _build_change_definition(self): + """Build the changeDefinition object with targets""" + targets = self._parse_targets(self._raw_targets) + change_name = self.ctx.args.change_state_name.to_serialized_data() if has_value(self.ctx.args.change_state_name) else "Change Definition" + + return { + 'kind': 'Targets', + 'name': change_name, + 'details': { + 'targets': targets + } + } + + @staticmethod + def _parse_targets(raw_targets): + if raw_targets is None: + raise InvalidArgumentValueError('--targets is required and must include key=value pairs.') + if not raw_targets: + raise InvalidArgumentValueError('--targets is required and must include key=value pairs.') + parsed_targets = [] + for token in raw_targets: + if token is None: + continue + segments = [] + for part in str(token).split(';'): + segments.extend(segment.strip() for segment in part.split(',') if segment.strip()) + print("segments:", segments) + if not segments: + continue + target_entry = {} + for segment in segments: + if '=' not in segment: + raise InvalidArgumentValueError('Each --targets entry must be in key=value format.') + key, value = segment.split('=', 1) + key = key.strip() + value = value.strip() + if not key or not value: + raise InvalidArgumentValueError('Each --targets entry must include a non-empty key and value.') + target_entry[key] = value + if not target_entry: + continue + parsed_targets.append(target_entry) + if not parsed_targets: + raise InvalidArgumentValueError('--targets must include at least one key=value pair.') + return parsed_targets + + def pre_instance_create(self): + """Set the changeDefinition in the request body before creating the instance""" + change_definition = getattr(self.ctx.vars, 'change_definition', None) + if change_definition is not None: + # The changeDefinition will be set in the content property of the HTTP operations + pass + + class ChangeStatesCreateOrUpdateAtSubscriptionLevel(_ChangeStateCreate.ChangeStatesCreateOrUpdateAtSubscriptionLevel): + @property + def content(self): + content = super().content + return _inject_change_definition_into_content(content, self.ctx) + + class ChangeStatesCreateOrUpdate(_ChangeStateCreate.ChangeStatesCreateOrUpdate): + @property + def content(self): + content = super().content + return _inject_change_definition_into_content(content, self.ctx) + + +class ChangeStateUpdate(_ChangeStateUpdate): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._raw_targets = [] + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + schema = super()._build_arguments_schema(*args, **kwargs) + if not hasattr(schema, "targets"): + schema.targets = AAZListArg( + options=["--targets"], + help=( + "Optional target definitions expressed as key=value pairs separated by commas or semicolons. " + "Example: --targets \"resourceId=,operation=delete\"" + ), + ) + schema.targets.Element = AAZStrArg() + return schema + + def _handler(self, command_args): + # Extract targets before calling parent handler so we can accept flexible input formats. + command_args = dict(command_args) if command_args else {} + raw_targets = command_args.pop('targets', None) + if raw_targets is not None: + self._raw_targets = self._extract_targets_from_arg(raw_targets) + return super()._handler(command_args) + + def _extract_targets_from_arg(self, raw_targets): + """Extract target values from the raw argument.""" + if raw_targets is None: + return [] + + if isinstance(raw_targets, AAZArgActionOperations): + elements = [] + for keys, data in raw_targets._ops: + if isinstance(data, AAZPromptInputOperation): + data = data() + normalized_value = '' + if isinstance(data, (list, tuple)): + normalized_value = ','.join(str(v) for v in data if v is not None) + elif data is not None: + normalized_value = str(data) + + idx = None + key_name = None + for key in keys: + if key == _ELEMENT_APPEND_KEY: + idx = len(elements) + elif isinstance(key, int): + idx = key + elif isinstance(key, str): + key_name = key + + if idx is None: + idx = len(elements) - 1 if elements else 0 + while len(elements) <= idx: + elements.append('') + + if key_name: + combined = f"{key_name}={normalized_value}" if normalized_value else key_name + elements[idx] = f"{elements[idx]},{combined}" if elements[idx] else combined + else: + elements[idx] = normalized_value + + return [value for value in elements if value] + + if hasattr(raw_targets, 'to_serialized_data'): + values = raw_targets.to_serialized_data() + elif isinstance(raw_targets, list): + values = raw_targets + else: + values = [raw_targets] + + return [str(v) for v in values if v is not None] + + def pre_operations(self): + super().pre_operations() + + # Build and set the changeDefinition with targets if targets are provided + if self._raw_targets: + change_definition = self._build_change_definition() + self.ctx.set_var('change_definition', change_definition, schema_builder=lambda: AAZAnyType()) + + def _build_change_definition(self): + """Build the changeDefinition object with targets""" + targets = self._parse_targets(self._raw_targets) + change_name = self.ctx.args.change_state_name.to_serialized_data() if has_value(self.ctx.args.change_state_name) else "Change Definition" + + return { + 'kind': 'Targets', + 'name': change_name, + 'details': { + 'targets': targets + } + } + + def _parse_targets(self, raw_targets): + """Parse target strings into structured objects""" + if not raw_targets: + return None # For update, targets may be optional + + parsed_targets = [] + for token in raw_targets: + if not token: + continue + + # Split by semicolon or comma to handle multiple key-value pairs in one token + segments = [] + for part in str(token).replace(';', ',').split(','): + segment = part.strip() + if segment: + segments.append(segment) + + if not segments: + continue + + target_entry = {} + for segment in segments: + if '=' not in segment: + raise InvalidArgumentValueError(f"Each --targets entry must be in key=value format. Invalid: '{segment}'") + + key, value = segment.split('=', 1) + key = key.strip() + value = value.strip() + + if not key or not value: + raise InvalidArgumentValueError('Each --targets entry must include a non-empty key and value.') + + # Map keys to the correct property names + key_mapping = { + 'resourceid': 'resourceId', + 'subscriptionid': 'subscriptionId', + 'resourcegroupname': 'resourceGroupName', + 'resourcegroup': 'resourceGroupName', # Allow shorter alias + 'rg': 'resourceGroupName', # Allow shorter alias + 'resourcetype': 'resourceType', + 'resourcename': 'resourceName', + 'httpmethod': 'httpMethod', + 'method': 'httpMethod', # Allow shorter alias + 'operation': 'httpMethod' # Allow 'operation' as alias for httpMethod + } + + normalized_key = key.lower() + if normalized_key in key_mapping: + mapped_key = key_mapping[normalized_key] + # Normalize HTTP method values to uppercase + if mapped_key == 'httpMethod' and value: + value = value.upper() + target_entry[mapped_key] = value + else: + target_entry[key] = value + + if target_entry: + parsed_targets.append(target_entry) + + return parsed_targets if parsed_targets else None + + def pre_instance_update(self): + """Set the changeDefinition in the request body before updating the instance""" + change_definition = getattr(self.ctx.vars, 'change_definition', None) + if change_definition is not None: + # The changeDefinition will be set in the content property of the HTTP operations + pass + + class ChangeStatesCreateOrUpdateAtSubscriptionLevel(_ChangeStateUpdate.ChangeStatesCreateOrUpdateAtSubscriptionLevel): + @property + def content(self): + content = super().content + return _inject_change_definition_into_content(content, self.ctx) + + class ChangeStatesCreateOrUpdate(_ChangeStateUpdate.ChangeStatesCreateOrUpdate): + @property + def content(self): + content = super().content + return _inject_change_definition_into_content(content, self.ctx) From b80cda8f6cfd82a17a7cd7cf3fe0cac0888dd12e Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Wed, 29 Oct 2025 16:10:33 -0700 Subject: [PATCH 03/25] Fix parsing --- src/change-state/azext_change_state/custom.py | 215 ++++++++++-------- 1 file changed, 115 insertions(+), 100 deletions(-) diff --git a/src/change-state/azext_change_state/custom.py b/src/change-state/azext_change_state/custom.py index 6002d586236..2cb450c4a2f 100644 --- a/src/change-state/azext_change_state/custom.py +++ b/src/change-state/azext_change_state/custom.py @@ -35,10 +35,88 @@ def _inject_change_definition_into_content(content, ctx): return content +def _normalize_targets_arg(raw_targets): + """Return a list of raw target strings from the parsed CLI argument.""" + if raw_targets is None: + return [] + + if isinstance(raw_targets, AAZArgActionOperations): + elements = [] + for keys, data in raw_targets._ops: + logger.debug("Processing target op keys=%s data=%s", keys, data) + if isinstance(data, AAZPromptInputOperation): + data = data() + + normalized_value = '' + if isinstance(data, (list, tuple)): + normalized_value = ','.join(str(v) for v in data if v is not None) + elif data is not None: + normalized_value = str(data) + + idx = None + key_name = None + for key in keys: + if key == _ELEMENT_APPEND_KEY: + idx = len(elements) + elif isinstance(key, int): + idx = key + elif isinstance(key, str): + key_name = key + + if idx is None: + idx = len(elements) - 1 if elements else 0 + while len(elements) <= idx: + elements.append('') + + if key_name: + combined = f"{key_name}={normalized_value}" if normalized_value else key_name + elements[idx] = f"{elements[idx]},{combined}" if elements[idx] else combined + else: + elements[idx] = normalized_value + + return [value for value in elements if value] + + if hasattr(raw_targets, 'to_serialized_data'): + values = raw_targets.to_serialized_data() + elif isinstance(raw_targets, list): + values = raw_targets + else: + values = [raw_targets] + + return [str(v) for v in values if v is not None] + + +def _inject_targets_into_result(data, targets): + """Ensure changeDefinition.details.targets is present in the command output.""" + if not targets or data is None: + return + + def process(item): + if not isinstance(item, dict): + return + containers = [] + if isinstance(item.get('properties'), dict): + containers.append(item['properties']) + containers.append(item) + for container in containers: + change_def = container.get('changeDefinition') + if isinstance(change_def, dict): + details = change_def.setdefault('details', {}) + if isinstance(details, dict) and not details.get('targets'): + details['targets'] = targets + + if isinstance(data, list): + for entry in data: + process(entry) + else: + process(data) + + class ChangeStateCreate(_ChangeStateCreate): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._raw_targets = [] + self._parsed_targets = None @classmethod def _build_arguments_schema(cls, *args, **kwargs): @@ -59,57 +137,9 @@ def _handler(self, command_args): command_args = dict(command_args) if command_args else {} raw_targets = command_args.pop('targets', None) if raw_targets is not None: - self._raw_targets = self._extract_targets_from_arg(raw_targets) + self._raw_targets = _normalize_targets_arg(raw_targets) return super()._handler(command_args) - def _extract_targets_from_arg(self, raw_targets): - """Extract target values from the raw argument.""" - if raw_targets is None: - return [] - - if isinstance(raw_targets, AAZArgActionOperations): - elements = [] - for keys, data in raw_targets._ops: - if isinstance(data, AAZPromptInputOperation): - data = data() - normalized_value = '' - if isinstance(data, (list, tuple)): - normalized_value = ','.join(str(v) for v in data if v is not None) - elif data is not None: - normalized_value = str(data) - - idx = None - key_name = None - for key in keys: - if key == _ELEMENT_APPEND_KEY: - idx = len(elements) - elif isinstance(key, int): - idx = key - elif isinstance(key, str): - key_name = key - - if idx is None: - idx = len(elements) - 1 if elements else 0 - while len(elements) <= idx: - elements.append('') - - if key_name: - combined = f"{key_name}={normalized_value}" if normalized_value else key_name - elements[idx] = f"{elements[idx]},{combined}" if elements[idx] else combined - else: - elements[idx] = normalized_value - - return [value for value in elements if value] - - if hasattr(raw_targets, 'to_serialized_data'): - values = raw_targets.to_serialized_data() - elif isinstance(raw_targets, list): - values = raw_targets - else: - values = [raw_targets] - - return [str(v) for v in values if v is not None] - def pre_operations(self): super().pre_operations() @@ -118,11 +148,13 @@ def pre_operations(self): # Build and set the changeDefinition with targets change_definition = self._build_change_definition() + logger.debug("Final changeDefinition for create: %s", change_definition) self.ctx.set_var('change_definition', change_definition, schema_builder=lambda: AAZAnyType()) def _build_change_definition(self): """Build the changeDefinition object with targets""" targets = self._parse_targets(self._raw_targets) + self._parsed_targets = targets change_name = self.ctx.args.change_state_name.to_serialized_data() if has_value(self.ctx.args.change_state_name) else "Change Definition" return { @@ -146,7 +178,6 @@ def _parse_targets(raw_targets): segments = [] for part in str(token).split(';'): segments.extend(segment.strip() for segment in part.split(',') if segment.strip()) - print("segments:", segments) if not segments: continue target_entry = {} @@ -158,7 +189,26 @@ def _parse_targets(raw_targets): value = value.strip() if not key or not value: raise InvalidArgumentValueError('Each --targets entry must include a non-empty key and value.') - target_entry[key] = value + key_mapping = { + 'resourceid': 'resourceId', + 'subscriptionid': 'subscriptionId', + 'resourcegroupname': 'resourceGroupName', + 'resourcegroup': 'resourceGroupName', + 'rg': 'resourceGroupName', + 'resourcetype': 'resourceType', + 'resourcename': 'resourceName', + 'httpmethod': 'httpMethod', + 'method': 'httpMethod', + 'operation': 'httpMethod' + } + normalized_key = key.lower() + if normalized_key in key_mapping: + mapped_key = key_mapping[normalized_key] + if mapped_key == 'httpMethod' and value: + value = value.upper() + target_entry[mapped_key] = value + else: + target_entry[key] = value if not target_entry: continue parsed_targets.append(target_entry) @@ -166,6 +216,11 @@ def _parse_targets(raw_targets): raise InvalidArgumentValueError('--targets must include at least one key=value pair.') return parsed_targets + def _output(self, *args, **kwargs): + result = super()._output(*args, **kwargs) + _inject_targets_into_result(result, self._parsed_targets) + return result + def pre_instance_create(self): """Set the changeDefinition in the request body before creating the instance""" change_definition = getattr(self.ctx.vars, 'change_definition', None) @@ -190,6 +245,7 @@ class ChangeStateUpdate(_ChangeStateUpdate): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._raw_targets = [] + self._parsed_targets = None @classmethod def _build_arguments_schema(cls, *args, **kwargs): @@ -210,68 +266,22 @@ def _handler(self, command_args): command_args = dict(command_args) if command_args else {} raw_targets = command_args.pop('targets', None) if raw_targets is not None: - self._raw_targets = self._extract_targets_from_arg(raw_targets) + self._raw_targets = _normalize_targets_arg(raw_targets) return super()._handler(command_args) - def _extract_targets_from_arg(self, raw_targets): - """Extract target values from the raw argument.""" - if raw_targets is None: - return [] - - if isinstance(raw_targets, AAZArgActionOperations): - elements = [] - for keys, data in raw_targets._ops: - if isinstance(data, AAZPromptInputOperation): - data = data() - normalized_value = '' - if isinstance(data, (list, tuple)): - normalized_value = ','.join(str(v) for v in data if v is not None) - elif data is not None: - normalized_value = str(data) - - idx = None - key_name = None - for key in keys: - if key == _ELEMENT_APPEND_KEY: - idx = len(elements) - elif isinstance(key, int): - idx = key - elif isinstance(key, str): - key_name = key - - if idx is None: - idx = len(elements) - 1 if elements else 0 - while len(elements) <= idx: - elements.append('') - - if key_name: - combined = f"{key_name}={normalized_value}" if normalized_value else key_name - elements[idx] = f"{elements[idx]},{combined}" if elements[idx] else combined - else: - elements[idx] = normalized_value - - return [value for value in elements if value] - - if hasattr(raw_targets, 'to_serialized_data'): - values = raw_targets.to_serialized_data() - elif isinstance(raw_targets, list): - values = raw_targets - else: - values = [raw_targets] - - return [str(v) for v in values if v is not None] - def pre_operations(self): super().pre_operations() # Build and set the changeDefinition with targets if targets are provided if self._raw_targets: change_definition = self._build_change_definition() + logger.debug("Final changeDefinition for update: %s", change_definition) self.ctx.set_var('change_definition', change_definition, schema_builder=lambda: AAZAnyType()) def _build_change_definition(self): """Build the changeDefinition object with targets""" targets = self._parse_targets(self._raw_targets) + self._parsed_targets = targets change_name = self.ctx.args.change_state_name.to_serialized_data() if has_value(self.ctx.args.change_state_name) else "Change Definition" return { @@ -343,6 +353,11 @@ def _parse_targets(self, raw_targets): return parsed_targets if parsed_targets else None + def _output(self, *args, **kwargs): + result = super()._output(*args, **kwargs) + _inject_targets_into_result(result, self._parsed_targets) + return result + def pre_instance_update(self): """Set the changeDefinition in the request body before updating the instance""" change_definition = getattr(self.ctx.vars, 'change_definition', None) From add68de3f12cb98dd9666895a7d959d29e6f1fc3 Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Wed, 29 Oct 2025 16:57:15 -0700 Subject: [PATCH 04/25] Fix displaying --- .../azext_change_state/commands.py | 14 +++-- src/change-state/azext_change_state/custom.py | 51 ++++++++++++++++++- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/change-state/azext_change_state/commands.py b/src/change-state/azext_change_state/commands.py index bc58e6c79b1..afd3355e44f 100644 --- a/src/change-state/azext_change_state/commands.py +++ b/src/change-state/azext_change_state/commands.py @@ -13,7 +13,13 @@ def load_command_table(self, _): # pylint: disable=unused-argument """Apply custom command overrides after the AAZ-generated command table is loaded.""" - if 'change-safety change-state create' in self.command_table: - self.command_table['change-safety change-state create'] = custom.ChangeStateCreate(loader=self) - if 'change-safety change-state update' in self.command_table: - self.command_table['change-safety change-state update'] = custom.ChangeStateUpdate(loader=self) + # Register custom commands for both 'change-state' and 'change-safety change-state' + for prefix in ["change-state", "change-safety change-state"]: + if f"{prefix} create" in self.command_table: + self.command_table[f"{prefix} create"] = custom.ChangeStateCreate(loader=self) + if f"{prefix} update" in self.command_table: + self.command_table[f"{prefix} update"] = custom.ChangeStateUpdate(loader=self) + if f"{prefix} show" in self.command_table: + self.command_table[f"{prefix} show"] = custom.ChangeStateShow(loader=self) + if f"{prefix} delete" in self.command_table: + self.command_table[f"{prefix} delete"] = custom.ChangeStateDelete(loader=self) diff --git a/src/change-state/azext_change_state/custom.py b/src/change-state/azext_change_state/custom.py index 2cb450c4a2f..9a0a773ebc8 100644 --- a/src/change-state/azext_change_state/custom.py +++ b/src/change-state/azext_change_state/custom.py @@ -12,7 +12,8 @@ from azure.cli.core.aaz import has_value, AAZAnyType, AAZListArg, AAZStrArg from azure.cli.core.aaz._arg_action import AAZArgActionOperations, AAZPromptInputOperation, _ELEMENT_APPEND_KEY from azure.cli.core.azclierror import InvalidArgumentValueError -from azext_change_state.aaz.latest.change_safety.change_state import Create as _ChangeStateCreate, Update as _ChangeStateUpdate +from azext_change_state.aaz.latest.change_safety.change_state import Create as _ChangeStateCreate, Update as _ChangeStateUpdate, Show as _ChangeStateShow, Delete as _ChangeStateDelete +from azure.cli.core.aaz import AAZObjectType, AAZStrType, AAZListType logger = get_logger(__name__) @@ -111,6 +112,27 @@ def process(item): else: process(data) +def _custom_show_schema_builder(): + # Import the generated Show class + from azext_change_state.aaz.latest.change_safety.change_state._show import Show as GeneratedShow + + # Get the base schema from the generated code + base_schema = GeneratedShow.ChangeStatesGet._build_schema_on_200() + + # Inject/override the targets schema + change_definition = base_schema.properties.change_definition + details = change_definition.details + details.targets = AAZListType(flags={"read_only": True}) + details.targets.Element = AAZObjectType() + details.targets.Element.resourceId = AAZStrType() + details.targets.Element.subscriptionId = AAZStrType() + details.targets.Element.resourceGroupName = AAZStrType() + details.targets.Element.resourceType = AAZStrType() + details.targets.Element.resourceName = AAZStrType() + details.targets.Element.httpMethod = AAZStrType() + + return base_schema + class ChangeStateCreate(_ChangeStateCreate): def __init__(self, *args, **kwargs): @@ -376,3 +398,30 @@ class ChangeStatesCreateOrUpdate(_ChangeStateUpdate.ChangeStatesCreateOrUpdate): def content(self): content = super().content return _inject_change_definition_into_content(content, self.ctx) + +class ChangeStateShow(_ChangeStateShow): + def _output(self, *args, **kwargs): + result = super()._output(*args, **kwargs) + # Optionally inject targets schema into result if needed + return result + + class ChangeStatesGetAtSubscriptionLevel(_ChangeStateShow.ChangeStatesGetAtSubscriptionLevel): + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=_custom_show_schema_builder + ) + + class ChangeStatesGet(_ChangeStateShow.ChangeStatesGet): + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=_custom_show_schema_builder + ) + +class ChangeStateDelete(_ChangeStateDelete): + pass From aeb58e6c679b492be1269dc94455ebc35c36b899 Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Wed, 29 Oct 2025 17:15:11 -0700 Subject: [PATCH 05/25] Fix commands --- .../azext_change_state/commands.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/change-state/azext_change_state/commands.py b/src/change-state/azext_change_state/commands.py index afd3355e44f..d05000e0a8a 100644 --- a/src/change-state/azext_change_state/commands.py +++ b/src/change-state/azext_change_state/commands.py @@ -12,14 +12,14 @@ def load_command_table(self, _): # pylint: disable=unused-argument - """Apply custom command overrides after the AAZ-generated command table is loaded.""" - # Register custom commands for both 'change-state' and 'change-safety change-state' - for prefix in ["change-state", "change-safety change-state"]: - if f"{prefix} create" in self.command_table: - self.command_table[f"{prefix} create"] = custom.ChangeStateCreate(loader=self) - if f"{prefix} update" in self.command_table: - self.command_table[f"{prefix} update"] = custom.ChangeStateUpdate(loader=self) - if f"{prefix} show" in self.command_table: - self.command_table[f"{prefix} show"] = custom.ChangeStateShow(loader=self) - if f"{prefix} delete" in self.command_table: - self.command_table[f"{prefix} delete"] = custom.ChangeStateDelete(loader=self) + from .custom import ChangeStateCreate, ChangeStateUpdate, ChangeStateDelete, ChangeStateShow + + create_command = ChangeStateCreate(loader=self) + update_command = ChangeStateUpdate(loader=self) + delete_command = ChangeStateDelete(loader=self) + show_command = ChangeStateShow(loader=self) + + self.command_table['change-state create'] = create_command + self.command_table['change-state update'] = update_command + self.command_table['change-state delete'] = delete_command + self.command_table['change-state show'] = show_command From 1636d5dc97ce178009fff62e91f215a895ce9d2f Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Wed, 29 Oct 2025 17:22:47 -0700 Subject: [PATCH 06/25] Add tests --- .../tests/latest/test_change_state.py | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/src/change-state/azext_change_state/tests/latest/test_change_state.py b/src/change-state/azext_change_state/tests/latest/test_change_state.py index e8fb36765e6..018dbf903c0 100644 --- a/src/change-state/azext_change_state/tests/latest/test_change_state.py +++ b/src/change-state/azext_change_state/tests/latest/test_change_state.py @@ -5,9 +5,160 @@ # Code generated by aaz-dev-tools # -------------------------------------------------------------------------------------------- +from types import SimpleNamespace + +import pytest from azure.cli.testsdk import * +from azext_change_state.custom import ( + ChangeStateCreate, + ChangeStateUpdate, + _inject_change_definition_into_content, + _inject_targets_into_result, +) +from azure.cli.core.azclierror import InvalidArgumentValueError + class ChangeStateScenario(ScenarioTest): # TODO: add tests here pass + + +class _SerializableValue: + """Test helper that mimics serialized AAZ values.""" + + def __init__(self, data): + self._data = data + + def to_serialized_data(self): + return self._data + + +def test_parse_targets_single_entry(): + result = ChangeStateCreate._parse_targets(["env=prod"]) + assert result == [{"env": "prod"}] + + +def test_parse_targets_with_multiple_delimiters(): + tokens = ["env=prod,region=us; role=web "] + result = ChangeStateCreate._parse_targets(tokens) + assert result == [{"env": "prod", "region": "us", "role": "web"}] + + +def test_parse_targets_rejects_invalid_entries(): + with pytest.raises(InvalidArgumentValueError): + ChangeStateCreate._parse_targets(["invalid"]) + + +def test_parse_targets_maps_resource_group_alias(): + result = ChangeStateCreate._parse_targets(["rg=my-group"]) + assert result == [{"resourceGroupName": "my-group"}] + + +def test_parse_targets_uppercases_http_method_value(): + result = ChangeStateCreate._parse_targets(["httpMethod=delete"]) + assert result == [{"httpMethod": "DELETE"}] + + +def test_parse_targets_maps_operation_to_http_method(): + result = ChangeStateCreate._parse_targets(["operation=POST,resource=/abc"]) + assert result == [{"httpMethod": "POST", "resource": "/abc"}] + + +def test_build_change_definition_uses_targets_and_name(): + cmd = object.__new__(ChangeStateCreate) + cmd._raw_targets = ["env=prod"] + cmd.ctx = SimpleNamespace(args=SimpleNamespace(change_state_name=_SerializableValue("test-change"))) + definition = ChangeStateCreate._build_change_definition(cmd) + assert definition == { + "kind": "Targets", + "name": "test-change", + "details": {"targets": [{"env": "prod"}]} + } + + +def test_build_change_definition_normalizes_operation(): + cmd = object.__new__(ChangeStateCreate) + cmd._raw_targets = ["operation=post"] + cmd.ctx = SimpleNamespace(args=SimpleNamespace(change_state_name=_SerializableValue("test-change"))) + definition = ChangeStateCreate._build_change_definition(cmd) + assert definition["details"]["targets"] == [{"httpMethod": "POST"}] + + +def test_build_change_definition_handles_serializable_value(): + class DummyName: + def to_serialized_data(self): + return "serialized-name" + + cmd = object.__new__(ChangeStateCreate) + cmd._raw_targets = ["env=prod"] + cmd.ctx = SimpleNamespace(args=SimpleNamespace(change_state_name=DummyName())) + definition = ChangeStateCreate._build_change_definition(cmd) + assert definition["name"] == "serialized-name" + + +def test_command_name_overrides(): + assert ChangeStateCreate.AZ_NAME == "change-safety change-state create" + assert ChangeStateUpdate.AZ_NAME == "change-safety change-state update" + + +def test_inject_change_definition_into_dict_payload(): + ctx = SimpleNamespace(vars=SimpleNamespace(change_definition=_SerializableValue(_valid_change_definition()))) + original = {"properties": {"existing": "value"}} + updated = _inject_change_definition_into_content(original, ctx) + assert updated["properties"]["changeDefinition"] == _valid_change_definition() + assert updated["properties"]["existing"] == "value" + + +def test_inject_change_definition_into_content_when_none(): + ctx = SimpleNamespace(vars=SimpleNamespace(change_definition=_SerializableValue(_valid_change_definition()))) + payload = _inject_change_definition_into_content(None, ctx) + assert payload["properties"]["changeDefinition"] == _valid_change_definition() + + +def test_inject_change_definition_skips_empty_definition(): + ctx = SimpleNamespace(vars=SimpleNamespace(change_definition=_SerializableValue({}))) + original = {"properties": {}} + updated = _inject_change_definition_into_content(original, ctx) + assert updated is original + + +def test_inject_targets_into_result_populates_properties_container(): + targets = [{"env": "prod"}] + data = {"properties": {"changeDefinition": {"details": {}}}} + _inject_targets_into_result(data, targets) + assert data["properties"]["changeDefinition"]["details"]["targets"] == targets + + +def test_inject_targets_into_result_handles_list_payloads(): + targets = [{"httpMethod": "POST"}] + data = [{"changeDefinition": {"details": {}}}] + _inject_targets_into_result(data, targets) + assert data[0]["changeDefinition"]["details"]["targets"] == targets + + +def test_create_operation_content_includes_change_definition(): + args_schema = ChangeStateCreate._build_arguments_schema() + args = args_schema() + args.change_state_name = "test-change" + args.change_type = "AppDeployment" + args.rollout_type = "Normal" + args.anticipated_start_time = "2024-11-01T08:00:00Z" + args.anticipated_end_time = "2024-11-01T10:00:00Z" + ctx = SimpleNamespace( + args=args, + vars=SimpleNamespace(change_definition=_SerializableValue(_valid_change_definition())) + ) + + op = object.__new__(ChangeStateCreate.ChangeStatesCreateOrUpdate) + op.ctx = ctx + payload = op.content + assert payload["properties"]["changeDefinition"] == _valid_change_definition() + + +def _valid_change_definition(): + return { + "kind": "Targets", + "name": "test-change", + "details": {"targets": [{"resourceId": "/foo", "operation": "DELETE"}]}, + } From b26a3b2e1fdb6e19736ae11c675b774a6ea2f44c Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Wed, 29 Oct 2025 17:32:31 -0700 Subject: [PATCH 07/25] Fix style --- .../azext_change_state/commands.py | 3 - src/change-state/azext_change_state/custom.py | 80 +++++++++++++++---- 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/src/change-state/azext_change_state/commands.py b/src/change-state/azext_change_state/commands.py index d05000e0a8a..fb19244e143 100644 --- a/src/change-state/azext_change_state/commands.py +++ b/src/change-state/azext_change_state/commands.py @@ -8,9 +8,6 @@ # pylint: disable=too-many-lines # pylint: disable=too-many-statements -from azext_change_state import custom - - def load_command_table(self, _): # pylint: disable=unused-argument from .custom import ChangeStateCreate, ChangeStateUpdate, ChangeStateDelete, ChangeStateShow diff --git a/src/change-state/azext_change_state/custom.py b/src/change-state/azext_change_state/custom.py index 9a0a773ebc8..47c4300dab6 100644 --- a/src/change-state/azext_change_state/custom.py +++ b/src/change-state/azext_change_state/custom.py @@ -7,18 +7,40 @@ # pylint: disable=too-many-lines # pylint: disable=too-many-statements +# pylint: disable=protected-access from knack.log import get_logger -from azure.cli.core.aaz import has_value, AAZAnyType, AAZListArg, AAZStrArg -from azure.cli.core.aaz._arg_action import AAZArgActionOperations, AAZPromptInputOperation, _ELEMENT_APPEND_KEY +from azure.cli.core.aaz import ( + has_value, + AAZAnyType, + AAZListArg, + AAZStrArg, + AAZObjectType, + AAZStrType, + AAZListType, +) +from azure.cli.core.aaz._arg_action import ( + AAZArgActionOperations, + AAZPromptInputOperation, + _ELEMENT_APPEND_KEY, +) from azure.cli.core.azclierror import InvalidArgumentValueError -from azext_change_state.aaz.latest.change_safety.change_state import Create as _ChangeStateCreate, Update as _ChangeStateUpdate, Show as _ChangeStateShow, Delete as _ChangeStateDelete -from azure.cli.core.aaz import AAZObjectType, AAZStrType, AAZListType +from azext_change_state.aaz.latest.change_safety.change_state import ( + Create as _ChangeStateCreate, + Update as _ChangeStateUpdate, + Show as _ChangeStateShow, + Delete as _ChangeStateDelete, +) logger = get_logger(__name__) +def _build_any_type(): + """Utility to satisfy schema_builder callsites while keeping lint happy.""" + return AAZAnyType() + + def _inject_change_definition_into_content(content, ctx): """Attach the computed changeDefinition payload to the serialized request content.""" change_definition_value = getattr(ctx.vars, "change_definition", None) @@ -112,6 +134,7 @@ def process(item): else: process(data) + def _custom_show_schema_builder(): # Import the generated Show class from azext_change_state.aaz.latest.change_safety.change_state._show import Show as GeneratedShow @@ -171,13 +194,22 @@ def pre_operations(self): # Build and set the changeDefinition with targets change_definition = self._build_change_definition() logger.debug("Final changeDefinition for create: %s", change_definition) - self.ctx.set_var('change_definition', change_definition, schema_builder=lambda: AAZAnyType()) + self.ctx.set_var( + 'change_definition', + change_definition, + schema_builder=_build_any_type, + ) def _build_change_definition(self): """Build the changeDefinition object with targets""" targets = self._parse_targets(self._raw_targets) self._parsed_targets = targets - change_name = self.ctx.args.change_state_name.to_serialized_data() if has_value(self.ctx.args.change_state_name) else "Change Definition" + change_arg = self.ctx.args.change_state_name + change_name = ( + change_arg.to_serialized_data() + if has_value(change_arg) + else "Change Definition" + ) return { 'kind': 'Targets', @@ -250,13 +282,15 @@ def pre_instance_create(self): # The changeDefinition will be set in the content property of the HTTP operations pass - class ChangeStatesCreateOrUpdateAtSubscriptionLevel(_ChangeStateCreate.ChangeStatesCreateOrUpdateAtSubscriptionLevel): + class ChangeStatesCreateOrUpdateAtSubscriptionLevel( + _ChangeStateCreate.ChangeStatesCreateOrUpdateAtSubscriptionLevel): @property def content(self): content = super().content return _inject_change_definition_into_content(content, self.ctx) - class ChangeStatesCreateOrUpdate(_ChangeStateCreate.ChangeStatesCreateOrUpdate): + class ChangeStatesCreateOrUpdate( + _ChangeStateCreate.ChangeStatesCreateOrUpdate): @property def content(self): content = super().content @@ -298,13 +332,22 @@ def pre_operations(self): if self._raw_targets: change_definition = self._build_change_definition() logger.debug("Final changeDefinition for update: %s", change_definition) - self.ctx.set_var('change_definition', change_definition, schema_builder=lambda: AAZAnyType()) + self.ctx.set_var( + 'change_definition', + change_definition, + schema_builder=_build_any_type, + ) def _build_change_definition(self): """Build the changeDefinition object with targets""" targets = self._parse_targets(self._raw_targets) self._parsed_targets = targets - change_name = self.ctx.args.change_state_name.to_serialized_data() if has_value(self.ctx.args.change_state_name) else "Change Definition" + change_arg = self.ctx.args.change_state_name + change_name = ( + change_arg.to_serialized_data() + if has_value(change_arg) + else "Change Definition" + ) return { 'kind': 'Targets', @@ -337,7 +380,11 @@ def _parse_targets(self, raw_targets): target_entry = {} for segment in segments: if '=' not in segment: - raise InvalidArgumentValueError(f"Each --targets entry must be in key=value format. Invalid: '{segment}'") + error_message = ( + "Each --targets entry must be in key=value format. " + f"Invalid: '{segment}'" + ) + raise InvalidArgumentValueError(error_message) key, value = segment.split('=', 1) key = key.strip() @@ -380,25 +427,29 @@ def _output(self, *args, **kwargs): _inject_targets_into_result(result, self._parsed_targets) return result - def pre_instance_update(self): + def pre_instance_update(self, instance): """Set the changeDefinition in the request body before updating the instance""" + del instance change_definition = getattr(self.ctx.vars, 'change_definition', None) if change_definition is not None: # The changeDefinition will be set in the content property of the HTTP operations pass - class ChangeStatesCreateOrUpdateAtSubscriptionLevel(_ChangeStateUpdate.ChangeStatesCreateOrUpdateAtSubscriptionLevel): + class ChangeStatesCreateOrUpdateAtSubscriptionLevel( + _ChangeStateUpdate.ChangeStatesCreateOrUpdateAtSubscriptionLevel): @property def content(self): content = super().content return _inject_change_definition_into_content(content, self.ctx) - class ChangeStatesCreateOrUpdate(_ChangeStateUpdate.ChangeStatesCreateOrUpdate): + class ChangeStatesCreateOrUpdate( + _ChangeStateUpdate.ChangeStatesCreateOrUpdate): @property def content(self): content = super().content return _inject_change_definition_into_content(content, self.ctx) + class ChangeStateShow(_ChangeStateShow): def _output(self, *args, **kwargs): result = super()._output(*args, **kwargs) @@ -423,5 +474,6 @@ def on_200(self, session): schema_builder=_custom_show_schema_builder ) + class ChangeStateDelete(_ChangeStateDelete): pass From ea95cc08fa6d117b2ce150fe8ae61e45cc87c39d Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Mon, 3 Nov 2025 11:22:44 -0800 Subject: [PATCH 08/25] Rename cli extension module name --- .../HISTORY.rst | 0 src/azure-changesafety/README.md | 49 ++++++ .../azext_change_state/__init__.py | 0 .../azext_change_state/_help.py | 96 ++++++++++ .../azext_change_state/_params.py | 0 .../azext_change_state/aaz/__init__.py | 0 .../azext_change_state/aaz/latest/__init__.py | 0 .../aaz/latest/change_safety/__cmd_group.py | 2 +- .../aaz/latest/change_safety/__init__.py | 0 .../change_safety/change_state/__cmd_group.py | 2 +- .../change_safety/change_state/__init__.py | 0 .../change_safety/change_state/_create.py | 6 +- .../change_safety/change_state/_delete.py | 6 +- .../change_safety/change_state/_show.py | 6 +- .../change_safety/change_state/_update.py | 2 +- .../azext_change_state/azext_metadata.json | 0 .../azext_change_state/commands.py | 8 +- .../azext_change_state/custom.py | 13 +- .../azext_change_state/tests/__init__.py | 0 .../tests/latest/__init__.py | 0 .../tests/latest/test_change_state.py | 98 +++++++++++ .../setup.cfg | 0 .../setup.py | 0 src/change-state/README.md | 5 - src/change-state/azext_change_state/_help.py | 11 -- .../tests/latest/test_change_state.py | 164 ------------------ 26 files changed, 269 insertions(+), 199 deletions(-) rename src/{change-state => azure-changesafety}/HISTORY.rst (100%) create mode 100644 src/azure-changesafety/README.md rename src/{change-state => azure-changesafety}/azext_change_state/__init__.py (100%) create mode 100644 src/azure-changesafety/azext_change_state/_help.py rename src/{change-state => azure-changesafety}/azext_change_state/_params.py (100%) rename src/{change-state => azure-changesafety}/azext_change_state/aaz/__init__.py (100%) rename src/{change-state => azure-changesafety}/azext_change_state/aaz/latest/__init__.py (100%) rename src/{change-state => azure-changesafety}/azext_change_state/aaz/latest/change_safety/__cmd_group.py (96%) rename src/{change-state => azure-changesafety}/azext_change_state/aaz/latest/change_safety/__init__.py (100%) rename src/{change-state => azure-changesafety}/azext_change_state/aaz/latest/change_safety/change_state/__cmd_group.py (94%) rename src/{change-state => azure-changesafety}/azext_change_state/aaz/latest/change_safety/change_state/__init__.py (100%) rename src/{change-state => azure-changesafety}/azext_change_state/aaz/latest/change_safety/change_state/_create.py (99%) rename src/{change-state => azure-changesafety}/azext_change_state/aaz/latest/change_safety/change_state/_delete.py (97%) rename src/{change-state => azure-changesafety}/azext_change_state/aaz/latest/change_safety/change_state/_show.py (99%) rename src/{change-state => azure-changesafety}/azext_change_state/aaz/latest/change_safety/change_state/_update.py (99%) rename src/{change-state => azure-changesafety}/azext_change_state/azext_metadata.json (100%) rename src/{change-state => azure-changesafety}/azext_change_state/commands.py (73%) rename src/{change-state => azure-changesafety}/azext_change_state/custom.py (97%) rename src/{change-state => azure-changesafety}/azext_change_state/tests/__init__.py (100%) rename src/{change-state => azure-changesafety}/azext_change_state/tests/latest/__init__.py (100%) create mode 100644 src/azure-changesafety/azext_change_state/tests/latest/test_change_state.py rename src/{change-state => azure-changesafety}/setup.cfg (100%) rename src/{change-state => azure-changesafety}/setup.py (100%) delete mode 100644 src/change-state/README.md delete mode 100644 src/change-state/azext_change_state/_help.py delete mode 100644 src/change-state/azext_change_state/tests/latest/test_change_state.py diff --git a/src/change-state/HISTORY.rst b/src/azure-changesafety/HISTORY.rst similarity index 100% rename from src/change-state/HISTORY.rst rename to src/azure-changesafety/HISTORY.rst diff --git a/src/azure-changesafety/README.md b/src/azure-changesafety/README.md new file mode 100644 index 00000000000..b2077c14a44 --- /dev/null +++ b/src/azure-changesafety/README.md @@ -0,0 +1,49 @@ +# Azure CLI Change Safety Extension +Azure CLI extension for managing Change Safety `ChangeState` resources used to coordinate operational changes across Azure targets. + +## Installation +```bash +az extension add --source --yes +# or install the latest published build +az extension add --name azure-changesafety +``` + +## Commands +```bash +az changesafety changestate create # Create a ChangeState definition for one or more targets. +az changesafety changestate update # Update metadata, rollout configuration, or target definitions. +az changesafety changestate delete # Delete a ChangeState resource. +az changesafety changestate show # Display details for a ChangeState resource. +``` + +Run `az changesafety changestate -h` to see full parameter details and examples. + +## Examples +Create a ChangeState describing a web app rollout: +```bash +az changesafety changestate create \ + -g MyResourceGroup \ + -n webapp-rollout-01 \ + --change-type AppDeployment \ + --rollout-type Normal \ + --targets "resourceId=/subscriptions//resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=create" \ + --links name=Runbook uri=https://contoso.com/runbook +``` + +Update the rollout type and add a comment: +```bash +az changesafety changestate update \ + -g MyResourceGroup \ + -n webapp-rollout-01 \ + --rollout-type Emergency \ + --comments "Escalated due to customer impact" +``` + +Delete a ChangeState: +```bash +az changesafety changestate delete -g MyResourceGroup -n webapp-rollout-01 --yes +``` + +## Additional Information +- View command documentation: `az changesafety changestate -h` +- Remove the extension when no longer needed: `az extension remove --name azure-changesafety` \ No newline at end of file diff --git a/src/change-state/azext_change_state/__init__.py b/src/azure-changesafety/azext_change_state/__init__.py similarity index 100% rename from src/change-state/azext_change_state/__init__.py rename to src/azure-changesafety/azext_change_state/__init__.py diff --git a/src/azure-changesafety/azext_change_state/_help.py b/src/azure-changesafety/azext_change_state/_help.py new file mode 100644 index 00000000000..a5c041b913b --- /dev/null +++ b/src/azure-changesafety/azext_change_state/_help.py @@ -0,0 +1,96 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: disable=line-too-long +# pylint: disable=too-many-lines + +from knack.help_files import helps # pylint: disable=unused-import + +helps['changesafety'] = """ + type: group + short-summary: Manage Change Safety resources. +""" + +helps['changesafety changestate'] = """ + type: group + short-summary: Manage ChangeState resources that describe planned changes across targets. +""" + +helps['changesafety changestate create'] = """ + type: command + short-summary: Create a ChangeState resource. + long-summary: > + Provide at least one target definition to describe which resources or operations the change + will affect. Targets are expressed as comma or semicolon separated key=value pairs such as + resourceId=RESOURCE_ID,operation=DELETE. The command is also available through the alias + `az change-safety change-state`. + parameters: + - name: --targets + short-summary: > + One or more target definitions expressed as key=value pairs (for example + resourceId=RESOURCE_ID,operation=CREATE,resourceType=Microsoft.Compute/virtualMachines). + - name: --change-type + short-summary: Classify the change such as AppDeployment, Config, ManualTouch, or PolicyDeployment. + - name: --rollout-type + short-summary: Specify the rollout urgency (Normal, Hotfix, or Emergency). + - name: --stage-map + short-summary: Reference an existing StageMap resource using resource-id=RESOURCE_ID and optional parameters key=value pairs. + - name: --links + short-summary: Add supporting links by repeating --links name=NAME uri=URL [description=TEXT]. + examples: + - name: Create a change state for a VM rollout + text: |- + az changesafety changestate create -g MyResourceGroup -n deploy-001 --change-type AppDeployment --rollout-type Normal --targets resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PUT + - name: Create with staging rollout configuration + text: |- + az changesafety changestate create -g MyResourceGroup -n ops-change-01 --rollout-type Hotfix --targets resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST +""" + +helps['changesafety changestate update'] = """ + type: command + short-summary: Update an existing ChangeState resource. + long-summary: > + Use this command to modify descriptive metadata, rollout settings, or replace targets for an + existing change. When you pass --targets, the supplied definitions overwrite the previous set. + This command is also available through the alias `az change-safety change-state`. + parameters: + - name: --targets + short-summary: > + Optional target definitions to replace the existing list. Provide key=value pairs such as + resourceId=RESOURCE_ID,operation=DELETE. + - name: --comments + short-summary: Provide notes about the latest update to the change state. + - name: --anticipated-start-time + short-summary: Update the expected start time in ISO 8601 format. + - name: --anticipated-end-time + short-summary: Update the expected completion time in ISO 8601 format. + examples: + - name: Adjust rollout type and add a comment + text: |- + az changesafety changestate update -g MyResourceGroup -n deploy-001 --rollout-type Emergency --comments "Escalated to emergency rollout" + - name: Replace the target definition + text: |- + az changesafety changestate update -g MyResourceGroup -n deploy-001 --targets resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/myServer,operation=PATCH +""" + +helps['changesafety changestate delete'] = """ + type: command + short-summary: Delete a ChangeState resource. + examples: + - name: Delete a change state without confirmation + text: |- + az changesafety changestate delete -g MyResourceGroup -n deploy-001 --yes +""" + +helps['changesafety changestate show'] = """ + type: command + short-summary: Show details for a ChangeState resource. + examples: + - name: Show a change state + text: |- + az changesafety changestate show -g MyResourceGroup -n deploy-001 +""" diff --git a/src/change-state/azext_change_state/_params.py b/src/azure-changesafety/azext_change_state/_params.py similarity index 100% rename from src/change-state/azext_change_state/_params.py rename to src/azure-changesafety/azext_change_state/_params.py diff --git a/src/change-state/azext_change_state/aaz/__init__.py b/src/azure-changesafety/azext_change_state/aaz/__init__.py similarity index 100% rename from src/change-state/azext_change_state/aaz/__init__.py rename to src/azure-changesafety/azext_change_state/aaz/__init__.py diff --git a/src/change-state/azext_change_state/aaz/latest/__init__.py b/src/azure-changesafety/azext_change_state/aaz/latest/__init__.py similarity index 100% rename from src/change-state/azext_change_state/aaz/latest/__init__.py rename to src/azure-changesafety/azext_change_state/aaz/latest/__init__.py diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/__cmd_group.py b/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/__cmd_group.py similarity index 96% rename from src/change-state/azext_change_state/aaz/latest/change_safety/__cmd_group.py rename to src/azure-changesafety/azext_change_state/aaz/latest/change_safety/__cmd_group.py index c6dd5b296ba..1734459314b 100644 --- a/src/change-state/azext_change_state/aaz/latest/change_safety/__cmd_group.py +++ b/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/__cmd_group.py @@ -12,7 +12,7 @@ @register_command_group( - "change-safety", + "changesafety", ) class __CMDGroup(AAZCommandGroup): """Manage Change Safety diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/__init__.py b/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/__init__.py similarity index 100% rename from src/change-state/azext_change_state/aaz/latest/change_safety/__init__.py rename to src/azure-changesafety/azext_change_state/aaz/latest/change_safety/__init__.py diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/__cmd_group.py b/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/__cmd_group.py similarity index 94% rename from src/change-state/azext_change_state/aaz/latest/change_safety/change_state/__cmd_group.py rename to src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/__cmd_group.py index 00d6ce9e052..401be3b73af 100644 --- a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/__cmd_group.py +++ b/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/__cmd_group.py @@ -12,7 +12,7 @@ @register_command_group( - "change-safety change-state", + "changesafety changestate", ) class __CMDGroup(AAZCommandGroup): """Manage Change State diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/__init__.py b/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/__init__.py similarity index 100% rename from src/change-state/azext_change_state/aaz/latest/change_safety/change_state/__init__.py rename to src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/__init__.py diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_create.py b/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_create.py similarity index 99% rename from src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_create.py rename to src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_create.py index 35c38872ced..0fe7e63f36f 100644 --- a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_create.py +++ b/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_create.py @@ -12,7 +12,7 @@ @register_command( - "change-safety change-state create", + "changesafety changestate create", ) class Create(AAZCommand): """Create a ChangeState @@ -21,8 +21,8 @@ class Create(AAZCommand): _aaz_info = { "version": "2025-09-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/Microsoft.ChangeSafety/changestates/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/Microsoft.ChangeSafety/changestates/{}", "2025-09-01-preview"], ] } diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_delete.py b/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_delete.py similarity index 97% rename from src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_delete.py rename to src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_delete.py index be78b96198f..96893b74400 100644 --- a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_delete.py +++ b/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_delete.py @@ -12,7 +12,7 @@ @register_command( - "change-safety change-state delete", + "changesafety changestate delete", confirmation="Are you sure you want to perform this operation?", ) class Delete(AAZCommand): @@ -22,8 +22,8 @@ class Delete(AAZCommand): _aaz_info = { "version": "2025-09-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/Microsoft.ChangeSafety/changestates/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/Microsoft.ChangeSafety/changestates/{}", "2025-09-01-preview"], ] } diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_show.py b/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_show.py similarity index 99% rename from src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_show.py rename to src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_show.py index f73fcf27901..aeb51bc3422 100644 --- a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_show.py +++ b/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_show.py @@ -12,7 +12,7 @@ @register_command( - "change-safety change-state show", + "changesafety changestate show", ) class Show(AAZCommand): """Get a ChangeState @@ -21,8 +21,8 @@ class Show(AAZCommand): _aaz_info = { "version": "2025-09-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/Microsoft.ChangeSafety/changestates/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/Microsoft.ChangeSafety/changestates/{}", "2025-09-01-preview"], ] } diff --git a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_update.py b/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_update.py similarity index 99% rename from src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_update.py rename to src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_update.py index ee1213da2a6..3de21715670 100644 --- a/src/change-state/azext_change_state/aaz/latest/change_safety/change_state/_update.py +++ b/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_update.py @@ -12,7 +12,7 @@ @register_command( - "change-safety change-state update", + "changesafety changestate update", ) class Update(AAZCommand): """Update a ChangeState diff --git a/src/change-state/azext_change_state/azext_metadata.json b/src/azure-changesafety/azext_change_state/azext_metadata.json similarity index 100% rename from src/change-state/azext_change_state/azext_metadata.json rename to src/azure-changesafety/azext_change_state/azext_metadata.json diff --git a/src/change-state/azext_change_state/commands.py b/src/azure-changesafety/azext_change_state/commands.py similarity index 73% rename from src/change-state/azext_change_state/commands.py rename to src/azure-changesafety/azext_change_state/commands.py index fb19244e143..e0bfc0702af 100644 --- a/src/change-state/azext_change_state/commands.py +++ b/src/azure-changesafety/azext_change_state/commands.py @@ -16,7 +16,7 @@ def load_command_table(self, _): # pylint: disable=unused-argument delete_command = ChangeStateDelete(loader=self) show_command = ChangeStateShow(loader=self) - self.command_table['change-state create'] = create_command - self.command_table['change-state update'] = update_command - self.command_table['change-state delete'] = delete_command - self.command_table['change-state show'] = show_command + self.command_table['changesafety changestate create'] = create_command + self.command_table['changesafety changestate update'] = update_command + self.command_table['changesafety changestate delete'] = delete_command + self.command_table['changesafety changestate show'] = show_command diff --git a/src/change-state/azext_change_state/custom.py b/src/azure-changesafety/azext_change_state/custom.py similarity index 97% rename from src/change-state/azext_change_state/custom.py rename to src/azure-changesafety/azext_change_state/custom.py index 47c4300dab6..55a78c9c3b7 100644 --- a/src/change-state/azext_change_state/custom.py +++ b/src/azure-changesafety/azext_change_state/custom.py @@ -106,7 +106,14 @@ def _normalize_targets_arg(raw_targets): else: values = [raw_targets] - return [str(v) for v in values if v is not None] + normalized_values = [] + for value in values: + if value is None: + continue + text = str(value).strip() + if text: + normalized_values.append(text) + return normalized_values def _inject_targets_into_result(data, targets): @@ -171,7 +178,7 @@ def _build_arguments_schema(cls, *args, **kwargs): options=["--targets"], help=( "Target definitions expressed as key=value pairs separated by commas or semicolons. " - "Example: --targets \"resourceId=,operation=delete\"" + "Example: --targets \"resourceId=RESOURCE_ID,operation=delete\"" ), ) schema.targets.Element = AAZStrArg() @@ -311,7 +318,7 @@ def _build_arguments_schema(cls, *args, **kwargs): options=["--targets"], help=( "Optional target definitions expressed as key=value pairs separated by commas or semicolons. " - "Example: --targets \"resourceId=,operation=delete\"" + "Example: --targets \"resourceId=RESOURCE_ID,operation=delete\"" ), ) schema.targets.Element = AAZStrArg() diff --git a/src/change-state/azext_change_state/tests/__init__.py b/src/azure-changesafety/azext_change_state/tests/__init__.py similarity index 100% rename from src/change-state/azext_change_state/tests/__init__.py rename to src/azure-changesafety/azext_change_state/tests/__init__.py diff --git a/src/change-state/azext_change_state/tests/latest/__init__.py b/src/azure-changesafety/azext_change_state/tests/latest/__init__.py similarity index 100% rename from src/change-state/azext_change_state/tests/latest/__init__.py rename to src/azure-changesafety/azext_change_state/tests/latest/__init__.py diff --git a/src/azure-changesafety/azext_change_state/tests/latest/test_change_state.py b/src/azure-changesafety/azext_change_state/tests/latest/test_change_state.py new file mode 100644 index 00000000000..de8e1247e3c --- /dev/null +++ b/src/azure-changesafety/azext_change_state/tests/latest/test_change_state.py @@ -0,0 +1,98 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +from types import SimpleNamespace + +import pytest +from azure.cli.testsdk import * + +from azext_change_state.custom import ( + ChangeStateCreate, + ChangeStateUpdate, + _inject_change_definition_into_content, + _inject_targets_into_result, + _normalize_targets_arg, +) +from azure.cli.core.azclierror import InvalidArgumentValueError +from azure.cli.core.aaz._arg_action import AAZArgActionOperations, _ELEMENT_APPEND_KEY + + +class ChangeStateScenario(ScenarioTest): + def test_normalize_targets_from_operations(self): + operations = AAZArgActionOperations.__new__(AAZArgActionOperations) + operations._ops = [ + ((_ELEMENT_APPEND_KEY,), "env=prod"), + ((0, "resourceId"), "/subscriptions/000/resourceGroups/rg/providers/Microsoft.Web/sites/app"), + ((0, "operation"), "delete"), + ((1,), "subscriptionId=00000000-0000-0000-0000-000000000000"), + ] + + normalized = _normalize_targets_arg(operations) + + assert normalized == [ + "env=prod,resourceId=/subscriptions/000/resourceGroups/rg/providers/Microsoft.Web/sites/app,operation=delete", + "subscriptionId=00000000-0000-0000-0000-000000000000", + ] + + def test_normalize_targets_from_serializable_value(self): + class DummySerializable: + def to_serialized_data(self): + return ["rg=my-rg", None, "", "operation=show"] + + normalized = _normalize_targets_arg(DummySerializable()) + + assert normalized == ["rg=my-rg", "operation=show"] + + def test_normalize_targets_from_list_of_strings(self): + raw_targets = [" resourceId=/foo ", "", "operation=PUT", None] + + normalized = _normalize_targets_arg(raw_targets) + + assert normalized == ["resourceId=/foo", "operation=PUT"] + + def test_normalize_targets_with_none_returns_empty(self): + assert _normalize_targets_arg(None) == [] + + def test_inject_change_definition_into_content_adds_properties(self): + ctx = _dummy_ctx_with_change_definition({"details": {"targets": []}}) + content = {"properties": {"existing": "value"}} + + result = _inject_change_definition_into_content(content, ctx) + + assert result["properties"]["existing"] == "value" + assert result["properties"]["changeDefinition"] == {"details": {"targets": []}} + + def test_inject_change_definition_with_empty_payload_noop(self): + ctx = _dummy_ctx_with_change_definition({}) + original = {"properties": {"foo": "bar"}} + + result = _inject_change_definition_into_content(original.copy(), ctx) + + assert result == original + + def test_inject_targets_into_result_updates_nested_properties(self): + data = {"properties": {"changeDefinition": {"details": {}}}} + targets = [{"resourceId": "/foo"}] + + _inject_targets_into_result(data, targets) + + assert data["properties"]["changeDefinition"]["details"]["targets"] == targets + + def test_inject_targets_does_not_override_existing(self): + existing = [{"resourceId": "/existing"}] + data = {"changeDefinition": {"details": {"targets": existing.copy()}}} + new_targets = [{"resourceId": "/new"}] + + _inject_targets_into_result(data, new_targets) + + assert data["changeDefinition"]["details"]["targets"] == existing + + +def _dummy_ctx_with_change_definition(payload): + dummy = SimpleNamespace() + dummy.to_serialized_data = lambda: payload + return SimpleNamespace(vars=SimpleNamespace(change_definition=dummy)) diff --git a/src/change-state/setup.cfg b/src/azure-changesafety/setup.cfg similarity index 100% rename from src/change-state/setup.cfg rename to src/azure-changesafety/setup.cfg diff --git a/src/change-state/setup.py b/src/azure-changesafety/setup.py similarity index 100% rename from src/change-state/setup.py rename to src/azure-changesafety/setup.py diff --git a/src/change-state/README.md b/src/change-state/README.md deleted file mode 100644 index 9e4e42b3250..00000000000 --- a/src/change-state/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Azure CLI ChangeState Extension # -This is an extension to Azure CLI to manage ChangeState resources. - -## How to use ## -Please add commands usage here. \ No newline at end of file diff --git a/src/change-state/azext_change_state/_help.py b/src/change-state/azext_change_state/_help.py deleted file mode 100644 index 126d5d00714..00000000000 --- a/src/change-state/azext_change_state/_help.py +++ /dev/null @@ -1,11 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -# Code generated by aaz-dev-tools -# -------------------------------------------------------------------------------------------- - -# pylint: disable=line-too-long -# pylint: disable=too-many-lines - -from knack.help_files import helps # pylint: disable=unused-import diff --git a/src/change-state/azext_change_state/tests/latest/test_change_state.py b/src/change-state/azext_change_state/tests/latest/test_change_state.py deleted file mode 100644 index 018dbf903c0..00000000000 --- a/src/change-state/azext_change_state/tests/latest/test_change_state.py +++ /dev/null @@ -1,164 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -# Code generated by aaz-dev-tools -# -------------------------------------------------------------------------------------------- - -from types import SimpleNamespace - -import pytest -from azure.cli.testsdk import * - -from azext_change_state.custom import ( - ChangeStateCreate, - ChangeStateUpdate, - _inject_change_definition_into_content, - _inject_targets_into_result, -) -from azure.cli.core.azclierror import InvalidArgumentValueError - - -class ChangeStateScenario(ScenarioTest): - # TODO: add tests here - pass - - -class _SerializableValue: - """Test helper that mimics serialized AAZ values.""" - - def __init__(self, data): - self._data = data - - def to_serialized_data(self): - return self._data - - -def test_parse_targets_single_entry(): - result = ChangeStateCreate._parse_targets(["env=prod"]) - assert result == [{"env": "prod"}] - - -def test_parse_targets_with_multiple_delimiters(): - tokens = ["env=prod,region=us; role=web "] - result = ChangeStateCreate._parse_targets(tokens) - assert result == [{"env": "prod", "region": "us", "role": "web"}] - - -def test_parse_targets_rejects_invalid_entries(): - with pytest.raises(InvalidArgumentValueError): - ChangeStateCreate._parse_targets(["invalid"]) - - -def test_parse_targets_maps_resource_group_alias(): - result = ChangeStateCreate._parse_targets(["rg=my-group"]) - assert result == [{"resourceGroupName": "my-group"}] - - -def test_parse_targets_uppercases_http_method_value(): - result = ChangeStateCreate._parse_targets(["httpMethod=delete"]) - assert result == [{"httpMethod": "DELETE"}] - - -def test_parse_targets_maps_operation_to_http_method(): - result = ChangeStateCreate._parse_targets(["operation=POST,resource=/abc"]) - assert result == [{"httpMethod": "POST", "resource": "/abc"}] - - -def test_build_change_definition_uses_targets_and_name(): - cmd = object.__new__(ChangeStateCreate) - cmd._raw_targets = ["env=prod"] - cmd.ctx = SimpleNamespace(args=SimpleNamespace(change_state_name=_SerializableValue("test-change"))) - definition = ChangeStateCreate._build_change_definition(cmd) - assert definition == { - "kind": "Targets", - "name": "test-change", - "details": {"targets": [{"env": "prod"}]} - } - - -def test_build_change_definition_normalizes_operation(): - cmd = object.__new__(ChangeStateCreate) - cmd._raw_targets = ["operation=post"] - cmd.ctx = SimpleNamespace(args=SimpleNamespace(change_state_name=_SerializableValue("test-change"))) - definition = ChangeStateCreate._build_change_definition(cmd) - assert definition["details"]["targets"] == [{"httpMethod": "POST"}] - - -def test_build_change_definition_handles_serializable_value(): - class DummyName: - def to_serialized_data(self): - return "serialized-name" - - cmd = object.__new__(ChangeStateCreate) - cmd._raw_targets = ["env=prod"] - cmd.ctx = SimpleNamespace(args=SimpleNamespace(change_state_name=DummyName())) - definition = ChangeStateCreate._build_change_definition(cmd) - assert definition["name"] == "serialized-name" - - -def test_command_name_overrides(): - assert ChangeStateCreate.AZ_NAME == "change-safety change-state create" - assert ChangeStateUpdate.AZ_NAME == "change-safety change-state update" - - -def test_inject_change_definition_into_dict_payload(): - ctx = SimpleNamespace(vars=SimpleNamespace(change_definition=_SerializableValue(_valid_change_definition()))) - original = {"properties": {"existing": "value"}} - updated = _inject_change_definition_into_content(original, ctx) - assert updated["properties"]["changeDefinition"] == _valid_change_definition() - assert updated["properties"]["existing"] == "value" - - -def test_inject_change_definition_into_content_when_none(): - ctx = SimpleNamespace(vars=SimpleNamespace(change_definition=_SerializableValue(_valid_change_definition()))) - payload = _inject_change_definition_into_content(None, ctx) - assert payload["properties"]["changeDefinition"] == _valid_change_definition() - - -def test_inject_change_definition_skips_empty_definition(): - ctx = SimpleNamespace(vars=SimpleNamespace(change_definition=_SerializableValue({}))) - original = {"properties": {}} - updated = _inject_change_definition_into_content(original, ctx) - assert updated is original - - -def test_inject_targets_into_result_populates_properties_container(): - targets = [{"env": "prod"}] - data = {"properties": {"changeDefinition": {"details": {}}}} - _inject_targets_into_result(data, targets) - assert data["properties"]["changeDefinition"]["details"]["targets"] == targets - - -def test_inject_targets_into_result_handles_list_payloads(): - targets = [{"httpMethod": "POST"}] - data = [{"changeDefinition": {"details": {}}}] - _inject_targets_into_result(data, targets) - assert data[0]["changeDefinition"]["details"]["targets"] == targets - - -def test_create_operation_content_includes_change_definition(): - args_schema = ChangeStateCreate._build_arguments_schema() - args = args_schema() - args.change_state_name = "test-change" - args.change_type = "AppDeployment" - args.rollout_type = "Normal" - args.anticipated_start_time = "2024-11-01T08:00:00Z" - args.anticipated_end_time = "2024-11-01T10:00:00Z" - ctx = SimpleNamespace( - args=args, - vars=SimpleNamespace(change_definition=_SerializableValue(_valid_change_definition())) - ) - - op = object.__new__(ChangeStateCreate.ChangeStatesCreateOrUpdate) - op.ctx = ctx - payload = op.content - assert payload["properties"]["changeDefinition"] == _valid_change_definition() - - -def _valid_change_definition(): - return { - "kind": "Targets", - "name": "test-change", - "details": {"targets": [{"resourceId": "/foo", "operation": "DELETE"}]}, - } From bdeffae6b9782d806da27482b5b4c0e6cd3697d9 Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Mon, 3 Nov 2025 13:01:03 -0800 Subject: [PATCH 09/25] Fix linter --- src/azure-changesafety/azext_change_state/_help.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/azure-changesafety/azext_change_state/_help.py b/src/azure-changesafety/azext_change_state/_help.py index a5c041b913b..26b4a50bd87 100644 --- a/src/azure-changesafety/azext_change_state/_help.py +++ b/src/azure-changesafety/azext_change_state/_help.py @@ -44,10 +44,10 @@ examples: - name: Create a change state for a VM rollout text: |- - az changesafety changestate create -g MyResourceGroup -n deploy-001 --change-type AppDeployment --rollout-type Normal --targets resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PUT + az changesafety changestate create -g MyResourceGroup -n deploy-001 --change-type AppDeployment --rollout-type Normal --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PUT" - name: Create with staging rollout configuration text: |- - az changesafety changestate create -g MyResourceGroup -n ops-change-01 --rollout-type Hotfix --targets resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST + az changesafety changestate create -g MyResourceGroup -n ops-change-01 --rollout-type Hotfix --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST" """ helps['changesafety changestate update'] = """ @@ -74,7 +74,7 @@ az changesafety changestate update -g MyResourceGroup -n deploy-001 --rollout-type Emergency --comments "Escalated to emergency rollout" - name: Replace the target definition text: |- - az changesafety changestate update -g MyResourceGroup -n deploy-001 --targets resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/myServer,operation=PATCH + az changesafety changestate update -g MyResourceGroup -n deploy-001 --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/myServer,operation=PATCH" """ helps['changesafety changestate delete'] = """ From 78286ddac1ac3991cb66ce89176340464c0ffabe Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Mon, 3 Nov 2025 13:21:25 -0800 Subject: [PATCH 10/25] Fix linter --- src/azure-changesafety/azext_change_state/_help.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/azure-changesafety/azext_change_state/_help.py b/src/azure-changesafety/azext_change_state/_help.py index 26b4a50bd87..0c231b6d404 100644 --- a/src/azure-changesafety/azext_change_state/_help.py +++ b/src/azure-changesafety/azext_change_state/_help.py @@ -42,6 +42,9 @@ - name: --links short-summary: Add supporting links by repeating --links name=NAME uri=URL [description=TEXT]. examples: + - name: Create with stage map reference and status link + text: |- + az changesafety changestate create -g MyResourceGroup -n deploy-002 --change-type ManualTouch --rollout-type Normal --stage-map "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.ChangeSafety/stageMaps/rollout-stage-map" --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH" --links name=status uri=https://contoso.com/change/rollout-002 - name: Create a change state for a VM rollout text: |- az changesafety changestate create -g MyResourceGroup -n deploy-001 --change-type AppDeployment --rollout-type Normal --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PUT" @@ -72,6 +75,9 @@ - name: Adjust rollout type and add a comment text: |- az changesafety changestate update -g MyResourceGroup -n deploy-001 --rollout-type Emergency --comments "Escalated to emergency rollout" + - name: Update scheduling window + text: |- + az changesafety changestate update -g MyResourceGroup -n deploy-001 --anticipated-start-time "2024-09-01T08:00:00Z" --anticipated-end-time "2024-09-01T12:00:00Z" - name: Replace the target definition text: |- az changesafety changestate update -g MyResourceGroup -n deploy-001 --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/myServer,operation=PATCH" From 56b725b968d8c90d1168f5d6d8752c29372fafac Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Mon, 3 Nov 2025 13:39:21 -0800 Subject: [PATCH 11/25] Fix linter --- src/azure-changesafety/azext_change_state/_help.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-changesafety/azext_change_state/_help.py b/src/azure-changesafety/azext_change_state/_help.py index 0c231b6d404..cae17e48d1c 100644 --- a/src/azure-changesafety/azext_change_state/_help.py +++ b/src/azure-changesafety/azext_change_state/_help.py @@ -44,7 +44,7 @@ examples: - name: Create with stage map reference and status link text: |- - az changesafety changestate create -g MyResourceGroup -n deploy-002 --change-type ManualTouch --rollout-type Normal --stage-map "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.ChangeSafety/stageMaps/rollout-stage-map" --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH" --links name=status uri=https://contoso.com/change/rollout-002 + az changesafety changestate create -g MyResourceGroup -n deploy-002 --change-type ManualTouch --rollout-type Normal --stage-map "{resource-id:/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.ChangeSafety/stageMaps/rollout-stage-map}" --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH" --links "[{name:status,uri:'https://contoso.com/change/rollout-002'}]" - name: Create a change state for a VM rollout text: |- az changesafety changestate create -g MyResourceGroup -n deploy-001 --change-type AppDeployment --rollout-type Normal --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PUT" From 964371c0a29ce9b6630181e7e5140d5d10d88468 Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Mon, 3 Nov 2025 13:48:25 -0800 Subject: [PATCH 12/25] Fix module name --- .../__init__.py | 8 ++++---- .../{azext_change_state => azext_changesafety}/_help.py | 0 .../{azext_change_state => azext_changesafety}/_params.py | 0 .../aaz/__init__.py | 0 .../aaz/latest/__init__.py | 0 .../aaz/latest/change_safety/__cmd_group.py | 0 .../aaz/latest/change_safety/__init__.py | 0 .../aaz/latest/change_safety/change_state/__cmd_group.py | 0 .../aaz/latest/change_safety/change_state/__init__.py | 0 .../aaz/latest/change_safety/change_state/_create.py | 0 .../aaz/latest/change_safety/change_state/_delete.py | 0 .../aaz/latest/change_safety/change_state/_show.py | 0 .../aaz/latest/change_safety/change_state/_update.py | 0 .../azext_metadata.json | 0 .../commands.py | 0 .../{azext_change_state => azext_changesafety}/custom.py | 4 ++-- .../tests/__init__.py | 0 .../tests/latest/__init__.py | 0 .../tests/latest/test_change_state.py | 2 +- src/azure-changesafety/setup.py | 2 +- 20 files changed, 8 insertions(+), 8 deletions(-) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/__init__.py (83%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/_help.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/_params.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/aaz/__init__.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/aaz/latest/__init__.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/aaz/latest/change_safety/__cmd_group.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/aaz/latest/change_safety/__init__.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/aaz/latest/change_safety/change_state/__cmd_group.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/aaz/latest/change_safety/change_state/__init__.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/aaz/latest/change_safety/change_state/_create.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/aaz/latest/change_safety/change_state/_delete.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/aaz/latest/change_safety/change_state/_show.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/aaz/latest/change_safety/change_state/_update.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/azext_metadata.json (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/commands.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/custom.py (99%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/tests/__init__.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/tests/latest/__init__.py (100%) rename src/azure-changesafety/{azext_change_state => azext_changesafety}/tests/latest/test_change_state.py (98%) diff --git a/src/azure-changesafety/azext_change_state/__init__.py b/src/azure-changesafety/azext_changesafety/__init__.py similarity index 83% rename from src/azure-changesafety/azext_change_state/__init__.py rename to src/azure-changesafety/azext_changesafety/__init__.py index a2755a92cdb..05751262e4f 100644 --- a/src/azure-changesafety/azext_change_state/__init__.py +++ b/src/azure-changesafety/azext_changesafety/__init__.py @@ -6,7 +6,7 @@ # -------------------------------------------------------------------------------------------- from azure.cli.core import AzCommandsLoader -from azext_change_state._help import helps # pylint: disable=unused-import +from azext_changesafety._help import helps # pylint: disable=unused-import class ChangeStateCommandsLoader(AzCommandsLoader): @@ -14,12 +14,12 @@ class ChangeStateCommandsLoader(AzCommandsLoader): def __init__(self, cli_ctx=None): from azure.cli.core.commands import CliCommandType custom_command_type = CliCommandType( - operations_tmpl='azext_change_state.custom#{}') + operations_tmpl='azext_changesafety.custom#{}') super().__init__(cli_ctx=cli_ctx, custom_command_type=custom_command_type) def load_command_table(self, args): - from azext_change_state.commands import load_command_table + from azext_changesafety.commands import load_command_table from azure.cli.core.aaz import load_aaz_command_table try: from . import aaz @@ -35,7 +35,7 @@ def load_command_table(self, args): return self.command_table def load_arguments(self, command): - from azext_change_state._params import load_arguments + from azext_changesafety._params import load_arguments load_arguments(self, command) diff --git a/src/azure-changesafety/azext_change_state/_help.py b/src/azure-changesafety/azext_changesafety/_help.py similarity index 100% rename from src/azure-changesafety/azext_change_state/_help.py rename to src/azure-changesafety/azext_changesafety/_help.py diff --git a/src/azure-changesafety/azext_change_state/_params.py b/src/azure-changesafety/azext_changesafety/_params.py similarity index 100% rename from src/azure-changesafety/azext_change_state/_params.py rename to src/azure-changesafety/azext_changesafety/_params.py diff --git a/src/azure-changesafety/azext_change_state/aaz/__init__.py b/src/azure-changesafety/azext_changesafety/aaz/__init__.py similarity index 100% rename from src/azure-changesafety/azext_change_state/aaz/__init__.py rename to src/azure-changesafety/azext_changesafety/aaz/__init__.py diff --git a/src/azure-changesafety/azext_change_state/aaz/latest/__init__.py b/src/azure-changesafety/azext_changesafety/aaz/latest/__init__.py similarity index 100% rename from src/azure-changesafety/azext_change_state/aaz/latest/__init__.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/__init__.py diff --git a/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/__cmd_group.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/__cmd_group.py similarity index 100% rename from src/azure-changesafety/azext_change_state/aaz/latest/change_safety/__cmd_group.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/__cmd_group.py diff --git a/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/__init__.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/__init__.py similarity index 100% rename from src/azure-changesafety/azext_change_state/aaz/latest/change_safety/__init__.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/__init__.py diff --git a/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/__cmd_group.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/__cmd_group.py similarity index 100% rename from src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/__cmd_group.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/__cmd_group.py diff --git a/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/__init__.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/__init__.py similarity index 100% rename from src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/__init__.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/__init__.py diff --git a/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_create.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_create.py similarity index 100% rename from src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_create.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_create.py diff --git a/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_delete.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_delete.py similarity index 100% rename from src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_delete.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_delete.py diff --git a/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_show.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_show.py similarity index 100% rename from src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_show.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_show.py diff --git a/src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_update.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_update.py similarity index 100% rename from src/azure-changesafety/azext_change_state/aaz/latest/change_safety/change_state/_update.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_update.py diff --git a/src/azure-changesafety/azext_change_state/azext_metadata.json b/src/azure-changesafety/azext_changesafety/azext_metadata.json similarity index 100% rename from src/azure-changesafety/azext_change_state/azext_metadata.json rename to src/azure-changesafety/azext_changesafety/azext_metadata.json diff --git a/src/azure-changesafety/azext_change_state/commands.py b/src/azure-changesafety/azext_changesafety/commands.py similarity index 100% rename from src/azure-changesafety/azext_change_state/commands.py rename to src/azure-changesafety/azext_changesafety/commands.py diff --git a/src/azure-changesafety/azext_change_state/custom.py b/src/azure-changesafety/azext_changesafety/custom.py similarity index 99% rename from src/azure-changesafety/azext_change_state/custom.py rename to src/azure-changesafety/azext_changesafety/custom.py index 55a78c9c3b7..25f13f592e4 100644 --- a/src/azure-changesafety/azext_change_state/custom.py +++ b/src/azure-changesafety/azext_changesafety/custom.py @@ -25,7 +25,7 @@ _ELEMENT_APPEND_KEY, ) from azure.cli.core.azclierror import InvalidArgumentValueError -from azext_change_state.aaz.latest.change_safety.change_state import ( +from azext_changesafety.aaz.latest.change_safety.change_state import ( Create as _ChangeStateCreate, Update as _ChangeStateUpdate, Show as _ChangeStateShow, @@ -144,7 +144,7 @@ def process(item): def _custom_show_schema_builder(): # Import the generated Show class - from azext_change_state.aaz.latest.change_safety.change_state._show import Show as GeneratedShow + from azext_changesafety.aaz.latest.change_safety.change_state._show import Show as GeneratedShow # Get the base schema from the generated code base_schema = GeneratedShow.ChangeStatesGet._build_schema_on_200() diff --git a/src/azure-changesafety/azext_change_state/tests/__init__.py b/src/azure-changesafety/azext_changesafety/tests/__init__.py similarity index 100% rename from src/azure-changesafety/azext_change_state/tests/__init__.py rename to src/azure-changesafety/azext_changesafety/tests/__init__.py diff --git a/src/azure-changesafety/azext_change_state/tests/latest/__init__.py b/src/azure-changesafety/azext_changesafety/tests/latest/__init__.py similarity index 100% rename from src/azure-changesafety/azext_change_state/tests/latest/__init__.py rename to src/azure-changesafety/azext_changesafety/tests/latest/__init__.py diff --git a/src/azure-changesafety/azext_change_state/tests/latest/test_change_state.py b/src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py similarity index 98% rename from src/azure-changesafety/azext_change_state/tests/latest/test_change_state.py rename to src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py index de8e1247e3c..91909a8db71 100644 --- a/src/azure-changesafety/azext_change_state/tests/latest/test_change_state.py +++ b/src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py @@ -10,7 +10,7 @@ import pytest from azure.cli.testsdk import * -from azext_change_state.custom import ( +from azext_changesafety.custom import ( ChangeStateCreate, ChangeStateUpdate, _inject_change_definition_into_content, diff --git a/src/azure-changesafety/setup.py b/src/azure-changesafety/setup.py index 686df8315a1..79dda33fcd9 100644 --- a/src/azure-changesafety/setup.py +++ b/src/azure-changesafety/setup.py @@ -44,6 +44,6 @@ url='https://github.com/Azure/azure-cli-extensions/tree/main/src/change-state', classifiers=CLASSIFIERS, packages=find_packages(exclude=["tests"]), - package_data={'azext_change_state': ['azext_metadata.json']}, + package_data={'azext_changesafety': ['azext_metadata.json']}, install_requires=DEPENDENCIES ) From 3f5b4d7cfe4039801837c9a1a6e557ef8ad5731e Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Mon, 3 Nov 2025 23:35:37 -0800 Subject: [PATCH 13/25] add test scenarios --- src/azure-changesafety/README.md | 8 +- .../azext_changesafety/_help.py | 23 +- .../azext_changesafety/custom.py | 75 ++++++ .../test_change_state_cli_scenario.yaml | 106 ++++++++ .../tests/latest/test_change_state.py | 248 +++++++++++++++++- 5 files changed, 435 insertions(+), 25 deletions(-) create mode 100644 src/azure-changesafety/azext_changesafety/tests/latest/recordings/test_change_state_cli_scenario.yaml diff --git a/src/azure-changesafety/README.md b/src/azure-changesafety/README.md index b2077c14a44..89e7d4aae7c 100644 --- a/src/azure-changesafety/README.md +++ b/src/azure-changesafety/README.md @@ -23,7 +23,7 @@ Create a ChangeState describing a web app rollout: ```bash az changesafety changestate create \ -g MyResourceGroup \ - -n webapp-rollout-01 \ + -n webAppRollout01 \ --change-type AppDeployment \ --rollout-type Normal \ --targets "resourceId=/subscriptions//resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=create" \ @@ -34,16 +34,16 @@ Update the rollout type and add a comment: ```bash az changesafety changestate update \ -g MyResourceGroup \ - -n webapp-rollout-01 \ + -n webAppRollout01 \ --rollout-type Emergency \ --comments "Escalated due to customer impact" ``` Delete a ChangeState: ```bash -az changesafety changestate delete -g MyResourceGroup -n webapp-rollout-01 --yes +az changesafety changestate delete -g MyResourceGroup -n webAppRollout01 --yes ``` ## Additional Information - View command documentation: `az changesafety changestate -h` -- Remove the extension when no longer needed: `az extension remove --name azure-changesafety` \ No newline at end of file +- Remove the extension when no longer needed: `az extension remove --name azure-changesafety` diff --git a/src/azure-changesafety/azext_changesafety/_help.py b/src/azure-changesafety/azext_changesafety/_help.py index cae17e48d1c..c6174626b2d 100644 --- a/src/azure-changesafety/azext_changesafety/_help.py +++ b/src/azure-changesafety/azext_changesafety/_help.py @@ -24,7 +24,7 @@ type: command short-summary: Create a ChangeState resource. long-summary: > - Provide at least one target definition to describe which resources or operations the change + Provide at least one target definition to describe which resources or operations the ChangeState will affect. Targets are expressed as comma or semicolon separated key=value pairs such as resourceId=RESOURCE_ID,operation=DELETE. The command is also available through the alias `az change-safety change-state`. @@ -42,15 +42,16 @@ - name: --links short-summary: Add supporting links by repeating --links name=NAME uri=URL [description=TEXT]. examples: - - name: Create with stage map reference and status link + - name: Create with StageMap reference and status link text: |- - az changesafety changestate create -g MyResourceGroup -n deploy-002 --change-type ManualTouch --rollout-type Normal --stage-map "{resource-id:/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.ChangeSafety/stageMaps/rollout-stage-map}" --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH" --links "[{name:status,uri:'https://contoso.com/change/rollout-002'}]" + az changesafety changestate create -g MyResourceGroup -n changestate002 --change-type ManualTouch --rollout-type Normal --stage-map "{resource-id:/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.ChangeSafety/stageMaps/rolloutStageMap}" --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH" --links "[{name:status,uri:'https://contoso.com/change/rollout-002'}]" + az changesafety changestate delete -g MyResourceGroup -n changestate002 --yes - name: Create a change state for a VM rollout text: |- - az changesafety changestate create -g MyResourceGroup -n deploy-001 --change-type AppDeployment --rollout-type Normal --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PUT" + az changesafety changestate create -g MyResourceGroup -n changestate001 --change-type AppDeployment --rollout-type Normal --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PUT" - name: Create with staging rollout configuration text: |- - az changesafety changestate create -g MyResourceGroup -n ops-change-01 --rollout-type Hotfix --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST" + az changesafety changestate create -g MyResourceGroup -n opsChange01 --rollout-type Hotfix --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST" """ helps['changesafety changestate update'] = """ @@ -58,7 +59,7 @@ short-summary: Update an existing ChangeState resource. long-summary: > Use this command to modify descriptive metadata, rollout settings, or replace targets for an - existing change. When you pass --targets, the supplied definitions overwrite the previous set. + existing ChangeState. When you pass --targets, the supplied definitions overwrite the previous set. This command is also available through the alias `az change-safety change-state`. parameters: - name: --targets @@ -74,13 +75,13 @@ examples: - name: Adjust rollout type and add a comment text: |- - az changesafety changestate update -g MyResourceGroup -n deploy-001 --rollout-type Emergency --comments "Escalated to emergency rollout" + az changesafety changestate update -g MyResourceGroup -n changestate001 --rollout-type Emergency --comments "Escalated to emergency rollout" - name: Update scheduling window text: |- - az changesafety changestate update -g MyResourceGroup -n deploy-001 --anticipated-start-time "2024-09-01T08:00:00Z" --anticipated-end-time "2024-09-01T12:00:00Z" + az changesafety changestate update -g MyResourceGroup -n changestate001 --anticipated-start-time "2024-09-01T08:00:00Z" --anticipated-end-time "2024-09-01T12:00:00Z" - name: Replace the target definition text: |- - az changesafety changestate update -g MyResourceGroup -n deploy-001 --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/myServer,operation=PATCH" + az changesafety changestate update -g MyResourceGroup -n changestate001 --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/myServer,operation=PATCH" """ helps['changesafety changestate delete'] = """ @@ -89,7 +90,7 @@ examples: - name: Delete a change state without confirmation text: |- - az changesafety changestate delete -g MyResourceGroup -n deploy-001 --yes + az changesafety changestate delete -g MyResourceGroup -n changestate001 --yes """ helps['changesafety changestate show'] = """ @@ -98,5 +99,5 @@ examples: - name: Show a change state text: |- - az changesafety changestate show -g MyResourceGroup -n deploy-001 + az changesafety changestate show -g MyResourceGroup -n changestate001 """ diff --git a/src/azure-changesafety/azext_changesafety/custom.py b/src/azure-changesafety/azext_changesafety/custom.py index 25f13f592e4..621aa9bbb1e 100644 --- a/src/azure-changesafety/azext_changesafety/custom.py +++ b/src/azure-changesafety/azext_changesafety/custom.py @@ -484,3 +484,78 @@ def on_200(self, session): class ChangeStateDelete(_ChangeStateDelete): pass + + +ChangeStateCreate.AZ_HELP["examples"] = [ + { + "name": "Create with StageMap reference and status link", + "text": ( + "az changesafety changestate create -g MyResourceGroup -n changestate002 " + "--change-type ManualTouch --rollout-type Normal " + "--stage-map \"{resource-id:/subscriptions/00000000-0000-0000-0000-000000000000/" + "resourceGroups/MyResourceGroup/providers/Microsoft.ChangeSafety/stageMaps/rolloutStageMap}\" " + "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" + "resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH\" " + "--links \"[{name:status,uri:'https://contoso.com/change/rollout-002'}]\"\n" + "az changesafety changestate delete -g MyResourceGroup -n changestate002 --yes" + ), + }, + { + "name": "Create a change state for a VM rollout", + "text": ( + "az changesafety changestate create -g MyResourceGroup -n changestate001 " + "--change-type AppDeployment --rollout-type Normal " + "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" + "resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PUT\"" + ), + }, + { + "name": "Create with staging rollout configuration", + "text": ( + "az changesafety changestate create -g MyResourceGroup -n opsChange01 " + "--rollout-type Hotfix " + "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" + "resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST\"" + ), + }, +] + +ChangeStateUpdate.AZ_HELP["examples"] = [ + { + "name": "Adjust rollout type and add a comment", + "text": ( + "az changesafety changestate update -g MyResourceGroup -n changestate001 " + "--rollout-type Emergency --comments \"Escalated to emergency rollout\"" + ), + }, + { + "name": "Update scheduling window", + "text": ( + "az changesafety changestate update -g MyResourceGroup -n changestate001 " + "--anticipated-start-time \"2024-09-01T08:00:00Z\" " + "--anticipated-end-time \"2024-09-01T12:00:00Z\"" + ), + }, + { + "name": "Replace the target definition", + "text": ( + "az changesafety changestate update -g MyResourceGroup -n changestate001 " + "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" + "resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/myServer,operation=PATCH\"" + ), + }, +] + +ChangeStateDelete.AZ_HELP["examples"] = [ + { + "name": "Delete a change state without confirmation", + "text": "az changesafety changestate delete -g MyResourceGroup -n changestate001 --yes", + }, +] + +ChangeStateShow.AZ_HELP["examples"] = [ + { + "name": "Show a change state", + "text": "az changesafety changestate show -g MyResourceGroup -n changestate001", + }, +] diff --git a/src/azure-changesafety/azext_changesafety/tests/latest/recordings/test_change_state_cli_scenario.yaml b/src/azure-changesafety/azext_changesafety/tests/latest/recordings/test_change_state_cli_scenario.yaml new file mode 100644 index 00000000000..f218d0e082d --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/tests/latest/recordings/test_change_state_cli_scenario.yaml @@ -0,0 +1,106 @@ +interactions: +- request: + body: '{"location": "eastus", "properties": {"changeType": "ManualTouch", "rolloutType": "Normal", "changeDefinition": {"kind": "Targets", "name": "changestate002", "details": {"targets": [{"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.Compute/virtualMachines/myVm", "httpMethod": "PATCH"}]}}}, "tags": null}' + headers: + Accept: + - application/json + CommandName: + - changesafety changestate create + Connection: + - keep-alive + Content-Type: + - application/json + ParameterSetName: + - -g -n --change-type --rollout-type --targets --stage-map --links --comments + method: PUT + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002", "name": "changestate002", "type": "Microsoft.ChangeSafety/changeStates", "location": "eastus", "properties": {"changeType": "ManualTouch", "rolloutType": "Normal", "comments": "Initial deployment", "changeDefinition": {"kind": "Targets", "name": "changestate002", "details": {"targets": [{"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.Compute/virtualMachines/myVm", "httpMethod": "PATCH"}]}}}}' + headers: + Content-Type: + - application/json + Date: + - Mon, 03 Nov 2025 18:00:00 GMT + status: + code: 200 + message: OK + url: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview +- request: + body: '{"properties": {"rolloutType": "Emergency", "comments": "Escalated rollout"}}' + headers: + Accept: + - application/json + CommandName: + - changesafety changestate update + Connection: + - keep-alive + Content-Type: + - application/json + ParameterSetName: + - -g -n --rollout-type --comments + method: PATCH + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002", "name": "changestate002", "type": "Microsoft.ChangeSafety/changeStates", "location": "eastus", "properties": {"changeType": "ManualTouch", "rolloutType": "Emergency", "comments": "Escalated rollout", "changeDefinition": {"kind": "Targets", "name": "changestate002", "details": {"targets": [{"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.Compute/virtualMachines/myVm", "httpMethod": "PATCH"}]}}}}' + headers: + Content-Type: + - application/json + Date: + - Mon, 03 Nov 2025 18:01:00 GMT + status: + code: 200 + message: OK + url: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview +- request: + body: '' + headers: + Accept: + - application/json + CommandName: + - changesafety changestate show + Connection: + - keep-alive + ParameterSetName: + - -g -n + method: GET + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview + response: + body: + string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002", "name": "changestate002", "type": "Microsoft.ChangeSafety/changeStates", "location": "eastus", "properties": {"changeType": "ManualTouch", "rolloutType": "Emergency", "comments": "Escalated rollout", "changeDefinition": {"kind": "Targets", "name": "changestate002", "details": {"targets": [{"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.Compute/virtualMachines/myVm", "httpMethod": "PATCH"}]}}}}' + headers: + Content-Type: + - application/json + Date: + - Mon, 03 Nov 2025 18:02:00 GMT + status: + code: 200 + message: OK + url: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview +- request: + body: '' + headers: + Accept: + - application/json + CommandName: + - changesafety changestate delete + Connection: + - keep-alive + ParameterSetName: + - -g -n --yes + method: DELETE + uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview + response: + body: + string: '' + headers: + Content-Type: + - application/json + Date: + - Mon, 03 Nov 2025 18:03:00 GMT + status: + code: 204 + message: No Content + url: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview +version: 1 diff --git a/src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py b/src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py index 91909a8db71..0fbb2d5d35c 100644 --- a/src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py +++ b/src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py @@ -5,26 +5,209 @@ # Code generated by aaz-dev-tools # -------------------------------------------------------------------------------------------- +import copy +import sys +import types from types import SimpleNamespace +from unittest import mock -import pytest -from azure.cli.testsdk import * +from azure.cli.testsdk import * # pylint: disable=wildcard-import,unused-wildcard-import from azext_changesafety.custom import ( ChangeStateCreate, + ChangeStateDelete, + ChangeStateShow, ChangeStateUpdate, _inject_change_definition_into_content, _inject_targets_into_result, _normalize_targets_arg, ) -from azure.cli.core.azclierror import InvalidArgumentValueError +from azure.cli.core.aaz import AAZAnyType, has_value from azure.cli.core.aaz._arg_action import AAZArgActionOperations, _ELEMENT_APPEND_KEY class ChangeStateScenario(ScenarioTest): + FAKE_SUBSCRIPTION_ID = "00000000-0000-0000-0000-000000000000" + _SCENARIO_STATE = {} + + class _DummyPoller: # pylint: disable=too-few-public-methods + def result(self, timeout=None): # pylint: disable=unused-argument + return None + + def wait(self, timeout=None): # pylint: disable=unused-argument + return None + + def done(self): + return True + + def add_done_callback(self, func): + if func: + func(self) + + @staticmethod + def _dummy_ctx_with_change_definition(payload): + dummy = SimpleNamespace() + dummy.to_serialized_data = lambda: payload + return SimpleNamespace(vars=SimpleNamespace(change_definition=dummy)) + + @classmethod + def _ensure_msrestazure_stub(cls): + if 'msrestazure' in sys.modules: + return + + msrestazure = types.ModuleType('msrestazure') + azure_operation = types.ModuleType('msrestazure.azure_operation') + + class AzureOperationPoller: # pylint: disable=too-few-public-methods + def _delay(self, *args, **kwargs): # pylint: disable=unused-argument + return + + azure_operation.AzureOperationPoller = AzureOperationPoller + arm_polling = types.ModuleType('msrestazure.polling.arm_polling') + + class ARMPolling: # pylint: disable=too-few-public-methods + def _delay(self, *args, **kwargs): # pylint: disable=unused-argument + return + + arm_polling.ARMPolling = ARMPolling + polling = types.ModuleType('msrestazure.polling') + polling.arm_polling = arm_polling + + msrestazure.azure_operation = azure_operation + msrestazure.polling = polling + + sys.modules['msrestazure'] = msrestazure + sys.modules['msrestazure.azure_operation'] = azure_operation + sys.modules['msrestazure.polling'] = polling + sys.modules['msrestazure.polling.arm_polling'] = arm_polling + + @staticmethod + def _get_arg_value(cmd, arg_name, default=None): + arg = getattr(cmd.ctx.args, arg_name, None) + if arg is None or not has_value(arg): + return default + return arg.to_serialized_data() + + @staticmethod + def _build_mock_instance(name, resource_group, subscription_id, change_type, rollout_type, targets, comments=None): + return { + "id": f"/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.ChangeSafety/changeStates/{name}", + "name": name, + "type": "Microsoft.ChangeSafety/changeStates", + "location": "eastus", + "properties": { + "changeType": change_type, + "rolloutType": rollout_type, + "comments": comments, + "changeDefinition": { + "kind": "Targets", + "name": name, + "details": { + "targets": targets or [] + } + } + } + } + + @staticmethod + def _mock_create_execute(cmd): + cls = ChangeStateScenario + cmd.pre_operations() + name = cls._get_arg_value(cmd, "change_state_name", "mock-change") + resource_group = cls._get_arg_value(cmd, "resource_group", "mock-rg") + subscription_id = cmd.ctx.subscription_id or cls.FAKE_SUBSCRIPTION_ID + change_type = cls._get_arg_value(cmd, "change_type", "ManualTouch") + rollout_type = cls._get_arg_value(cmd, "rollout_type", "Normal") + comments = cls._get_arg_value(cmd, "comments") + targets = copy.deepcopy(cmd._parsed_targets or []) + instance = cls._build_mock_instance( + name=name, + resource_group=resource_group, + subscription_id=subscription_id, + change_type=change_type, + rollout_type=rollout_type, + targets=targets, + comments=comments, + ) + cls._SCENARIO_STATE["instance"] = copy.deepcopy(instance) + cmd.ctx.set_var("instance", copy.deepcopy(instance), schema_builder=lambda: AAZAnyType()) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_update_execute(cmd): + cls = ChangeStateScenario + cmd._raw_targets = [token for token in (cmd._raw_targets or []) if token and token != 'Undefined'] # pylint: disable=protected-access + cmd.pre_operations() + current = copy.deepcopy(cls._SCENARIO_STATE.get("instance")) + if current is None: + name = cls._get_arg_value(cmd, "change_state_name", "mock-change") + resource_group = cls._get_arg_value(cmd, "resource_group", "mock-rg") + subscription_id = cmd.ctx.subscription_id or cls.FAKE_SUBSCRIPTION_ID + current = cls._build_mock_instance( + name=name, + resource_group=resource_group, + subscription_id=subscription_id, + change_type="ManualTouch", + rollout_type="Normal", + targets=[], + ) + new_change_type = cls._get_arg_value(cmd, "change_type") + new_rollout = cls._get_arg_value(cmd, "rollout_type") + new_comments = cls._get_arg_value(cmd, "comments") + if new_change_type: + current["properties"]["changeType"] = new_change_type + if new_rollout: + current["properties"]["rolloutType"] = new_rollout + if new_comments is not None: + current["properties"]["comments"] = new_comments + if cmd._parsed_targets: # pylint: disable=protected-access + current["properties"]["changeDefinition"]["details"]["targets"] = copy.deepcopy(cmd._parsed_targets) # pylint: disable=protected-access + cls._SCENARIO_STATE["instance"] = copy.deepcopy(current) + cmd.ctx.set_var("instance", copy.deepcopy(current), schema_builder=lambda: AAZAnyType()) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_show_execute(cmd): + cls = ChangeStateScenario + cmd.pre_operations() + instance = copy.deepcopy(cls._SCENARIO_STATE.get("instance")) + cmd.ctx.set_var("instance", instance, schema_builder=lambda: AAZAnyType()) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_delete_execute(cmd): + cls = ChangeStateScenario + cmd.pre_operations() + cls._SCENARIO_STATE.pop("instance", None) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_build_lro_poller(cmd, executor, extract_result): # pylint: disable=unused-argument + executor() + return ChangeStateScenario._DummyPoller() # pylint: disable=protected-access + + def setUp(self): + type(self)._ensure_msrestazure_stub() + super().setUp() + type(self)._SCENARIO_STATE.clear() + self._patchers = [ + mock.patch('azext_changesafety.custom.ChangeStateCreate._execute_operations', new=type(self)._mock_create_execute), + mock.patch('azext_changesafety.custom.ChangeStateUpdate._execute_operations', new=type(self)._mock_update_execute), + mock.patch('azext_changesafety.custom.ChangeStateShow._execute_operations', new=type(self)._mock_show_execute), + mock.patch('azext_changesafety.custom.ChangeStateDelete._execute_operations', new=type(self)._mock_delete_execute), + mock.patch('azext_changesafety.custom.ChangeStateDelete.build_lro_poller', new=type(self)._mock_build_lro_poller), + ] + for patcher in self._patchers: + patcher.start() + self.addCleanup(patcher.stop) + def test_normalize_targets_from_operations(self): operations = AAZArgActionOperations.__new__(AAZArgActionOperations) - operations._ops = [ + operations._ops = [ # pylint: disable=protected-access ((_ELEMENT_APPEND_KEY,), "env=prod"), ((0, "resourceId"), "/subscriptions/000/resourceGroups/rg/providers/Microsoft.Web/sites/app"), ((0, "operation"), "delete"), @@ -58,7 +241,7 @@ def test_normalize_targets_with_none_returns_empty(self): assert _normalize_targets_arg(None) == [] def test_inject_change_definition_into_content_adds_properties(self): - ctx = _dummy_ctx_with_change_definition({"details": {"targets": []}}) + ctx = self._dummy_ctx_with_change_definition({"details": {"targets": []}}) content = {"properties": {"existing": "value"}} result = _inject_change_definition_into_content(content, ctx) @@ -67,7 +250,7 @@ def test_inject_change_definition_into_content_adds_properties(self): assert result["properties"]["changeDefinition"] == {"details": {"targets": []}} def test_inject_change_definition_with_empty_payload_noop(self): - ctx = _dummy_ctx_with_change_definition({}) + ctx = self._dummy_ctx_with_change_definition({}) original = {"properties": {"foo": "bar"}} result = _inject_change_definition_into_content(original.copy(), ctx) @@ -91,8 +274,53 @@ def test_inject_targets_does_not_override_existing(self): assert data["changeDefinition"]["details"]["targets"] == existing + def test_change_state_cli_scenario(self): + resource_group = "rgChangeSafetyScenario" + change_state_name = self.create_random_name('chg', 12) + target_resource = ( + f"/subscriptions/{self.FAKE_SUBSCRIPTION_ID}/resourceGroups/{resource_group}/" + "providers/Microsoft.Compute/virtualMachines/myVm" + ) + self.kwargs.update({ + "rg": resource_group, + "name": change_state_name, + "change_type": "ManualTouch", + "rollout_type": "Normal", + "updated_rollout": "Emergency", + "targets": f"resourceId={target_resource},operation=PATCH", + }) + + create_checks = [ + JMESPathCheck('name', change_state_name), + JMESPathCheck('properties.changeType', 'ManualTouch'), + JMESPathCheck('properties.rolloutType', 'Normal'), + JMESPathCheck('properties.changeDefinition.details.targets[0].resourceId', target_resource), + JMESPathCheck('properties.changeDefinition.details.targets[0].httpMethod', 'PATCH'), + ] + self.cmd( + 'az changesafety changestate create -g {rg} -n {name} ' + '--change-type {change_type} --rollout-type {rollout_type} ' + '--targets "{targets}" --comments "Initial deployment"', + checks=create_checks, + ) + + update_checks = [ + JMESPathCheck('properties.rolloutType', 'Emergency'), + JMESPathCheck('properties.comments', 'Escalated rollout'), + ] + self.cmd( + 'az changesafety changestate update -g {rg} -n {name} ' + '--rollout-type {updated_rollout} --comments "Escalated rollout"', + checks=update_checks, + ) + + self.cmd( + 'az changesafety changestate show -g {rg} -n {name}', + checks=[ + JMESPathCheck('properties.comments', 'Escalated rollout'), + JMESPathCheck('properties.changeDefinition.details.targets[0].resourceId', target_resource), + ], + ) -def _dummy_ctx_with_change_definition(payload): - dummy = SimpleNamespace() - dummy.to_serialized_data = lambda: payload - return SimpleNamespace(vars=SimpleNamespace(change_definition=dummy)) + self.cmd('az changesafety changestate delete -g {rg} -n {name} -y') + self.assertNotIn("instance", type(self)._SCENARIO_STATE) From 15783690c08c61ad19f7cd598be0abd97f02cb3d Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Mon, 3 Nov 2025 23:53:21 -0800 Subject: [PATCH 14/25] Fix style --- .../azext_changesafety/custom.py | 158 ++++++++++-------- 1 file changed, 85 insertions(+), 73 deletions(-) diff --git a/src/azure-changesafety/azext_changesafety/custom.py b/src/azure-changesafety/azext_changesafety/custom.py index 621aa9bbb1e..320f61fe9a6 100644 --- a/src/azure-changesafety/azext_changesafety/custom.py +++ b/src/azure-changesafety/azext_changesafety/custom.py @@ -486,76 +486,88 @@ class ChangeStateDelete(_ChangeStateDelete): pass -ChangeStateCreate.AZ_HELP["examples"] = [ - { - "name": "Create with StageMap reference and status link", - "text": ( - "az changesafety changestate create -g MyResourceGroup -n changestate002 " - "--change-type ManualTouch --rollout-type Normal " - "--stage-map \"{resource-id:/subscriptions/00000000-0000-0000-0000-000000000000/" - "resourceGroups/MyResourceGroup/providers/Microsoft.ChangeSafety/stageMaps/rolloutStageMap}\" " - "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" - "resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH\" " - "--links \"[{name:status,uri:'https://contoso.com/change/rollout-002'}]\"\n" - "az changesafety changestate delete -g MyResourceGroup -n changestate002 --yes" - ), - }, - { - "name": "Create a change state for a VM rollout", - "text": ( - "az changesafety changestate create -g MyResourceGroup -n changestate001 " - "--change-type AppDeployment --rollout-type Normal " - "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" - "resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PUT\"" - ), - }, - { - "name": "Create with staging rollout configuration", - "text": ( - "az changesafety changestate create -g MyResourceGroup -n opsChange01 " - "--rollout-type Hotfix " - "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" - "resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST\"" - ), - }, -] - -ChangeStateUpdate.AZ_HELP["examples"] = [ - { - "name": "Adjust rollout type and add a comment", - "text": ( - "az changesafety changestate update -g MyResourceGroup -n changestate001 " - "--rollout-type Emergency --comments \"Escalated to emergency rollout\"" - ), - }, - { - "name": "Update scheduling window", - "text": ( - "az changesafety changestate update -g MyResourceGroup -n changestate001 " - "--anticipated-start-time \"2024-09-01T08:00:00Z\" " - "--anticipated-end-time \"2024-09-01T12:00:00Z\"" - ), - }, - { - "name": "Replace the target definition", - "text": ( - "az changesafety changestate update -g MyResourceGroup -n changestate001 " - "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" - "resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/myServer,operation=PATCH\"" - ), - }, -] - -ChangeStateDelete.AZ_HELP["examples"] = [ - { - "name": "Delete a change state without confirmation", - "text": "az changesafety changestate delete -g MyResourceGroup -n changestate001 --yes", - }, -] - -ChangeStateShow.AZ_HELP["examples"] = [ - { - "name": "Show a change state", - "text": "az changesafety changestate show -g MyResourceGroup -n changestate001", - }, -] +ChangeStateCreate.AZ_HELP = { + **ChangeStateCreate.AZ_HELP, + "examples": [ + { + "name": "Create with StageMap reference and status link", + "text": ( + "az changesafety changestate create -g MyResourceGroup -n changestate002 " + "--change-type ManualTouch --rollout-type Normal " + "--stage-map \"{resource-id:/subscriptions/00000000-0000-0000-0000-000000000000/" + "resourceGroups/MyResourceGroup/providers/Microsoft.ChangeSafety/stageMaps/rolloutStageMap}\" " + "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" + "resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH\" " + "--links \"[{name:status,uri:'https://contoso.com/change/rollout-002'}]\"\n" + "az changesafety changestate delete -g MyResourceGroup -n changestate002 --yes" + ), + }, + { + "name": "Create a change state for a VM rollout", + "text": ( + "az changesafety changestate create -g MyResourceGroup -n changestate001 " + "--change-type AppDeployment --rollout-type Normal " + "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" + "resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PUT\"" + ), + }, + { + "name": "Create with staging rollout configuration", + "text": ( + "az changesafety changestate create -g MyResourceGroup -n opsChange01 " + "--rollout-type Hotfix " + "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" + "resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST\"" + ), + }, + ], +} + +ChangeStateUpdate.AZ_HELP = { + **ChangeStateUpdate.AZ_HELP, + "examples": [ + { + "name": "Adjust rollout type and add a comment", + "text": ( + "az changesafety changestate update -g MyResourceGroup -n changestate001 " + "--rollout-type Emergency --comments \"Escalated to emergency rollout\"" + ), + }, + { + "name": "Update scheduling window", + "text": ( + "az changesafety changestate update -g MyResourceGroup -n changestate001 " + "--anticipated-start-time \"2024-09-01T08:00:00Z\" " + "--anticipated-end-time \"2024-09-01T12:00:00Z\"" + ), + }, + { + "name": "Replace the target definition", + "text": ( + "az changesafety changestate update -g MyResourceGroup -n changestate001 " + "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" + "resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/myServer,operation=PATCH\"" + ), + }, + ], +} + +ChangeStateDelete.AZ_HELP = { + **ChangeStateDelete.AZ_HELP, + "examples": [ + { + "name": "Delete a change state without confirmation", + "text": "az changesafety changestate delete -g MyResourceGroup -n changestate001 --yes", + }, + ], +} + +ChangeStateShow.AZ_HELP = { + **ChangeStateShow.AZ_HELP, + "examples": [ + { + "name": "Show a change state", + "text": "az changesafety changestate show -g MyResourceGroup -n changestate001", + }, + ], +} From 6c8d92c3a5d54b7953df9229e148807abd99eb26 Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Tue, 4 Nov 2025 09:22:53 -0800 Subject: [PATCH 15/25] Add service name --- src/service_name.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/service_name.json b/src/service_name.json index 24283c09b6f..e489cc04a02 100644 --- a/src/service_name.json +++ b/src/service_name.json @@ -978,5 +978,10 @@ "Command": "az migrate", "AzureServiceName": "Azure Migrate", "URL": "https://learn.microsoft.com/azure/migrate" + }, + { + "Command": "az changesafety", + "AzureServiceName": "ChangeSafety", + "URL": "" } ] From ee776c6dab310937996566be9bbe2eac6dc16b6e Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Mon, 24 Nov 2025 17:52:08 -0800 Subject: [PATCH 16/25] Add stageprogression and stagemap --- .../azext_changesafety/_help.py | 20 +- .../change_safety/stage_map/__cmd_group.py | 23 + .../change_safety/stage_map/__init__.py | 15 + .../latest/change_safety/stage_map/_create.py | 880 +++++++++++++++++ .../latest/change_safety/stage_map/_delete.py | 205 ++++ .../latest/change_safety/stage_map/_show.py | 522 ++++++++++ .../latest/change_safety/stage_map/_update.py | 903 ++++++++++++++++++ .../stage_progression/__cmd_group.py | 23 + .../stage_progression/__init__.py | 15 + .../stage_progression/_create.py | 581 +++++++++++ .../stage_progression/_delete.py | 222 +++++ .../change_safety/stage_progression/_show.py | 423 ++++++++ .../stage_progression/_update.py | 717 ++++++++++++++ .../azext_changesafety/custom.py | 186 +++- .../tests/latest/test_change_state.py | 160 +++- 15 files changed, 4884 insertions(+), 11 deletions(-) create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/__cmd_group.py create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/__init__.py create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_create.py create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_delete.py create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_show.py create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_update.py create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/__cmd_group.py create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/__init__.py create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py diff --git a/src/azure-changesafety/azext_changesafety/_help.py b/src/azure-changesafety/azext_changesafety/_help.py index c6174626b2d..1ffb1040d33 100644 --- a/src/azure-changesafety/azext_changesafety/_help.py +++ b/src/azure-changesafety/azext_changesafety/_help.py @@ -27,16 +27,23 @@ Provide at least one target definition to describe which resources or operations the ChangeState will affect. Targets are expressed as comma or semicolon separated key=value pairs such as resourceId=RESOURCE_ID,operation=DELETE. The command is also available through the alias - `az change-safety change-state`. + `az change-safety change-state`. If you omit scheduling flags, the anticipated start time defaults + to now and the anticipated end time defaults to eight hours later (UTC). parameters: - name: --targets short-summary: > One or more target definitions expressed as key=value pairs (for example resourceId=RESOURCE_ID,operation=CREATE,resourceType=Microsoft.Compute/virtualMachines). + - name: --anticipated-start-time + short-summary: Expected start time in ISO 8601 format. Defaults to current UTC time when omitted. + - name: --anticipated-end-time + short-summary: Expected completion time in ISO 8601 format. Defaults to eight hours after the anticipated start time when omitted. - name: --change-type short-summary: Classify the change such as AppDeployment, Config, ManualTouch, or PolicyDeployment. - name: --rollout-type short-summary: Specify the rollout urgency (Normal, Hotfix, or Emergency). + - name: --stage-map-name --stagemap-name + short-summary: StageMap name in the current subscription scope; the resource ID is built for you. - name: --stage-map short-summary: Reference an existing StageMap resource using resource-id=RESOURCE_ID and optional parameters key=value pairs. - name: --links @@ -52,6 +59,9 @@ - name: Create with staging rollout configuration text: |- az changesafety changestate create -g MyResourceGroup -n opsChange01 --rollout-type Hotfix --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST" + - name: Reference a StageMap by name + text: |- + az changesafety changestate create -g MyResourceGroup -n changestate003 --change-type ManualTouch --rollout-type Normal --stagemap-name rolloutStageMap --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=DELETE" """ helps['changesafety changestate update'] = """ @@ -66,12 +76,16 @@ short-summary: > Optional target definitions to replace the existing list. Provide key=value pairs such as resourceId=RESOURCE_ID,operation=DELETE. + - name: --stage-map-name --stagemap-name + short-summary: StageMap name in the current subscription scope; the resource ID is built for you. + - name: --stage-map + short-summary: Reference an existing StageMap resource using resource-id=RESOURCE_ID and optional parameters key=value pairs. - name: --comments short-summary: Provide notes about the latest update to the change state. - name: --anticipated-start-time - short-summary: Update the expected start time in ISO 8601 format. + short-summary: Update the expected start time in ISO 8601 format. If omitted, the current value is preserved. - name: --anticipated-end-time - short-summary: Update the expected completion time in ISO 8601 format. + short-summary: Update the expected completion time in ISO 8601 format. If omitted, the current value is preserved. examples: - name: Adjust rollout type and add a comment text: |- diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/__cmd_group.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/__cmd_group.py new file mode 100644 index 00000000000..0af235ba459 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/__cmd_group.py @@ -0,0 +1,23 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "changesafety stagemap", +) +class __CMDGroup(AAZCommandGroup): + """Manage Stage Map + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/__init__.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/__init__.py new file mode 100644 index 00000000000..a3db3e36481 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/__init__.py @@ -0,0 +1,15 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * +from ._create import * +from ._delete import * +from ._show import * +from ._update import * diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_create.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_create.py new file mode 100644 index 00000000000..a0621b795ea --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_create.py @@ -0,0 +1,880 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "changesafety stagemap create", +) +class Create(AAZCommand): + """Create a StageMap + """ + + _aaz_info = { + "version": "2025-09-01-preview", + "resources": [ + ["mgmt-plane", "/managementgroups/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.management_group_name = AAZStrArg( + options=["--management-group-name"], + help="The name of the management group. The name is case insensitive.", + fmt=AAZStrArgFormat( + max_length=90, + min_length=1, + ), + ) + _args_schema.stage_map_name = AAZStrArg( + options=["--stage-map-name"], + help="The name of the StageMap", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.parameters = AAZDictArg( + options=["--parameters"], + arg_group="Properties", + help="StageMap parameters schema for each stage.", + ) + _args_schema.stages = AAZListArg( + options=["--stages"], + arg_group="Properties", + help="Array of stages objects.", + ) + + parameters = cls._args_schema.parameters + parameters.Element = AAZObjectArg() + + _element = cls._args_schema.parameters.Element + _element.array = AAZObjectArg( + options=["array"], + ) + _element.metadata = AAZDictArg( + options=["metadata"], + help="user-specified parameter metadata", + ) + _element.number = AAZObjectArg( + options=["number"], + ) + _element.object = AAZObjectArg( + options=["object"], + ) + _element.string = AAZObjectArg( + options=["string"], + ) + + array = cls._args_schema.parameters.Element.array + array.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + ) + array.default_value = AAZListArg( + options=["default-value"], + help="Default value for the parameter.", + ) + + allowed_values = cls._args_schema.parameters.Element.array.allowed_values + allowed_values.Element = AAZAnyTypeArg() + + default_value = cls._args_schema.parameters.Element.array.default_value + default_value.Element = AAZAnyTypeArg() + + metadata = cls._args_schema.parameters.Element.metadata + metadata.Element = AAZStrArg() + + number = cls._args_schema.parameters.Element.number + number.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + ) + number.default_value = AAZIntArg( + options=["default-value"], + help="Default value for the parameter.", + ) + + allowed_values = cls._args_schema.parameters.Element.number.allowed_values + allowed_values.Element = AAZIntArg() + + object = cls._args_schema.parameters.Element.object + object.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + ) + object.default_value = AAZObjectArg( + options=["default-value"], + help="Default value for the parameter.", + blank={}, + ) + + allowed_values = cls._args_schema.parameters.Element.object.allowed_values + allowed_values.Element = AAZDictArg() + + _element = cls._args_schema.parameters.Element.object.allowed_values.Element + _element.Element = AAZAnyTypeArg() + + string = cls._args_schema.parameters.Element.string + string.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + ) + string.default_value = AAZStrArg( + options=["default-value"], + help="Default value for the parameter.", + ) + + allowed_values = cls._args_schema.parameters.Element.string.allowed_values + allowed_values.Element = AAZStrArg() + + stages = cls._args_schema.stages + stages.Element = AAZObjectArg() + + _element = cls._args_schema.stages.Element + _element.name = AAZStrArg( + options=["name"], + help="Name of the individual stage.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + _element.nested_stage_map = AAZObjectArg( + options=["nested-stage-map"], + help="Nested stage map details.", + ) + _element.sequence = AAZIntArg( + options=["sequence"], + help="Positive integer defining the orchestration order of the stages.", + required=True, + fmt=AAZIntArgFormat( + minimum=1, + ), + ) + _element.stage_variables = AAZDictArg( + options=["stage-variables"], + help="Variables to apply on the change of that stage. Key value pairs supporting any JSON values.", + ) + + nested_stage_map = cls._args_schema.stages.Element.nested_stage_map + nested_stage_map.parameters = AAZDictArg( + options=["parameters"], + help="Key value pairs of parameter names & their values for the stageMap referenced by the resourceId field.", + ) + nested_stage_map.resource_id = AAZStrArg( + options=["resource-id"], + help="ARM resource ID for the nested stagemap resource.", + ) + + parameters = cls._args_schema.stages.Element.nested_stage_map.parameters + parameters.Element = AAZAnyTypeArg() + + stage_variables = cls._args_schema.stages.Element.stage_variables + stage_variables.Element = AAZAnyTypeArg() + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.args.management_group_name) and has_value(self.ctx.args.stage_map_name) + condition_1 = has_value(self.ctx.args.stage_map_name) and has_value(self.ctx.subscription_id) + if condition_0: + self.StageMapsCreateOrUpdateAtManagementGroupLevel(ctx=self.ctx)() + if condition_1: + self.StageMapsCreateOrUpdateAtSubscriptionLevel(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class StageMapsCreateOrUpdateAtManagementGroupLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200, 201]: + return self.on_200_201(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/managementGroups/{managementGroupName}/providers/Microsoft.ChangeSafety/stageMaps/{stageMapName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "managementGroupName", self.ctx.args.management_group_name, + required=True, + ), + **self.serialize_url_param( + "stageMapName", self.ctx.args.stage_map_name, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + typ=AAZObjectType, + typ_kwargs={"flags": {"required": True, "client_flatten": True}} + ) + _builder.set_prop("properties", AAZObjectType) + + properties = _builder.get(".properties") + if properties is not None: + properties.set_prop("parameters", AAZDictType, ".parameters") + properties.set_prop("stages", AAZListType, ".stages", typ_kwargs={"flags": {"required": True}}) + + parameters = _builder.get(".properties.parameters") + if parameters is not None: + parameters.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.parameters{}") + if _elements is not None: + _elements.set_prop("metadata", AAZDictType, ".metadata") + _elements.set_const("type", "array", AAZStrType, ".array", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "number", AAZStrType, ".number", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "object", AAZStrType, ".object", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "string", AAZStrType, ".string", typ_kwargs={"flags": {"required": True}}) + _elements.discriminate_by("type", "array") + _elements.discriminate_by("type", "number") + _elements.discriminate_by("type", "object") + _elements.discriminate_by("type", "string") + + metadata = _builder.get(".properties.parameters{}.metadata") + if metadata is not None: + metadata.set_elements(AAZStrType, ".") + + disc_array = _builder.get(".properties.parameters{}{type:array}") + if disc_array is not None: + disc_array.set_prop("allowedValues", AAZListType, ".array.allowed_values") + disc_array.set_prop("defaultValue", AAZListType, ".array.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:array}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZAnyType, ".") + + default_value = _builder.get(".properties.parameters{}{type:array}.defaultValue") + if default_value is not None: + default_value.set_elements(AAZAnyType, ".") + + disc_number = _builder.get(".properties.parameters{}{type:number}") + if disc_number is not None: + disc_number.set_prop("allowedValues", AAZListType, ".number.allowed_values") + disc_number.set_prop("defaultValue", AAZIntType, ".number.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:number}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZIntType, ".") + + disc_object = _builder.get(".properties.parameters{}{type:object}") + if disc_object is not None: + disc_object.set_prop("allowedValues", AAZListType, ".object.allowed_values") + disc_object.set_prop("defaultValue", AAZObjectType, ".object.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:object}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZDictType, ".") + + _elements = _builder.get(".properties.parameters{}{type:object}.allowedValues[]") + if _elements is not None: + _elements.set_elements(AAZAnyType, ".") + + disc_string = _builder.get(".properties.parameters{}{type:string}") + if disc_string is not None: + disc_string.set_prop("allowedValues", AAZListType, ".string.allowed_values") + disc_string.set_prop("defaultValue", AAZStrType, ".string.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:string}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZStrType, ".") + + stages = _builder.get(".properties.stages") + if stages is not None: + stages.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.stages[]") + if _elements is not None: + _elements.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}}) + _elements.set_prop("nestedStageMap", AAZObjectType, ".nested_stage_map") + _elements.set_prop("sequence", AAZIntType, ".sequence", typ_kwargs={"flags": {"required": True}}) + _elements.set_prop("stageVariables", AAZDictType, ".stage_variables") + + nested_stage_map = _builder.get(".properties.stages[].nestedStageMap") + if nested_stage_map is not None: + nested_stage_map.set_prop("parameters", AAZDictType, ".parameters") + nested_stage_map.set_prop("resourceId", AAZStrType, ".resource_id") + + parameters = _builder.get(".properties.stages[].nestedStageMap.parameters") + if parameters is not None: + parameters.set_elements(AAZAnyType, ".") + + stage_variables = _builder.get(".properties.stages[].stageVariables") + if stage_variables is not None: + stage_variables.set_elements(AAZAnyType, ".") + + return self.serialize_content(_content_value) + + def on_200_201(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200_201 + ) + + _schema_on_200_201 = None + + @classmethod + def _build_schema_on_200_201(cls): + if cls._schema_on_200_201 is not None: + return cls._schema_on_200_201 + + cls._schema_on_200_201 = AAZObjectType() + + _schema_on_200_201 = cls._schema_on_200_201 + _schema_on_200_201.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200_201.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200_201.properties = AAZObjectType() + _schema_on_200_201.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200_201.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200_201.properties + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.stages = AAZListType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200_201.properties.parameters + parameters.Element = AAZObjectType() + + _element = cls._schema_on_200_201.properties.parameters.Element + _element.metadata = AAZDictType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + metadata = cls._schema_on_200_201.properties.parameters.Element.metadata + metadata.Element = AAZStrType() + + disc_array = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "array") + disc_array.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_array.default_value = AAZListType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values.Element = AAZAnyType() + + default_value = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value.Element = AAZAnyType() + + disc_number = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "number") + disc_number.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_number.default_value = AAZIntType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values.Element = AAZIntType() + + disc_object = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "object") + disc_object.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_object.default_value = AAZObjectType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values.Element = AAZDictType() + + _element = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element.Element = AAZAnyType() + + disc_string = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "string") + disc_string.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_string.default_value = AAZStrType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values.Element = AAZStrType() + + stages = cls._schema_on_200_201.properties.stages + stages.Element = AAZObjectType() + + _element = cls._schema_on_200_201.properties.stages.Element + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.nested_stage_map = AAZObjectType( + serialized_name="nestedStageMap", + ) + _element.sequence = AAZIntType( + flags={"required": True}, + ) + _element.stage_variables = AAZDictType( + serialized_name="stageVariables", + ) + + nested_stage_map = cls._schema_on_200_201.properties.stages.Element.nested_stage_map + nested_stage_map.parameters = AAZDictType() + nested_stage_map.resource_id = AAZStrType( + serialized_name="resourceId", + ) + + parameters = cls._schema_on_200_201.properties.stages.Element.nested_stage_map.parameters + parameters.Element = AAZAnyType() + + stage_variables = cls._schema_on_200_201.properties.stages.Element.stage_variables + stage_variables.Element = AAZAnyType() + + system_data = cls._schema_on_200_201.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200_201 + + class StageMapsCreateOrUpdateAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200, 201]: + return self.on_200_201(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/stageMaps/{stageMapName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "stageMapName", self.ctx.args.stage_map_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + typ=AAZObjectType, + typ_kwargs={"flags": {"required": True, "client_flatten": True}} + ) + _builder.set_prop("properties", AAZObjectType) + + properties = _builder.get(".properties") + if properties is not None: + properties.set_prop("parameters", AAZDictType, ".parameters") + properties.set_prop("stages", AAZListType, ".stages", typ_kwargs={"flags": {"required": True}}) + + parameters = _builder.get(".properties.parameters") + if parameters is not None: + parameters.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.parameters{}") + if _elements is not None: + _elements.set_prop("metadata", AAZDictType, ".metadata") + _elements.set_const("type", "array", AAZStrType, ".array", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "number", AAZStrType, ".number", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "object", AAZStrType, ".object", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "string", AAZStrType, ".string", typ_kwargs={"flags": {"required": True}}) + _elements.discriminate_by("type", "array") + _elements.discriminate_by("type", "number") + _elements.discriminate_by("type", "object") + _elements.discriminate_by("type", "string") + + metadata = _builder.get(".properties.parameters{}.metadata") + if metadata is not None: + metadata.set_elements(AAZStrType, ".") + + disc_array = _builder.get(".properties.parameters{}{type:array}") + if disc_array is not None: + disc_array.set_prop("allowedValues", AAZListType, ".array.allowed_values") + disc_array.set_prop("defaultValue", AAZListType, ".array.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:array}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZAnyType, ".") + + default_value = _builder.get(".properties.parameters{}{type:array}.defaultValue") + if default_value is not None: + default_value.set_elements(AAZAnyType, ".") + + disc_number = _builder.get(".properties.parameters{}{type:number}") + if disc_number is not None: + disc_number.set_prop("allowedValues", AAZListType, ".number.allowed_values") + disc_number.set_prop("defaultValue", AAZIntType, ".number.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:number}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZIntType, ".") + + disc_object = _builder.get(".properties.parameters{}{type:object}") + if disc_object is not None: + disc_object.set_prop("allowedValues", AAZListType, ".object.allowed_values") + disc_object.set_prop("defaultValue", AAZObjectType, ".object.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:object}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZDictType, ".") + + _elements = _builder.get(".properties.parameters{}{type:object}.allowedValues[]") + if _elements is not None: + _elements.set_elements(AAZAnyType, ".") + + disc_string = _builder.get(".properties.parameters{}{type:string}") + if disc_string is not None: + disc_string.set_prop("allowedValues", AAZListType, ".string.allowed_values") + disc_string.set_prop("defaultValue", AAZStrType, ".string.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:string}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZStrType, ".") + + stages = _builder.get(".properties.stages") + if stages is not None: + stages.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.stages[]") + if _elements is not None: + _elements.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}}) + _elements.set_prop("nestedStageMap", AAZObjectType, ".nested_stage_map") + _elements.set_prop("sequence", AAZIntType, ".sequence", typ_kwargs={"flags": {"required": True}}) + _elements.set_prop("stageVariables", AAZDictType, ".stage_variables") + + nested_stage_map = _builder.get(".properties.stages[].nestedStageMap") + if nested_stage_map is not None: + nested_stage_map.set_prop("parameters", AAZDictType, ".parameters") + nested_stage_map.set_prop("resourceId", AAZStrType, ".resource_id") + + parameters = _builder.get(".properties.stages[].nestedStageMap.parameters") + if parameters is not None: + parameters.set_elements(AAZAnyType, ".") + + stage_variables = _builder.get(".properties.stages[].stageVariables") + if stage_variables is not None: + stage_variables.set_elements(AAZAnyType, ".") + + return self.serialize_content(_content_value) + + def on_200_201(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200_201 + ) + + _schema_on_200_201 = None + + @classmethod + def _build_schema_on_200_201(cls): + if cls._schema_on_200_201 is not None: + return cls._schema_on_200_201 + + cls._schema_on_200_201 = AAZObjectType() + + _schema_on_200_201 = cls._schema_on_200_201 + _schema_on_200_201.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200_201.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200_201.properties = AAZObjectType() + _schema_on_200_201.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200_201.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200_201.properties + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.stages = AAZListType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200_201.properties.parameters + parameters.Element = AAZObjectType() + + _element = cls._schema_on_200_201.properties.parameters.Element + _element.metadata = AAZDictType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + metadata = cls._schema_on_200_201.properties.parameters.Element.metadata + metadata.Element = AAZStrType() + + disc_array = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "array") + disc_array.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_array.default_value = AAZListType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values.Element = AAZAnyType() + + default_value = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value.Element = AAZAnyType() + + disc_number = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "number") + disc_number.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_number.default_value = AAZIntType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values.Element = AAZIntType() + + disc_object = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "object") + disc_object.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_object.default_value = AAZObjectType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values.Element = AAZDictType() + + _element = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element.Element = AAZAnyType() + + disc_string = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "string") + disc_string.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_string.default_value = AAZStrType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200_201.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values.Element = AAZStrType() + + stages = cls._schema_on_200_201.properties.stages + stages.Element = AAZObjectType() + + _element = cls._schema_on_200_201.properties.stages.Element + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.nested_stage_map = AAZObjectType( + serialized_name="nestedStageMap", + ) + _element.sequence = AAZIntType( + flags={"required": True}, + ) + _element.stage_variables = AAZDictType( + serialized_name="stageVariables", + ) + + nested_stage_map = cls._schema_on_200_201.properties.stages.Element.nested_stage_map + nested_stage_map.parameters = AAZDictType() + nested_stage_map.resource_id = AAZStrType( + serialized_name="resourceId", + ) + + parameters = cls._schema_on_200_201.properties.stages.Element.nested_stage_map.parameters + parameters.Element = AAZAnyType() + + stage_variables = cls._schema_on_200_201.properties.stages.Element.stage_variables + stage_variables.Element = AAZAnyType() + + system_data = cls._schema_on_200_201.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200_201 + + +class _CreateHelper: + """Helper class for Create""" + + +__all__ = ["Create"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_delete.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_delete.py new file mode 100644 index 00000000000..57b3de942df --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_delete.py @@ -0,0 +1,205 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "changesafety stagemap delete", + confirmation="Are you sure you want to perform this operation?", +) +class Delete(AAZCommand): + """Delete a StageMap + """ + + _aaz_info = { + "version": "2025-09-01-preview", + "resources": [ + ["mgmt-plane", "/managementgroups/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return None + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.management_group_name = AAZStrArg( + options=["--management-group-name"], + help="The name of the management group. The name is case insensitive.", + fmt=AAZStrArgFormat( + max_length=90, + min_length=1, + ), + ) + _args_schema.stage_map_name = AAZStrArg( + options=["--stage-map-name"], + help="The name of the StageMap", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.args.management_group_name) and has_value(self.ctx.args.stage_map_name) + condition_1 = has_value(self.ctx.args.stage_map_name) and has_value(self.ctx.subscription_id) + if condition_0: + self.StageMapsDeleteAtManagementGroupLevel(ctx=self.ctx)() + if condition_1: + self.StageMapsDeleteAtSubscriptionLevel(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + class StageMapsDeleteAtManagementGroupLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + if session.http_response.status_code in [204]: + return self.on_204(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/managementGroups/{managementGroupName}/providers/Microsoft.ChangeSafety/stageMaps/{stageMapName}", + **self.url_parameters + ) + + @property + def method(self): + return "DELETE" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "managementGroupName", self.ctx.args.management_group_name, + required=True, + ), + **self.serialize_url_param( + "stageMapName", self.ctx.args.stage_map_name, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + def on_200(self, session): + pass + + def on_204(self, session): + pass + + class StageMapsDeleteAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + if session.http_response.status_code in [204]: + return self.on_204(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/stageMaps/{stageMapName}", + **self.url_parameters + ) + + @property + def method(self): + return "DELETE" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "stageMapName", self.ctx.args.stage_map_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + def on_200(self, session): + pass + + def on_204(self, session): + pass + + +class _DeleteHelper: + """Helper class for Delete""" + + +__all__ = ["Delete"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_show.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_show.py new file mode 100644 index 00000000000..fe3cc4289b6 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_show.py @@ -0,0 +1,522 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "changesafety stagemap show", +) +class Show(AAZCommand): + """Get a StageMap + """ + + _aaz_info = { + "version": "2025-09-01-preview", + "resources": [ + ["mgmt-plane", "/managementgroups/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.management_group_name = AAZStrArg( + options=["--management-group-name"], + help="The name of the management group. The name is case insensitive.", + fmt=AAZStrArgFormat( + max_length=90, + min_length=1, + ), + ) + _args_schema.stage_map_name = AAZStrArg( + options=["--stage-map-name"], + help="The name of the StageMap", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.args.management_group_name) and has_value(self.ctx.args.stage_map_name) + condition_1 = has_value(self.ctx.args.stage_map_name) and has_value(self.ctx.subscription_id) + if condition_0: + self.StageMapsGetAtManagementGroupLevel(ctx=self.ctx)() + if condition_1: + self.StageMapsGetAtSubscriptionLevel(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class StageMapsGetAtManagementGroupLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/managementGroups/{managementGroupName}/providers/Microsoft.ChangeSafety/stageMaps/{stageMapName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "managementGroupName", self.ctx.args.management_group_name, + required=True, + ), + **self.serialize_url_param( + "stageMapName", self.ctx.args.stage_map_name, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType() + _schema_on_200.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.properties + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.stages = AAZListType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200.properties.parameters + parameters.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.parameters.Element + _element.metadata = AAZDictType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + metadata = cls._schema_on_200.properties.parameters.Element.metadata + metadata.Element = AAZStrType() + + disc_array = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "array") + disc_array.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_array.default_value = AAZListType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values.Element = AAZAnyType() + + default_value = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value.Element = AAZAnyType() + + disc_number = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "number") + disc_number.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_number.default_value = AAZIntType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values.Element = AAZIntType() + + disc_object = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "object") + disc_object.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_object.default_value = AAZObjectType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values.Element = AAZDictType() + + _element = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element.Element = AAZAnyType() + + disc_string = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "string") + disc_string.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_string.default_value = AAZStrType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values.Element = AAZStrType() + + stages = cls._schema_on_200.properties.stages + stages.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.stages.Element + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.nested_stage_map = AAZObjectType( + serialized_name="nestedStageMap", + ) + _element.sequence = AAZIntType( + flags={"required": True}, + ) + _element.stage_variables = AAZDictType( + serialized_name="stageVariables", + ) + + nested_stage_map = cls._schema_on_200.properties.stages.Element.nested_stage_map + nested_stage_map.parameters = AAZDictType() + nested_stage_map.resource_id = AAZStrType( + serialized_name="resourceId", + ) + + parameters = cls._schema_on_200.properties.stages.Element.nested_stage_map.parameters + parameters.Element = AAZAnyType() + + stage_variables = cls._schema_on_200.properties.stages.Element.stage_variables + stage_variables.Element = AAZAnyType() + + system_data = cls._schema_on_200.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + class StageMapsGetAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/stageMaps/{stageMapName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "stageMapName", self.ctx.args.stage_map_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType() + _schema_on_200.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.properties + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.stages = AAZListType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200.properties.parameters + parameters.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.parameters.Element + _element.metadata = AAZDictType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + metadata = cls._schema_on_200.properties.parameters.Element.metadata + metadata.Element = AAZStrType() + + disc_array = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "array") + disc_array.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_array.default_value = AAZListType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values.Element = AAZAnyType() + + default_value = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value.Element = AAZAnyType() + + disc_number = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "number") + disc_number.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_number.default_value = AAZIntType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values.Element = AAZIntType() + + disc_object = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "object") + disc_object.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_object.default_value = AAZObjectType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values.Element = AAZDictType() + + _element = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element.Element = AAZAnyType() + + disc_string = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "string") + disc_string.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_string.default_value = AAZStrType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values.Element = AAZStrType() + + stages = cls._schema_on_200.properties.stages + stages.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.stages.Element + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.nested_stage_map = AAZObjectType( + serialized_name="nestedStageMap", + ) + _element.sequence = AAZIntType( + flags={"required": True}, + ) + _element.stage_variables = AAZDictType( + serialized_name="stageVariables", + ) + + nested_stage_map = cls._schema_on_200.properties.stages.Element.nested_stage_map + nested_stage_map.parameters = AAZDictType() + nested_stage_map.resource_id = AAZStrType( + serialized_name="resourceId", + ) + + parameters = cls._schema_on_200.properties.stages.Element.nested_stage_map.parameters + parameters.Element = AAZAnyType() + + stage_variables = cls._schema_on_200.properties.stages.Element.stage_variables + stage_variables.Element = AAZAnyType() + + system_data = cls._schema_on_200.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + +class _ShowHelper: + """Helper class for Show""" + + +__all__ = ["Show"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_update.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_update.py new file mode 100644 index 00000000000..81f3de1cea2 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_update.py @@ -0,0 +1,903 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "changesafety stagemap update", +) +class Update(AAZCommand): + """Update a StageMap + """ + + _aaz_info = { + "version": "2025-09-01-preview", + "resources": [ + ["mgmt-plane", "/managementgroups/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], + ] + } + + AZ_SUPPORT_GENERIC_UPDATE = True + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.management_group_name = AAZStrArg( + options=["--management-group-name"], + help="The name of the management group. The name is case insensitive.", + fmt=AAZStrArgFormat( + max_length=90, + min_length=1, + ), + ) + _args_schema.stage_map_name = AAZStrArg( + options=["--stage-map-name"], + help="The name of the StageMap", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.parameters = AAZDictArg( + options=["--parameters"], + arg_group="Properties", + help="StageMap parameters schema for each stage.", + nullable=True, + ) + _args_schema.stages = AAZListArg( + options=["--stages"], + arg_group="Properties", + help="Array of stages objects.", + ) + + parameters = cls._args_schema.parameters + parameters.Element = AAZObjectArg( + nullable=True, + ) + + _element = cls._args_schema.parameters.Element + _element.array = AAZObjectArg( + options=["array"], + ) + _element.metadata = AAZDictArg( + options=["metadata"], + help="user-specified parameter metadata", + nullable=True, + ) + _element.number = AAZObjectArg( + options=["number"], + ) + _element.object = AAZObjectArg( + options=["object"], + ) + _element.string = AAZObjectArg( + options=["string"], + ) + + array = cls._args_schema.parameters.Element.array + array.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + nullable=True, + ) + array.default_value = AAZListArg( + options=["default-value"], + help="Default value for the parameter.", + nullable=True, + ) + + allowed_values = cls._args_schema.parameters.Element.array.allowed_values + allowed_values.Element = AAZAnyTypeArg( + nullable=True, + ) + + default_value = cls._args_schema.parameters.Element.array.default_value + default_value.Element = AAZAnyTypeArg( + nullable=True, + ) + + metadata = cls._args_schema.parameters.Element.metadata + metadata.Element = AAZStrArg( + nullable=True, + ) + + number = cls._args_schema.parameters.Element.number + number.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + nullable=True, + ) + number.default_value = AAZIntArg( + options=["default-value"], + help="Default value for the parameter.", + nullable=True, + ) + + allowed_values = cls._args_schema.parameters.Element.number.allowed_values + allowed_values.Element = AAZIntArg( + nullable=True, + ) + + object = cls._args_schema.parameters.Element.object + object.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + nullable=True, + ) + object.default_value = AAZObjectArg( + options=["default-value"], + help="Default value for the parameter.", + nullable=True, + blank={}, + ) + + allowed_values = cls._args_schema.parameters.Element.object.allowed_values + allowed_values.Element = AAZDictArg( + nullable=True, + ) + + _element = cls._args_schema.parameters.Element.object.allowed_values.Element + _element.Element = AAZAnyTypeArg( + nullable=True, + ) + + string = cls._args_schema.parameters.Element.string + string.allowed_values = AAZListArg( + options=["allowed-values"], + help="Allowed list of the values for the parameter.", + nullable=True, + ) + string.default_value = AAZStrArg( + options=["default-value"], + help="Default value for the parameter.", + nullable=True, + ) + + allowed_values = cls._args_schema.parameters.Element.string.allowed_values + allowed_values.Element = AAZStrArg( + nullable=True, + ) + + stages = cls._args_schema.stages + stages.Element = AAZObjectArg( + nullable=True, + ) + + _element = cls._args_schema.stages.Element + _element.name = AAZStrArg( + options=["name"], + help="Name of the individual stage.", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + _element.nested_stage_map = AAZObjectArg( + options=["nested-stage-map"], + help="Nested stage map details.", + nullable=True, + ) + _element.sequence = AAZIntArg( + options=["sequence"], + help="Positive integer defining the orchestration order of the stages.", + fmt=AAZIntArgFormat( + minimum=1, + ), + ) + _element.stage_variables = AAZDictArg( + options=["stage-variables"], + help="Variables to apply on the change of that stage. Key value pairs supporting any JSON values.", + nullable=True, + ) + + nested_stage_map = cls._args_schema.stages.Element.nested_stage_map + nested_stage_map.parameters = AAZDictArg( + options=["parameters"], + help="Key value pairs of parameter names & their values for the stageMap referenced by the resourceId field.", + nullable=True, + ) + nested_stage_map.resource_id = AAZStrArg( + options=["resource-id"], + help="ARM resource ID for the nested stagemap resource.", + nullable=True, + ) + + parameters = cls._args_schema.stages.Element.nested_stage_map.parameters + parameters.Element = AAZAnyTypeArg( + nullable=True, + ) + + stage_variables = cls._args_schema.stages.Element.stage_variables + stage_variables.Element = AAZAnyTypeArg( + nullable=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.args.management_group_name) and has_value(self.ctx.args.stage_map_name) + condition_1 = has_value(self.ctx.args.stage_map_name) and has_value(self.ctx.subscription_id) + condition_2 = has_value(self.ctx.args.management_group_name) and has_value(self.ctx.args.stage_map_name) + condition_3 = has_value(self.ctx.args.stage_map_name) and has_value(self.ctx.subscription_id) + if condition_0: + self.StageMapsGetAtManagementGroupLevel(ctx=self.ctx)() + if condition_1: + self.StageMapsGetAtSubscriptionLevel(ctx=self.ctx)() + self.pre_instance_update(self.ctx.vars.instance) + self.InstanceUpdateByJson(ctx=self.ctx)() + self.InstanceUpdateByGeneric(ctx=self.ctx)() + self.post_instance_update(self.ctx.vars.instance) + if condition_2: + self.StageMapsCreateOrUpdateAtManagementGroupLevel(ctx=self.ctx)() + if condition_3: + self.StageMapsCreateOrUpdateAtSubscriptionLevel(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + @register_callback + def pre_instance_update(self, instance): + pass + + @register_callback + def post_instance_update(self, instance): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class StageMapsGetAtManagementGroupLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/managementGroups/{managementGroupName}/providers/Microsoft.ChangeSafety/stageMaps/{stageMapName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "managementGroupName", self.ctx.args.management_group_name, + required=True, + ), + **self.serialize_url_param( + "stageMapName", self.ctx.args.stage_map_name, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + _UpdateHelper._build_schema_stage_map_read(cls._schema_on_200) + + return cls._schema_on_200 + + class StageMapsGetAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/stageMaps/{stageMapName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "stageMapName", self.ctx.args.stage_map_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + _UpdateHelper._build_schema_stage_map_read(cls._schema_on_200) + + return cls._schema_on_200 + + class StageMapsCreateOrUpdateAtManagementGroupLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200, 201]: + return self.on_200_201(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/managementGroups/{managementGroupName}/providers/Microsoft.ChangeSafety/stageMaps/{stageMapName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "managementGroupName", self.ctx.args.management_group_name, + required=True, + ), + **self.serialize_url_param( + "stageMapName", self.ctx.args.stage_map_name, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + value=self.ctx.vars.instance, + ) + + return self.serialize_content(_content_value) + + def on_200_201(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200_201 + ) + + _schema_on_200_201 = None + + @classmethod + def _build_schema_on_200_201(cls): + if cls._schema_on_200_201 is not None: + return cls._schema_on_200_201 + + cls._schema_on_200_201 = AAZObjectType() + _UpdateHelper._build_schema_stage_map_read(cls._schema_on_200_201) + + return cls._schema_on_200_201 + + class StageMapsCreateOrUpdateAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200, 201]: + return self.on_200_201(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/stageMaps/{stageMapName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "stageMapName", self.ctx.args.stage_map_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + value=self.ctx.vars.instance, + ) + + return self.serialize_content(_content_value) + + def on_200_201(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200_201 + ) + + _schema_on_200_201 = None + + @classmethod + def _build_schema_on_200_201(cls): + if cls._schema_on_200_201 is not None: + return cls._schema_on_200_201 + + cls._schema_on_200_201 = AAZObjectType() + _UpdateHelper._build_schema_stage_map_read(cls._schema_on_200_201) + + return cls._schema_on_200_201 + + class InstanceUpdateByJson(AAZJsonInstanceUpdateOperation): + + def __call__(self, *args, **kwargs): + self._update_instance(self.ctx.vars.instance) + + def _update_instance(self, instance): + _instance_value, _builder = self.new_content_builder( + self.ctx.args, + value=instance, + typ=AAZObjectType + ) + _builder.set_prop("properties", AAZObjectType) + + properties = _builder.get(".properties") + if properties is not None: + properties.set_prop("parameters", AAZDictType, ".parameters") + properties.set_prop("stages", AAZListType, ".stages", typ_kwargs={"flags": {"required": True}}) + + parameters = _builder.get(".properties.parameters") + if parameters is not None: + parameters.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.parameters{}") + if _elements is not None: + _elements.set_prop("metadata", AAZDictType, ".metadata") + _elements.set_const("type", "array", AAZStrType, ".array", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "number", AAZStrType, ".number", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "object", AAZStrType, ".object", typ_kwargs={"flags": {"required": True}}) + _elements.set_const("type", "string", AAZStrType, ".string", typ_kwargs={"flags": {"required": True}}) + _elements.discriminate_by("type", "array") + _elements.discriminate_by("type", "number") + _elements.discriminate_by("type", "object") + _elements.discriminate_by("type", "string") + + metadata = _builder.get(".properties.parameters{}.metadata") + if metadata is not None: + metadata.set_elements(AAZStrType, ".") + + disc_array = _builder.get(".properties.parameters{}{type:array}") + if disc_array is not None: + disc_array.set_prop("allowedValues", AAZListType, ".array.allowed_values") + disc_array.set_prop("defaultValue", AAZListType, ".array.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:array}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZAnyType, ".") + + default_value = _builder.get(".properties.parameters{}{type:array}.defaultValue") + if default_value is not None: + default_value.set_elements(AAZAnyType, ".") + + disc_number = _builder.get(".properties.parameters{}{type:number}") + if disc_number is not None: + disc_number.set_prop("allowedValues", AAZListType, ".number.allowed_values") + disc_number.set_prop("defaultValue", AAZIntType, ".number.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:number}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZIntType, ".") + + disc_object = _builder.get(".properties.parameters{}{type:object}") + if disc_object is not None: + disc_object.set_prop("allowedValues", AAZListType, ".object.allowed_values") + disc_object.set_prop("defaultValue", AAZObjectType, ".object.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:object}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZDictType, ".") + + _elements = _builder.get(".properties.parameters{}{type:object}.allowedValues[]") + if _elements is not None: + _elements.set_elements(AAZAnyType, ".") + + disc_string = _builder.get(".properties.parameters{}{type:string}") + if disc_string is not None: + disc_string.set_prop("allowedValues", AAZListType, ".string.allowed_values") + disc_string.set_prop("defaultValue", AAZStrType, ".string.default_value") + + allowed_values = _builder.get(".properties.parameters{}{type:string}.allowedValues") + if allowed_values is not None: + allowed_values.set_elements(AAZStrType, ".") + + stages = _builder.get(".properties.stages") + if stages is not None: + stages.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.stages[]") + if _elements is not None: + _elements.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}}) + _elements.set_prop("nestedStageMap", AAZObjectType, ".nested_stage_map") + _elements.set_prop("sequence", AAZIntType, ".sequence", typ_kwargs={"flags": {"required": True}}) + _elements.set_prop("stageVariables", AAZDictType, ".stage_variables") + + nested_stage_map = _builder.get(".properties.stages[].nestedStageMap") + if nested_stage_map is not None: + nested_stage_map.set_prop("parameters", AAZDictType, ".parameters") + nested_stage_map.set_prop("resourceId", AAZStrType, ".resource_id") + + parameters = _builder.get(".properties.stages[].nestedStageMap.parameters") + if parameters is not None: + parameters.set_elements(AAZAnyType, ".") + + stage_variables = _builder.get(".properties.stages[].stageVariables") + if stage_variables is not None: + stage_variables.set_elements(AAZAnyType, ".") + + return _instance_value + + class InstanceUpdateByGeneric(AAZGenericInstanceUpdateOperation): + + def __call__(self, *args, **kwargs): + self._update_instance_by_generic( + self.ctx.vars.instance, + self.ctx.generic_update_args + ) + + +class _UpdateHelper: + """Helper class for Update""" + + _schema_stage_map_read = None + + @classmethod + def _build_schema_stage_map_read(cls, _schema): + if cls._schema_stage_map_read is not None: + _schema.id = cls._schema_stage_map_read.id + _schema.name = cls._schema_stage_map_read.name + _schema.properties = cls._schema_stage_map_read.properties + _schema.system_data = cls._schema_stage_map_read.system_data + _schema.type = cls._schema_stage_map_read.type + return + + cls._schema_stage_map_read = _schema_stage_map_read = AAZObjectType() + + stage_map_read = _schema_stage_map_read + stage_map_read.id = AAZStrType( + flags={"read_only": True}, + ) + stage_map_read.name = AAZStrType( + flags={"read_only": True}, + ) + stage_map_read.properties = AAZObjectType() + stage_map_read.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + stage_map_read.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = _schema_stage_map_read.properties + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.stages = AAZListType( + flags={"required": True}, + ) + + parameters = _schema_stage_map_read.properties.parameters + parameters.Element = AAZObjectType() + + _element = _schema_stage_map_read.properties.parameters.Element + _element.metadata = AAZDictType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + metadata = _schema_stage_map_read.properties.parameters.Element.metadata + metadata.Element = AAZStrType() + + disc_array = _schema_stage_map_read.properties.parameters.Element.discriminate_by("type", "array") + disc_array.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_array.default_value = AAZListType( + serialized_name="defaultValue", + ) + + allowed_values = _schema_stage_map_read.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values.Element = AAZAnyType() + + default_value = _schema_stage_map_read.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value.Element = AAZAnyType() + + disc_number = _schema_stage_map_read.properties.parameters.Element.discriminate_by("type", "number") + disc_number.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_number.default_value = AAZIntType( + serialized_name="defaultValue", + ) + + allowed_values = _schema_stage_map_read.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values.Element = AAZIntType() + + disc_object = _schema_stage_map_read.properties.parameters.Element.discriminate_by("type", "object") + disc_object.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_object.default_value = AAZObjectType( + serialized_name="defaultValue", + ) + + allowed_values = _schema_stage_map_read.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values.Element = AAZDictType() + + _element = _schema_stage_map_read.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element.Element = AAZAnyType() + + disc_string = _schema_stage_map_read.properties.parameters.Element.discriminate_by("type", "string") + disc_string.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_string.default_value = AAZStrType( + serialized_name="defaultValue", + ) + + allowed_values = _schema_stage_map_read.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values.Element = AAZStrType() + + stages = _schema_stage_map_read.properties.stages + stages.Element = AAZObjectType() + + _element = _schema_stage_map_read.properties.stages.Element + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.nested_stage_map = AAZObjectType( + serialized_name="nestedStageMap", + ) + _element.sequence = AAZIntType( + flags={"required": True}, + ) + _element.stage_variables = AAZDictType( + serialized_name="stageVariables", + ) + + nested_stage_map = _schema_stage_map_read.properties.stages.Element.nested_stage_map + nested_stage_map.parameters = AAZDictType() + nested_stage_map.resource_id = AAZStrType( + serialized_name="resourceId", + ) + + parameters = _schema_stage_map_read.properties.stages.Element.nested_stage_map.parameters + parameters.Element = AAZAnyType() + + stage_variables = _schema_stage_map_read.properties.stages.Element.stage_variables + stage_variables.Element = AAZAnyType() + + system_data = _schema_stage_map_read.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + _schema.id = cls._schema_stage_map_read.id + _schema.name = cls._schema_stage_map_read.name + _schema.properties = cls._schema_stage_map_read.properties + _schema.system_data = cls._schema_stage_map_read.system_data + _schema.type = cls._schema_stage_map_read.type + + +__all__ = ["Update"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/__cmd_group.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/__cmd_group.py new file mode 100644 index 00000000000..ac53d653455 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/__cmd_group.py @@ -0,0 +1,23 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command_group( + "changesafety stageprogression", +) +class __CMDGroup(AAZCommandGroup): + """Manage Stage Progression + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/__init__.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/__init__.py new file mode 100644 index 00000000000..a3db3e36481 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/__init__.py @@ -0,0 +1,15 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * +from ._create import * +from ._delete import * +from ._show import * +from ._update import * diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py new file mode 100644 index 00000000000..ef0d14a24c1 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py @@ -0,0 +1,581 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "changesafety stageprogression create", +) +class Create(AAZCommand): + """Create a StageProgression + """ + + _aaz_info = { + "version": "2025-09-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.change_state_name = AAZStrArg( + options=["--change-state-name"], + help="The name of the ChangeState resource.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg() + _args_schema.stage_progression_name = AAZStrArg( + options=["-n", "--name", "--stage-progression-name"], + help="name of the stageProgression", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-|]{3,100}$", + max_length=100, + min_length=3, + ), + ) + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.additional_data = AAZObjectArg( + options=["--additional-data"], + arg_group="Properties", + help="Additional metadata for the stageProgression resource.", + blank={}, + ) + _args_schema.comments = AAZStrArg( + options=["--comments"], + arg_group="Properties", + help="Comments about the update to the resource.", + fmt=AAZStrArgFormat( + max_length=2000, + ), + ) + _args_schema.links = AAZListArg( + options=["--links"], + arg_group="Properties", + help="Collection of related links for the change.", + ) + _args_schema.parameters = AAZDictArg( + options=["--parameters"], + arg_group="Properties", + help="Stage specific key value pairs of parameter names & their values for the current Stage, If any.", + ) + _args_schema.stage_reference = AAZStrArg( + options=["--stage-reference"], + arg_group="Properties", + help="Stage name relevant to hierarchical StageMap.", + fmt=AAZStrArgFormat( + max_length=200, + min_length=3, + ), + ) + _args_schema.stage_variables = AAZDictArg( + options=["--stage-variables"], + arg_group="Properties", + help="Variables to apply on the change of that stage. Key value pairs supporting any JSON values.", + ) + _args_schema.status = AAZStrArg( + options=["--status"], + arg_group="Properties", + help="StageProgression resource status.", + enum={"Cancelled": "Cancelled", "Completed": "Completed", "Failed": "Failed", "InProgress": "InProgress", "Initialized": "Initialized", "Paused": "Paused", "Skipped": "Skipped"}, + ) + + links = cls._args_schema.links + links.Element = AAZObjectArg() + + _element = cls._args_schema.links.Element + _element.description = AAZStrArg( + options=["description"], + help="Description or note about the link.", + fmt=AAZStrArgFormat( + max_length=2000, + ), + ) + _element.name = AAZStrArg( + options=["name"], + help="name of the link.", + required=True, + ) + _element.uri = AAZStrArg( + options=["uri"], + help="URL or comma separated URLs for the link.", + required=True, + ) + + parameters = cls._args_schema.parameters + parameters.Element = AAZAnyTypeArg() + + stage_variables = cls._args_schema.stage_variables + stage_variables.Element = AAZAnyTypeArg() + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) + if condition_0: + self.StageProgressionsCreateOrUpdateAtSubscriptionLevel(ctx=self.ctx)() + if condition_1: + self.StageProgressionsCreateOrUpdate(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class StageProgressionsCreateOrUpdateAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200, 201]: + return self.on_200_201(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "stageProgressionName", self.ctx.args.stage_progression_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + typ=AAZObjectType, + typ_kwargs={"flags": {"required": True, "client_flatten": True}} + ) + _builder.set_prop("properties", AAZObjectType) + + properties = _builder.get(".properties") + if properties is not None: + properties.set_prop("additionalData", AAZObjectType, ".additional_data") + properties.set_prop("comments", AAZStrType, ".comments") + properties.set_prop("links", AAZListType, ".links") + properties.set_prop("parameters", AAZDictType, ".parameters") + properties.set_prop("stageReference", AAZStrType, ".stage_reference", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("stageVariables", AAZDictType, ".stage_variables") + properties.set_prop("status", AAZStrType, ".status", typ_kwargs={"flags": {"required": True}}) + + links = _builder.get(".properties.links") + if links is not None: + links.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.links[]") + if _elements is not None: + _elements.set_prop("description", AAZStrType, ".description") + _elements.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}}) + _elements.set_prop("uri", AAZStrType, ".uri", typ_kwargs={"flags": {"required": True}}) + + parameters = _builder.get(".properties.parameters") + if parameters is not None: + parameters.set_elements(AAZAnyType, ".") + + stage_variables = _builder.get(".properties.stageVariables") + if stage_variables is not None: + stage_variables.set_elements(AAZAnyType, ".") + + return self.serialize_content(_content_value) + + def on_200_201(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200_201 + ) + + _schema_on_200_201 = None + + @classmethod + def _build_schema_on_200_201(cls): + if cls._schema_on_200_201 is not None: + return cls._schema_on_200_201 + + cls._schema_on_200_201 = AAZObjectType() + + _schema_on_200_201 = cls._schema_on_200_201 + _schema_on_200_201.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200_201.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200_201.properties = AAZObjectType() + _schema_on_200_201.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200_201.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200_201.properties + properties.additional_data = AAZObjectType( + serialized_name="additionalData", + ) + properties.comments = AAZStrType() + properties.links = AAZListType() + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.sequence = AAZIntType( + flags={"read_only": True}, + ) + properties.stage_reference = AAZStrType( + serialized_name="stageReference", + flags={"required": True}, + ) + properties.stage_variables = AAZDictType( + serialized_name="stageVariables", + ) + properties.status = AAZStrType( + flags={"required": True}, + ) + + links = cls._schema_on_200_201.properties.links + links.Element = AAZObjectType() + + _element = cls._schema_on_200_201.properties.links.Element + _element.description = AAZStrType() + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.uri = AAZStrType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200_201.properties.parameters + parameters.Element = AAZAnyType() + + stage_variables = cls._schema_on_200_201.properties.stage_variables + stage_variables.Element = AAZAnyType() + + system_data = cls._schema_on_200_201.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200_201 + + class StageProgressionsCreateOrUpdate(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200, 201]: + return self.on_200_201(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "stageProgressionName", self.ctx.args.stage_progression_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + typ=AAZObjectType, + typ_kwargs={"flags": {"required": True, "client_flatten": True}} + ) + _builder.set_prop("properties", AAZObjectType) + + properties = _builder.get(".properties") + if properties is not None: + properties.set_prop("additionalData", AAZObjectType, ".additional_data") + properties.set_prop("comments", AAZStrType, ".comments") + properties.set_prop("links", AAZListType, ".links") + properties.set_prop("parameters", AAZDictType, ".parameters") + properties.set_prop("stageReference", AAZStrType, ".stage_reference", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("stageVariables", AAZDictType, ".stage_variables") + properties.set_prop("status", AAZStrType, ".status", typ_kwargs={"flags": {"required": True}}) + + links = _builder.get(".properties.links") + if links is not None: + links.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.links[]") + if _elements is not None: + _elements.set_prop("description", AAZStrType, ".description") + _elements.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}}) + _elements.set_prop("uri", AAZStrType, ".uri", typ_kwargs={"flags": {"required": True}}) + + parameters = _builder.get(".properties.parameters") + if parameters is not None: + parameters.set_elements(AAZAnyType, ".") + + stage_variables = _builder.get(".properties.stageVariables") + if stage_variables is not None: + stage_variables.set_elements(AAZAnyType, ".") + + return self.serialize_content(_content_value) + + def on_200_201(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200_201 + ) + + _schema_on_200_201 = None + + @classmethod + def _build_schema_on_200_201(cls): + if cls._schema_on_200_201 is not None: + return cls._schema_on_200_201 + + cls._schema_on_200_201 = AAZObjectType() + + _schema_on_200_201 = cls._schema_on_200_201 + _schema_on_200_201.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200_201.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200_201.properties = AAZObjectType() + _schema_on_200_201.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200_201.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200_201.properties + properties.additional_data = AAZObjectType( + serialized_name="additionalData", + ) + properties.comments = AAZStrType() + properties.links = AAZListType() + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.sequence = AAZIntType( + flags={"read_only": True}, + ) + properties.stage_reference = AAZStrType( + serialized_name="stageReference", + flags={"required": True}, + ) + properties.stage_variables = AAZDictType( + serialized_name="stageVariables", + ) + properties.status = AAZStrType( + flags={"required": True}, + ) + + links = cls._schema_on_200_201.properties.links + links.Element = AAZObjectType() + + _element = cls._schema_on_200_201.properties.links.Element + _element.description = AAZStrType() + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.uri = AAZStrType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200_201.properties.parameters + parameters.Element = AAZAnyType() + + stage_variables = cls._schema_on_200_201.properties.stage_variables + stage_variables.Element = AAZAnyType() + + system_data = cls._schema_on_200_201.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200_201 + + +class _CreateHelper: + """Helper class for Create""" + + +__all__ = ["Create"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py new file mode 100644 index 00000000000..95c37ae3537 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py @@ -0,0 +1,222 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "changesafety stageprogression delete", + confirmation="Are you sure you want to perform this operation?", +) +class Delete(AAZCommand): + """Delete a StageProgression + """ + + _aaz_info = { + "version": "2025-09-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return None + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.change_state_name = AAZStrArg( + options=["--change-state-name"], + help="The name of the ChangeState resource.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg() + _args_schema.stage_progression_name = AAZStrArg( + options=["-n", "--name", "--stage-progression-name"], + help="name of the stageProgression", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-|]{3,100}$", + max_length=100, + min_length=3, + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) + if condition_0: + self.StageProgressionsDeleteAtSubscriptionLevel(ctx=self.ctx)() + if condition_1: + self.StageProgressionsDelete(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + class StageProgressionsDeleteAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + if session.http_response.status_code in [204]: + return self.on_204(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + **self.url_parameters + ) + + @property + def method(self): + return "DELETE" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "stageProgressionName", self.ctx.args.stage_progression_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + def on_200(self, session): + pass + + def on_204(self, session): + pass + + class StageProgressionsDelete(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + if session.http_response.status_code in [204]: + return self.on_204(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + **self.url_parameters + ) + + @property + def method(self): + return "DELETE" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "stageProgressionName", self.ctx.args.stage_progression_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + def on_200(self, session): + pass + + def on_204(self, session): + pass + + +class _DeleteHelper: + """Helper class for Delete""" + + +__all__ = ["Delete"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py new file mode 100644 index 00000000000..18d752292ba --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py @@ -0,0 +1,423 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "change-safety stage-progression show", +) +class Show(AAZCommand): + """Get a StageProgression + """ + + _aaz_info = { + "version": "2025-09-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.change_state_name = AAZStrArg( + options=["--change-state-name"], + help="The name of the ChangeState resource.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg() + _args_schema.stage_progression_name = AAZStrArg( + options=["-n", "--name", "--stage-progression-name"], + help="name of the stageProgression", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-|]{3,100}$", + max_length=100, + min_length=3, + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) + if condition_0: + self.StageProgressionsGetAtSubscriptionLevel(ctx=self.ctx)() + if condition_1: + self.StageProgressionsGet(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class StageProgressionsGetAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "stageProgressionName", self.ctx.args.stage_progression_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType() + _schema_on_200.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.properties + properties.additional_data = AAZObjectType( + serialized_name="additionalData", + ) + properties.comments = AAZStrType() + properties.links = AAZListType() + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.sequence = AAZIntType( + flags={"read_only": True}, + ) + properties.stage_reference = AAZStrType( + serialized_name="stageReference", + flags={"required": True}, + ) + properties.stage_variables = AAZDictType( + serialized_name="stageVariables", + ) + properties.status = AAZStrType( + flags={"required": True}, + ) + + links = cls._schema_on_200.properties.links + links.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.links.Element + _element.description = AAZStrType() + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.uri = AAZStrType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200.properties.parameters + parameters.Element = AAZAnyType() + + stage_variables = cls._schema_on_200.properties.stage_variables + stage_variables.Element = AAZAnyType() + + system_data = cls._schema_on_200.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + class StageProgressionsGet(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "stageProgressionName", self.ctx.args.stage_progression_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType() + _schema_on_200.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.properties + properties.additional_data = AAZObjectType( + serialized_name="additionalData", + ) + properties.comments = AAZStrType() + properties.links = AAZListType() + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.sequence = AAZIntType( + flags={"read_only": True}, + ) + properties.stage_reference = AAZStrType( + serialized_name="stageReference", + flags={"required": True}, + ) + properties.stage_variables = AAZDictType( + serialized_name="stageVariables", + ) + properties.status = AAZStrType( + flags={"required": True}, + ) + + links = cls._schema_on_200.properties.links + links.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.links.Element + _element.description = AAZStrType() + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.uri = AAZStrType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200.properties.parameters + parameters.Element = AAZAnyType() + + stage_variables = cls._schema_on_200.properties.stage_variables + stage_variables.Element = AAZAnyType() + + system_data = cls._schema_on_200.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + +class _ShowHelper: + """Helper class for Show""" + + +__all__ = ["Show"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py new file mode 100644 index 00000000000..37e5a3e8e35 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py @@ -0,0 +1,717 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "changesafety stageprogression update", +) +class Update(AAZCommand): + """Update a StageProgression + """ + + _aaz_info = { + "version": "2025-09-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], + ] + } + + AZ_SUPPORT_GENERIC_UPDATE = True + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.change_state_name = AAZStrArg( + options=["--change-state-name"], + help="The name of the ChangeState resource.", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg() + _args_schema.stage_progression_name = AAZStrArg( + options=["-n", "--name", "--stage-progression-name"], + help="name of the stageProgression", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-|]{3,100}$", + max_length=100, + min_length=3, + ), + ) + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.additional_data = AAZObjectArg( + options=["--additional-data"], + arg_group="Properties", + help="Additional metadata for the stageProgression resource.", + nullable=True, + blank={}, + ) + _args_schema.comments = AAZStrArg( + options=["--comments"], + arg_group="Properties", + help="Comments about the update to the resource.", + nullable=True, + fmt=AAZStrArgFormat( + max_length=2000, + ), + ) + _args_schema.links = AAZListArg( + options=["--links"], + arg_group="Properties", + help="Collection of related links for the change.", + nullable=True, + ) + _args_schema.parameters = AAZDictArg( + options=["--parameters"], + arg_group="Properties", + help="Stage specific key value pairs of parameter names & their values for the current Stage, If any.", + nullable=True, + ) + _args_schema.stage_reference = AAZStrArg( + options=["--stage-reference"], + arg_group="Properties", + help="Stage name relevant to hierarchical StageMap.", + fmt=AAZStrArgFormat( + max_length=200, + min_length=3, + ), + ) + _args_schema.stage_variables = AAZDictArg( + options=["--stage-variables"], + arg_group="Properties", + help="Variables to apply on the change of that stage. Key value pairs supporting any JSON values.", + nullable=True, + ) + _args_schema.status = AAZStrArg( + options=["--status"], + arg_group="Properties", + help="StageProgression resource status.", + enum={"Cancelled": "Cancelled", "Completed": "Completed", "Failed": "Failed", "InProgress": "InProgress", "Initialized": "Initialized", "Paused": "Paused", "Skipped": "Skipped"}, + ) + + links = cls._args_schema.links + links.Element = AAZObjectArg( + nullable=True, + ) + + _element = cls._args_schema.links.Element + _element.description = AAZStrArg( + options=["description"], + help="Description or note about the link.", + nullable=True, + fmt=AAZStrArgFormat( + max_length=2000, + ), + ) + _element.name = AAZStrArg( + options=["name"], + help="name of the link.", + ) + _element.uri = AAZStrArg( + options=["uri"], + help="URL or comma separated URLs for the link.", + ) + + parameters = cls._args_schema.parameters + parameters.Element = AAZAnyTypeArg( + nullable=True, + ) + + stage_variables = cls._args_schema.stage_variables + stage_variables.Element = AAZAnyTypeArg( + nullable=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) + condition_2 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_3 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) + if condition_0: + self.StageProgressionsGetAtSubscriptionLevel(ctx=self.ctx)() + if condition_1: + self.StageProgressionsGet(ctx=self.ctx)() + self.pre_instance_update(self.ctx.vars.instance) + self.InstanceUpdateByJson(ctx=self.ctx)() + self.InstanceUpdateByGeneric(ctx=self.ctx)() + self.post_instance_update(self.ctx.vars.instance) + if condition_2: + self.StageProgressionsCreateOrUpdateAtSubscriptionLevel(ctx=self.ctx)() + if condition_3: + self.StageProgressionsCreateOrUpdate(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + @register_callback + def pre_instance_update(self, instance): + pass + + @register_callback + def post_instance_update(self, instance): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) + return result + + class StageProgressionsGetAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "stageProgressionName", self.ctx.args.stage_progression_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + _UpdateHelper._build_schema_stage_progression_read(cls._schema_on_200) + + return cls._schema_on_200 + + class StageProgressionsGet(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "stageProgressionName", self.ctx.args.stage_progression_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + _UpdateHelper._build_schema_stage_progression_read(cls._schema_on_200) + + return cls._schema_on_200 + + class StageProgressionsCreateOrUpdateAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200, 201]: + return self.on_200_201(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "stageProgressionName", self.ctx.args.stage_progression_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + value=self.ctx.vars.instance, + ) + + return self.serialize_content(_content_value) + + def on_200_201(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200_201 + ) + + _schema_on_200_201 = None + + @classmethod + def _build_schema_on_200_201(cls): + if cls._schema_on_200_201 is not None: + return cls._schema_on_200_201 + + cls._schema_on_200_201 = AAZObjectType() + _UpdateHelper._build_schema_stage_progression_read(cls._schema_on_200_201) + + return cls._schema_on_200_201 + + class StageProgressionsCreateOrUpdate(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200, 201]: + return self.on_200_201(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeStateName", self.ctx.args.change_state_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "stageProgressionName", self.ctx.args.stage_progression_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2025-09-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + value=self.ctx.vars.instance, + ) + + return self.serialize_content(_content_value) + + def on_200_201(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200_201 + ) + + _schema_on_200_201 = None + + @classmethod + def _build_schema_on_200_201(cls): + if cls._schema_on_200_201 is not None: + return cls._schema_on_200_201 + + cls._schema_on_200_201 = AAZObjectType() + _UpdateHelper._build_schema_stage_progression_read(cls._schema_on_200_201) + + return cls._schema_on_200_201 + + class InstanceUpdateByJson(AAZJsonInstanceUpdateOperation): + + def __call__(self, *args, **kwargs): + self._update_instance(self.ctx.vars.instance) + + def _update_instance(self, instance): + _instance_value, _builder = self.new_content_builder( + self.ctx.args, + value=instance, + typ=AAZObjectType + ) + _builder.set_prop("properties", AAZObjectType) + + properties = _builder.get(".properties") + if properties is not None: + properties.set_prop("additionalData", AAZObjectType, ".additional_data") + properties.set_prop("comments", AAZStrType, ".comments") + properties.set_prop("links", AAZListType, ".links") + properties.set_prop("parameters", AAZDictType, ".parameters") + properties.set_prop("stageReference", AAZStrType, ".stage_reference", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("stageVariables", AAZDictType, ".stage_variables") + properties.set_prop("status", AAZStrType, ".status", typ_kwargs={"flags": {"required": True}}) + + links = _builder.get(".properties.links") + if links is not None: + links.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.links[]") + if _elements is not None: + _elements.set_prop("description", AAZStrType, ".description") + _elements.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}}) + _elements.set_prop("uri", AAZStrType, ".uri", typ_kwargs={"flags": {"required": True}}) + + parameters = _builder.get(".properties.parameters") + if parameters is not None: + parameters.set_elements(AAZAnyType, ".") + + stage_variables = _builder.get(".properties.stageVariables") + if stage_variables is not None: + stage_variables.set_elements(AAZAnyType, ".") + + return _instance_value + + class InstanceUpdateByGeneric(AAZGenericInstanceUpdateOperation): + + def __call__(self, *args, **kwargs): + self._update_instance_by_generic( + self.ctx.vars.instance, + self.ctx.generic_update_args + ) + + +class _UpdateHelper: + """Helper class for Update""" + + _schema_stage_progression_read = None + + @classmethod + def _build_schema_stage_progression_read(cls, _schema): + if cls._schema_stage_progression_read is not None: + _schema.id = cls._schema_stage_progression_read.id + _schema.name = cls._schema_stage_progression_read.name + _schema.properties = cls._schema_stage_progression_read.properties + _schema.system_data = cls._schema_stage_progression_read.system_data + _schema.type = cls._schema_stage_progression_read.type + return + + cls._schema_stage_progression_read = _schema_stage_progression_read = AAZObjectType() + + stage_progression_read = _schema_stage_progression_read + stage_progression_read.id = AAZStrType( + flags={"read_only": True}, + ) + stage_progression_read.name = AAZStrType( + flags={"read_only": True}, + ) + stage_progression_read.properties = AAZObjectType() + stage_progression_read.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + stage_progression_read.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = _schema_stage_progression_read.properties + properties.additional_data = AAZObjectType( + serialized_name="additionalData", + ) + properties.comments = AAZStrType() + properties.links = AAZListType() + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.sequence = AAZIntType( + flags={"read_only": True}, + ) + properties.stage_reference = AAZStrType( + serialized_name="stageReference", + flags={"required": True}, + ) + properties.stage_variables = AAZDictType( + serialized_name="stageVariables", + ) + properties.status = AAZStrType( + flags={"required": True}, + ) + + links = _schema_stage_progression_read.properties.links + links.Element = AAZObjectType() + + _element = _schema_stage_progression_read.properties.links.Element + _element.description = AAZStrType() + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.uri = AAZStrType( + flags={"required": True}, + ) + + parameters = _schema_stage_progression_read.properties.parameters + parameters.Element = AAZAnyType() + + stage_variables = _schema_stage_progression_read.properties.stage_variables + stage_variables.Element = AAZAnyType() + + system_data = _schema_stage_progression_read.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + _schema.id = cls._schema_stage_progression_read.id + _schema.name = cls._schema_stage_progression_read.name + _schema.properties = cls._schema_stage_progression_read.properties + _schema.system_data = cls._schema_stage_progression_read.system_data + _schema.type = cls._schema_stage_progression_read.type + + +__all__ = ["Update"] diff --git a/src/azure-changesafety/azext_changesafety/custom.py b/src/azure-changesafety/azext_changesafety/custom.py index 320f61fe9a6..281e4a92179 100644 --- a/src/azure-changesafety/azext_changesafety/custom.py +++ b/src/azure-changesafety/azext_changesafety/custom.py @@ -5,15 +5,19 @@ # Code generated by aaz-dev-tools # -------------------------------------------------------------------------------------------- +import datetime +from argparse import SUPPRESS + # pylint: disable=too-many-lines # pylint: disable=too-many-statements # pylint: disable=protected-access - from knack.log import get_logger from azure.cli.core.aaz import ( has_value, AAZAnyType, + AAZAnyTypeArg, AAZListArg, + AAZObjectArg, AAZStrArg, AAZObjectType, AAZStrType, @@ -173,6 +177,23 @@ def __init__(self, *args, **kwargs): @classmethod def _build_arguments_schema(cls, *args, **kwargs): schema = super()._build_arguments_schema(*args, **kwargs) + if not hasattr(schema, "change_definition"): + schema.change_definition = AAZAnyTypeArg( # type: ignore[attr-defined] + options=["--change-definition"], + arg_group="Properties", + help=SUPPRESS, + nullable=True, + blank={}, + ) + if not hasattr(schema, "stagemap_name"): + schema.stagemap_name = AAZStrArg( # type: ignore[attr-defined] + options=["--stagemap-name", "--stage-map-name"], + arg_group="Properties", + help=( + "StageMap name in the current subscription scope. Automatically builds " + "the stage map resource ID." + ), + ) if not hasattr(schema, "targets"): schema.targets = AAZListArg( options=["--targets"], @@ -186,6 +207,8 @@ def _build_arguments_schema(cls, *args, **kwargs): def _handler(self, command_args): # Extract targets before calling parent handler so we can accept flexible input formats. + self._raw_targets = [] + self._parsed_targets = None command_args = dict(command_args) if command_args else {} raw_targets = command_args.pop('targets', None) if raw_targets is not None: @@ -195,11 +218,22 @@ def _handler(self, command_args): def pre_operations(self): super().pre_operations() - if not self._raw_targets: - raise InvalidArgumentValueError('--targets is required and must include key=value pairs.') + self._ensure_schedule_defaults() + self._apply_stage_map_shortcut() + + change_definition_arg = getattr(self.ctx.args, "change_definition", None) + change_definition_value = None + self._raw_targets = [t for t in (self._raw_targets or []) if t and str(t) != 'Undefined'] + if has_value(change_definition_arg): + change_definition_value = self._parse_change_definition_arg(change_definition_arg) + if self._raw_targets: + raise InvalidArgumentValueError("Use either --change-definition or --targets, not both.") + + if change_definition_value is None and not self._raw_targets: + raise InvalidArgumentValueError('--targets is required unless you provide --change-definition JSON.') # Build and set the changeDefinition with targets - change_definition = self._build_change_definition() + change_definition = change_definition_value or self._build_change_definition() logger.debug("Final changeDefinition for create: %s", change_definition) self.ctx.set_var( 'change_definition', @@ -207,6 +241,73 @@ def pre_operations(self): schema_builder=_build_any_type, ) + def _ensure_schedule_defaults(self): + """Populate anticipated start/end time defaults when the user omits them.""" + now = datetime.datetime.now(datetime.timezone.utc) + start_arg = getattr(self.ctx.args, "anticipated_start_time", None) + end_arg = getattr(self.ctx.args, "anticipated_end_time", None) + start_dt = self._parse_datetime_value(start_arg) if has_value(start_arg) else None + if start_dt is None: + start_dt = now + self.ctx.args.anticipated_start_time = self._to_iso8601(start_dt) + if not has_value(end_arg): + self.ctx.args.anticipated_end_time = self._to_iso8601(start_dt + datetime.timedelta(hours=8)) + + @staticmethod + def _to_iso8601(value): + serialized = value.isoformat(timespec="seconds") + return serialized.replace("+00:00", "Z") + + @staticmethod + def _parse_datetime_value(value): + text = None + if value is None: + return None + if hasattr(value, "to_serialized_data"): + text = value.to_serialized_data() + elif isinstance(value, str): + text = value + if not text: + return None + try: + return datetime.datetime.fromisoformat(str(text).replace("Z", "+00:00")) + except ValueError: + return None + + def _apply_stage_map_shortcut(self): + """Translate --stagemap-name into the stage_map resourceId payload.""" + stage_map_arg = getattr(self.ctx.args, "stage_map", None) + stage_map_name_arg = getattr(self.ctx.args, "stagemap_name", None) + has_stage_map = has_value(stage_map_arg) + has_stage_map_name = has_value(stage_map_name_arg) + + if has_stage_map and has_stage_map_name: + raise InvalidArgumentValueError("Use either --stage-map or --stagemap-name/--stage-map-name, not both.") + + if not has_stage_map_name: + return + + scope_prefix = self._resolve_stage_map_scope() + resource_id = f"{scope_prefix}/providers/Microsoft.ChangeSafety/stageMaps/{stage_map_name_arg.to_serialized_data()}" + logger.debug("Resolved StageMap resourceId from name: %s", resource_id) + self.ctx.args.stage_map = { + "resource_id": resource_id + } + + def _resolve_stage_map_scope(self): + subscription_id = getattr(self.ctx, "subscription_id", None) + if not subscription_id: + raise InvalidArgumentValueError("A subscription is required to resolve the StageMap scope.") + return f"/subscriptions/{subscription_id}" + + def _parse_change_definition_arg(self, change_definition_arg): + data = change_definition_arg.to_serialized_data() + if data is None: + return None + if not isinstance(data, dict): + raise InvalidArgumentValueError("--change-definition must be valid JSON object.") + return data + def _build_change_definition(self): """Build the changeDefinition object with targets""" targets = self._parse_targets(self._raw_targets) @@ -313,6 +414,23 @@ def __init__(self, *args, **kwargs): @classmethod def _build_arguments_schema(cls, *args, **kwargs): schema = super()._build_arguments_schema(*args, **kwargs) + if not hasattr(schema, "change_definition"): + schema.change_definition = AAZAnyTypeArg( # type: ignore[attr-defined] + options=["--change-definition"], + arg_group="Properties", + help=SUPPRESS, + nullable=True, + blank={}, + ) + if not hasattr(schema, "stagemap_name"): + schema.stagemap_name = AAZStrArg( # type: ignore[attr-defined] + options=["--stagemap-name", "--stage-map-name"], + arg_group="Properties", + help=( + "StageMap name in the current subscription scope. Automatically builds " + "the stage map resource ID." + ), + ) if not hasattr(schema, "targets"): schema.targets = AAZListArg( options=["--targets"], @@ -326,6 +444,8 @@ def _build_arguments_schema(cls, *args, **kwargs): def _handler(self, command_args): # Extract targets before calling parent handler so we can accept flexible input formats. + self._raw_targets = [] + self._parsed_targets = None command_args = dict(command_args) if command_args else {} raw_targets = command_args.pop('targets', None) if raw_targets is not None: @@ -335,9 +455,25 @@ def _handler(self, command_args): def pre_operations(self): super().pre_operations() + self._apply_stage_map_shortcut() + + change_definition_arg = getattr(self.ctx.args, "change_definition", None) + change_definition_value = None + self._raw_targets = [t for t in (self._raw_targets or []) if t and str(t) != 'Undefined'] + if has_value(change_definition_arg): + change_definition_value = self._parse_change_definition_arg(change_definition_arg) + if self._raw_targets: + raise InvalidArgumentValueError("Use either --change-definition or --targets, not both.") + # Build and set the changeDefinition with targets if targets are provided - if self._raw_targets: + if change_definition_value is not None: + change_definition = change_definition_value + elif self._raw_targets: change_definition = self._build_change_definition() + else: + change_definition = None + + if change_definition: logger.debug("Final changeDefinition for update: %s", change_definition) self.ctx.set_var( 'change_definition', @@ -429,6 +565,37 @@ def _parse_targets(self, raw_targets): return parsed_targets if parsed_targets else None + def _parse_change_definition_arg(self, change_definition_arg): + data = change_definition_arg.to_serialized_data() + if data is None: + return None + if not isinstance(data, dict): + raise InvalidArgumentValueError("--change-definition must be valid JSON object.") + return data + + def _apply_stage_map_shortcut(self): + """Translate --stagemap-name into the stage_map resourceId payload.""" + stage_map_arg = getattr(self.ctx.args, "stage_map", None) + stage_map_name_arg = getattr(self.ctx.args, "stagemap_name", None) + has_stage_map = has_value(stage_map_arg) + has_stage_map_name = has_value(stage_map_name_arg) + + if has_stage_map and has_stage_map_name: + raise InvalidArgumentValueError("Use either --stage-map or --stagemap-name/--stage-map-name, not both.") + + if not has_stage_map_name: + return + + subscription_id = getattr(self.ctx, "subscription_id", None) + if not subscription_id: + raise InvalidArgumentValueError("A subscription is required to resolve the StageMap scope.") + + resource_id = f"/subscriptions/{subscription_id}/providers/Microsoft.ChangeSafety/stageMaps/{stage_map_name_arg.to_serialized_data()}" + logger.debug("Resolved StageMap resourceId from name: %s", resource_id) + self.ctx.args.stage_map = { + "resource_id": resource_id + } + def _output(self, *args, **kwargs): result = super()._output(*args, **kwargs) _inject_targets_into_result(result, self._parsed_targets) @@ -520,6 +687,15 @@ class ChangeStateDelete(_ChangeStateDelete): "resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST\"" ), }, + { + "name": "Create with StageMap name and default schedule", + "text": ( + "az changesafety changestate create -g MyResourceGroup -n changestate003 " + "--change-type ManualTouch --rollout-type Normal --stagemap-name rolloutStageMap " + "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" + "resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=DELETE\"" + ), + }, ], } diff --git a/src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py b/src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py index 0fbb2d5d35c..94415fca1fa 100644 --- a/src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py +++ b/src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py @@ -6,7 +6,11 @@ # -------------------------------------------------------------------------------------------- import copy +import datetime +import json +import os import sys +import tempfile import types from types import SimpleNamespace from unittest import mock @@ -89,7 +93,18 @@ def _get_arg_value(cmd, arg_name, default=None): return arg.to_serialized_data() @staticmethod - def _build_mock_instance(name, resource_group, subscription_id, change_type, rollout_type, targets, comments=None): + def _build_mock_instance( + name, + resource_group, + subscription_id, + change_type, + rollout_type, + targets, + comments=None, + change_definition=None, + stage_map=None, + anticipated_start_time=None, + anticipated_end_time=None): return { "id": f"/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.ChangeSafety/changeStates/{name}", "name": name, @@ -99,7 +114,10 @@ def _build_mock_instance(name, resource_group, subscription_id, change_type, rol "changeType": change_type, "rolloutType": rollout_type, "comments": comments, - "changeDefinition": { + "anticipatedStartTime": anticipated_start_time, + "anticipatedEndTime": anticipated_end_time, + "stageMap": stage_map, + "changeDefinition": change_definition or { "kind": "Targets", "name": name, "details": { @@ -120,6 +138,20 @@ def _mock_create_execute(cmd): rollout_type = cls._get_arg_value(cmd, "rollout_type", "Normal") comments = cls._get_arg_value(cmd, "comments") targets = copy.deepcopy(cmd._parsed_targets or []) + change_definition_var = getattr(cmd.ctx.vars, "change_definition", None) + change_definition_value = change_definition_var.to_serialized_data() if change_definition_var else None + stage_map_arg = getattr(cmd.ctx.args, "stage_map", None) + stage_map_value = None + if stage_map_arg is not None: + if hasattr(stage_map_arg, "to_serialized_data") and has_value(stage_map_arg): + stage_map_value = stage_map_arg.to_serialized_data() + elif isinstance(stage_map_arg, dict): + stage_map_value = stage_map_arg + if isinstance(stage_map_value, dict) and "resource_id" in stage_map_value and "resourceId" not in stage_map_value: + stage_map_value = {**stage_map_value} + stage_map_value["resourceId"] = stage_map_value.pop("resource_id") + start_time = cls._get_arg_value(cmd, "anticipated_start_time") + end_time = cls._get_arg_value(cmd, "anticipated_end_time") instance = cls._build_mock_instance( name=name, resource_group=resource_group, @@ -128,6 +160,10 @@ def _mock_create_execute(cmd): rollout_type=rollout_type, targets=targets, comments=comments, + change_definition=change_definition_value, + stage_map=stage_map_value, + anticipated_start_time=start_time, + anticipated_end_time=end_time, ) cls._SCENARIO_STATE["instance"] = copy.deepcopy(instance) cmd.ctx.set_var("instance", copy.deepcopy(instance), schema_builder=lambda: AAZAnyType()) @@ -155,13 +191,35 @@ def _mock_update_execute(cmd): new_change_type = cls._get_arg_value(cmd, "change_type") new_rollout = cls._get_arg_value(cmd, "rollout_type") new_comments = cls._get_arg_value(cmd, "comments") + new_start = cls._get_arg_value(cmd, "anticipated_start_time") + new_end = cls._get_arg_value(cmd, "anticipated_end_time") + stage_map_arg = getattr(cmd.ctx.args, "stage_map", None) + stage_map_value = None + if stage_map_arg is not None: + if hasattr(stage_map_arg, "to_serialized_data") and has_value(stage_map_arg): + stage_map_value = stage_map_arg.to_serialized_data() + elif isinstance(stage_map_arg, dict): + stage_map_value = stage_map_arg + if isinstance(stage_map_value, dict) and "resource_id" in stage_map_value and "resourceId" not in stage_map_value: + stage_map_value = {**stage_map_value} + stage_map_value["resourceId"] = stage_map_value.pop("resource_id") + change_definition_var = getattr(cmd.ctx.vars, "change_definition", None) + change_definition_value = change_definition_var.to_serialized_data() if change_definition_var else None if new_change_type: current["properties"]["changeType"] = new_change_type if new_rollout: current["properties"]["rolloutType"] = new_rollout if new_comments is not None: current["properties"]["comments"] = new_comments - if cmd._parsed_targets: # pylint: disable=protected-access + if new_start is not None: + current["properties"]["anticipatedStartTime"] = new_start + if new_end is not None: + current["properties"]["anticipatedEndTime"] = new_end + if stage_map_value is not None: + current["properties"]["stageMap"] = stage_map_value + if change_definition_value is not None: + current["properties"]["changeDefinition"] = change_definition_value + elif cmd._parsed_targets: # pylint: disable=protected-access current["properties"]["changeDefinition"]["details"]["targets"] = copy.deepcopy(cmd._parsed_targets) # pylint: disable=protected-access cls._SCENARIO_STATE["instance"] = copy.deepcopy(current) cmd.ctx.set_var("instance", copy.deepcopy(current), schema_builder=lambda: AAZAnyType()) @@ -274,6 +332,102 @@ def test_inject_targets_does_not_override_existing(self): assert data["changeDefinition"]["details"]["targets"] == existing + def test_default_schedule_times_applied_on_create(self): + resource_group = "rgScheduleDefaults" + change_state_name = self.create_random_name('chg', 12) + target_resource = ( + f"/subscriptions/{self.FAKE_SUBSCRIPTION_ID}/resourceGroups/{resource_group}/" + "providers/Microsoft.Compute/virtualMachines/myVm" + ) + self.kwargs.update({ + "rg": resource_group, + "name": change_state_name, + "change_type": "ManualTouch", + "rollout_type": "Normal", + "targets": f"resourceId={target_resource},operation=PATCH", + }) + + result = self.cmd( + 'az changesafety changestate create -g {rg} -n {name} ' + '--change-type {change_type} --rollout-type {rollout_type} ' + '--targets "{targets}"', + ).get_output_in_json() + + start = datetime.datetime.fromisoformat(result['properties']['anticipatedStartTime'].replace('Z', '+00:00')) + end = datetime.datetime.fromisoformat(result['properties']['anticipatedEndTime'].replace('Z', '+00:00')) + delta_seconds = abs((end - start).total_seconds() - 8 * 3600) + self.assertLessEqual(delta_seconds, 5) + + def test_create_with_change_definition_without_targets(self): + resource_group = "rgChangeDefinition" + change_state_name = self.create_random_name('chg', 12) + target_resource = ( + f"/subscriptions/{self.FAKE_SUBSCRIPTION_ID}/resourceGroups/{resource_group}/" + "providers/Microsoft.Compute/virtualMachines/myVm" + ) + change_definition = { + "kind": "Targets", + "name": change_state_name, + "details": { + "targets": [ + {"resourceId": target_resource, "httpMethod": "DELETE"} + ] + } + } + with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as handle: + json.dump(change_definition, handle) + change_def_path = handle.name + self.addCleanup(lambda: os.path.exists(change_def_path) and os.remove(change_def_path)) + self.kwargs.update({ + "rg": resource_group, + "name": change_state_name, + "change_type": "ManualTouch", + "rollout_type": "Hotfix", + "change_definition": change_def_path, + }) + + result = self.cmd( + 'az changesafety changestate create -g {rg} -n {name} ' + '--change-type {change_type} --rollout-type {rollout_type} ' + '--change-definition "@{change_definition}"', + ).get_output_in_json() + + self.assertEqual( + result["properties"]["changeDefinition"]["details"]["targets"][0]["resourceId"], + target_resource, + ) + self.assertEqual( + result["properties"]["changeDefinition"]["details"]["targets"][0]["httpMethod"], + "DELETE", + ) + + def test_stage_map_name_shortcut(self): + resource_group = "rgStageMapShortcut" + change_state_name = self.create_random_name('chg', 12) + stage_map_name = "rollout-plan" + target_resource = ( + f"/subscriptions/{self.FAKE_SUBSCRIPTION_ID}/resourceGroups/{resource_group}/" + "providers/Microsoft.Storage/storageAccounts/demo" + ) + expected_stage_map = f"/subscriptions/{self.FAKE_SUBSCRIPTION_ID}/providers/Microsoft.ChangeSafety/stageMaps/{stage_map_name}" + self.kwargs.update({ + "rg": resource_group, + "name": change_state_name, + "change_type": "ManualTouch", + "rollout_type": "Normal", + "targets": f"resourceId={target_resource},operation=PATCH", + "stage_map_name": stage_map_name, + "subscription": self.FAKE_SUBSCRIPTION_ID, + }) + + result = self.cmd( + 'az changesafety changestate create -g {rg} -n {name} ' + '--change-type {change_type} --rollout-type {rollout_type} ' + '--targets "{targets}" --stagemap-name {stage_map_name} --subscription {subscription}', + ).get_output_in_json() + + self.assertEqual(result["properties"]["stageMap"]["resourceId"], expected_stage_map) + def test_change_state_cli_scenario(self): resource_group = "rgChangeSafetyScenario" change_state_name = self.create_random_name('chg', 12) From 8dd8743727b299b0426c47dc7c1e86fb17a67b59 Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Thu, 11 Dec 2025 10:34:17 -0800 Subject: [PATCH 17/25] Update alias --- src/azure-changesafety/README.md | 22 ++++++------ .../azext_changesafety/_help.py | 34 +++++++++---------- .../azext_changesafety/commands.py | 8 ++--- .../azext_changesafety/custom.py | 20 +++++------ src/azure-changesafety/setup.py | 6 ++-- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/azure-changesafety/README.md b/src/azure-changesafety/README.md index 89e7d4aae7c..da057c8251d 100644 --- a/src/azure-changesafety/README.md +++ b/src/azure-changesafety/README.md @@ -10,20 +10,20 @@ az extension add --name azure-changesafety ## Commands ```bash -az changesafety changestate create # Create a ChangeState definition for one or more targets. -az changesafety changestate update # Update metadata, rollout configuration, or target definitions. -az changesafety changestate delete # Delete a ChangeState resource. -az changesafety changestate show # Display details for a ChangeState resource. +az changesafety changerecord create # Create a ChangeState definition for one or more targets. +az changesafety changerecord update # Update metadata, rollout configuration, or target definitions. +az changesafety changerecord delete # Delete a ChangeState resource. +az changesafety changerecord show # Display details for a ChangeState resource. ``` -Run `az changesafety changestate -h` to see full parameter details and examples. +Run `az changesafety changerecord -h` to see full parameter details and examples. ## Examples Create a ChangeState describing a web app rollout: ```bash -az changesafety changestate create \ +az changesafety changerecord create \ -g MyResourceGroup \ - -n webAppRollout01 \ + -n changerecord-webapp-rollout \ --change-type AppDeployment \ --rollout-type Normal \ --targets "resourceId=/subscriptions//resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=create" \ @@ -32,18 +32,18 @@ az changesafety changestate create \ Update the rollout type and add a comment: ```bash -az changesafety changestate update \ +az changesafety changerecord update \ -g MyResourceGroup \ - -n webAppRollout01 \ + -n changerecord-webapp-rollout \ --rollout-type Emergency \ --comments "Escalated due to customer impact" ``` Delete a ChangeState: ```bash -az changesafety changestate delete -g MyResourceGroup -n webAppRollout01 --yes +az changesafety changerecord delete -g MyResourceGroup -n changerecord-webapp-rollout --yes ``` ## Additional Information -- View command documentation: `az changesafety changestate -h` +- View command documentation: `az changesafety changerecord -h` - Remove the extension when no longer needed: `az extension remove --name azure-changesafety` diff --git a/src/azure-changesafety/azext_changesafety/_help.py b/src/azure-changesafety/azext_changesafety/_help.py index 1ffb1040d33..05d31d347e5 100644 --- a/src/azure-changesafety/azext_changesafety/_help.py +++ b/src/azure-changesafety/azext_changesafety/_help.py @@ -15,19 +15,19 @@ short-summary: Manage Change Safety resources. """ -helps['changesafety changestate'] = """ +helps['changesafety changerecord'] = """ type: group short-summary: Manage ChangeState resources that describe planned changes across targets. """ -helps['changesafety changestate create'] = """ +helps['changesafety changerecord create'] = """ type: command short-summary: Create a ChangeState resource. long-summary: > Provide at least one target definition to describe which resources or operations the ChangeState will affect. Targets are expressed as comma or semicolon separated key=value pairs such as resourceId=RESOURCE_ID,operation=DELETE. The command is also available through the alias - `az change-safety change-state`. If you omit scheduling flags, the anticipated start time defaults + `az changesafety changerecord`. If you omit scheduling flags, the anticipated start time defaults to now and the anticipated end time defaults to eight hours later (UTC). parameters: - name: --targets @@ -51,26 +51,26 @@ examples: - name: Create with StageMap reference and status link text: |- - az changesafety changestate create -g MyResourceGroup -n changestate002 --change-type ManualTouch --rollout-type Normal --stage-map "{resource-id:/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.ChangeSafety/stageMaps/rolloutStageMap}" --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH" --links "[{name:status,uri:'https://contoso.com/change/rollout-002'}]" - az changesafety changestate delete -g MyResourceGroup -n changestate002 --yes + az changesafety changerecord create -g MyResourceGroup -n changerecord002 --change-type ManualTouch --rollout-type Normal --stage-map "{resource-id:/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.ChangeSafety/stageMaps/rolloutStageMap}" --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH" --links "[{name:status,uri:'https://contoso.com/change/rollout-002'}]" + az changesafety changerecord delete -g MyResourceGroup -n changerecord002 --yes - name: Create a change state for a VM rollout text: |- - az changesafety changestate create -g MyResourceGroup -n changestate001 --change-type AppDeployment --rollout-type Normal --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PUT" + az changesafety changerecord create -g MyResourceGroup -n changerecord001 --change-type AppDeployment --rollout-type Normal --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PUT" - name: Create with staging rollout configuration text: |- - az changesafety changestate create -g MyResourceGroup -n opsChange01 --rollout-type Hotfix --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST" + az changesafety changerecord create -g MyResourceGroup -n changerecord-ops01 --rollout-type Hotfix --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST" - name: Reference a StageMap by name text: |- - az changesafety changestate create -g MyResourceGroup -n changestate003 --change-type ManualTouch --rollout-type Normal --stagemap-name rolloutStageMap --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=DELETE" + az changesafety changerecord create -g MyResourceGroup -n changerecord003 --change-type ManualTouch --rollout-type Normal --stagemap-name rolloutStageMap --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=DELETE" """ -helps['changesafety changestate update'] = """ +helps['changesafety changerecord update'] = """ type: command short-summary: Update an existing ChangeState resource. long-summary: > Use this command to modify descriptive metadata, rollout settings, or replace targets for an existing ChangeState. When you pass --targets, the supplied definitions overwrite the previous set. - This command is also available through the alias `az change-safety change-state`. + This command is also available through the alias `az change-safety change-record`. parameters: - name: --targets short-summary: > @@ -89,29 +89,29 @@ examples: - name: Adjust rollout type and add a comment text: |- - az changesafety changestate update -g MyResourceGroup -n changestate001 --rollout-type Emergency --comments "Escalated to emergency rollout" + az changesafety changerecord update -g MyResourceGroup -n changerecord001 --rollout-type Emergency --comments "Escalated to emergency rollout" - name: Update scheduling window text: |- - az changesafety changestate update -g MyResourceGroup -n changestate001 --anticipated-start-time "2024-09-01T08:00:00Z" --anticipated-end-time "2024-09-01T12:00:00Z" + az changesafety changerecord update -g MyResourceGroup -n changerecord001 --anticipated-start-time "2024-09-01T08:00:00Z" --anticipated-end-time "2024-09-01T12:00:00Z" - name: Replace the target definition text: |- - az changesafety changestate update -g MyResourceGroup -n changestate001 --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/myServer,operation=PATCH" + az changesafety changerecord update -g MyResourceGroup -n changerecord001 --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/myServer,operation=PATCH" """ -helps['changesafety changestate delete'] = """ +helps['changesafety changerecord delete'] = """ type: command short-summary: Delete a ChangeState resource. examples: - name: Delete a change state without confirmation text: |- - az changesafety changestate delete -g MyResourceGroup -n changestate001 --yes + az changesafety changerecord delete -g MyResourceGroup -n changerecord001 --yes """ -helps['changesafety changestate show'] = """ +helps['changesafety changerecord show'] = """ type: command short-summary: Show details for a ChangeState resource. examples: - name: Show a change state text: |- - az changesafety changestate show -g MyResourceGroup -n changestate001 + az changesafety changerecord show -g MyResourceGroup -n changerecord001 """ diff --git a/src/azure-changesafety/azext_changesafety/commands.py b/src/azure-changesafety/azext_changesafety/commands.py index e0bfc0702af..a093c096786 100644 --- a/src/azure-changesafety/azext_changesafety/commands.py +++ b/src/azure-changesafety/azext_changesafety/commands.py @@ -16,7 +16,7 @@ def load_command_table(self, _): # pylint: disable=unused-argument delete_command = ChangeStateDelete(loader=self) show_command = ChangeStateShow(loader=self) - self.command_table['changesafety changestate create'] = create_command - self.command_table['changesafety changestate update'] = update_command - self.command_table['changesafety changestate delete'] = delete_command - self.command_table['changesafety changestate show'] = show_command + self.command_table['changesafety changerecord create'] = create_command + self.command_table['changesafety changerecord update'] = update_command + self.command_table['changesafety changerecord delete'] = delete_command + self.command_table['changesafety changerecord show'] = show_command \ No newline at end of file diff --git a/src/azure-changesafety/azext_changesafety/custom.py b/src/azure-changesafety/azext_changesafety/custom.py index 281e4a92179..0c21530622b 100644 --- a/src/azure-changesafety/azext_changesafety/custom.py +++ b/src/azure-changesafety/azext_changesafety/custom.py @@ -659,20 +659,20 @@ class ChangeStateDelete(_ChangeStateDelete): { "name": "Create with StageMap reference and status link", "text": ( - "az changesafety changestate create -g MyResourceGroup -n changestate002 " + "az changesafety changerecord create -g MyResourceGroup -n changerecord002 " "--change-type ManualTouch --rollout-type Normal " "--stage-map \"{resource-id:/subscriptions/00000000-0000-0000-0000-000000000000/" "resourceGroups/MyResourceGroup/providers/Microsoft.ChangeSafety/stageMaps/rolloutStageMap}\" " "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" "resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH\" " "--links \"[{name:status,uri:'https://contoso.com/change/rollout-002'}]\"\n" - "az changesafety changestate delete -g MyResourceGroup -n changestate002 --yes" + "az changesafety changerecord delete -g MyResourceGroup -n changerecord002 --yes" ), }, { "name": "Create a change state for a VM rollout", "text": ( - "az changesafety changestate create -g MyResourceGroup -n changestate001 " + "az changesafety changerecord create -g MyResourceGroup -n changerecord001 " "--change-type AppDeployment --rollout-type Normal " "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" "resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PUT\"" @@ -681,7 +681,7 @@ class ChangeStateDelete(_ChangeStateDelete): { "name": "Create with staging rollout configuration", "text": ( - "az changesafety changestate create -g MyResourceGroup -n opsChange01 " + "az changesafety changerecord create -g MyResourceGroup -n changerecord-ops01 " "--rollout-type Hotfix " "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" "resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST\"" @@ -690,7 +690,7 @@ class ChangeStateDelete(_ChangeStateDelete): { "name": "Create with StageMap name and default schedule", "text": ( - "az changesafety changestate create -g MyResourceGroup -n changestate003 " + "az changesafety changerecord create -g MyResourceGroup -n changerecord003 " "--change-type ManualTouch --rollout-type Normal --stagemap-name rolloutStageMap " "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" "resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=DELETE\"" @@ -705,14 +705,14 @@ class ChangeStateDelete(_ChangeStateDelete): { "name": "Adjust rollout type and add a comment", "text": ( - "az changesafety changestate update -g MyResourceGroup -n changestate001 " + "az changesafety changerecord update -g MyResourceGroup -n changerecord001 " "--rollout-type Emergency --comments \"Escalated to emergency rollout\"" ), }, { "name": "Update scheduling window", "text": ( - "az changesafety changestate update -g MyResourceGroup -n changestate001 " + "az changesafety changerecord update -g MyResourceGroup -n changerecord001 " "--anticipated-start-time \"2024-09-01T08:00:00Z\" " "--anticipated-end-time \"2024-09-01T12:00:00Z\"" ), @@ -720,7 +720,7 @@ class ChangeStateDelete(_ChangeStateDelete): { "name": "Replace the target definition", "text": ( - "az changesafety changestate update -g MyResourceGroup -n changestate001 " + "az changesafety changerecord update -g MyResourceGroup -n changerecord001 " "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" "resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/myServer,operation=PATCH\"" ), @@ -733,7 +733,7 @@ class ChangeStateDelete(_ChangeStateDelete): "examples": [ { "name": "Delete a change state without confirmation", - "text": "az changesafety changestate delete -g MyResourceGroup -n changestate001 --yes", + "text": "az changesafety changerecord delete -g MyResourceGroup -n changerecord001 --yes", }, ], } @@ -743,7 +743,7 @@ class ChangeStateDelete(_ChangeStateDelete): "examples": [ { "name": "Show a change state", - "text": "az changesafety changestate show -g MyResourceGroup -n changestate001", + "text": "az changesafety changerecord show -g MyResourceGroup -n changerecord001", }, ], } diff --git a/src/azure-changesafety/setup.py b/src/azure-changesafety/setup.py index 79dda33fcd9..9574e32ade5 100644 --- a/src/azure-changesafety/setup.py +++ b/src/azure-changesafety/setup.py @@ -34,14 +34,14 @@ HISTORY = f.read() setup( - name='change-state', + name='azure-changesafety', version=VERSION, - description='Microsoft Azure Command-Line Tools ChangeState Extension.', + description='Microsoft Azure Command-Line Tools ChangeSafety Extension.', long_description=README + '\n\n' + HISTORY, license='MIT', author='Microsoft Corporation', author_email='azpycli@microsoft.com', - url='https://github.com/Azure/azure-cli-extensions/tree/main/src/change-state', + url='https://github.com/Azure/azure-cli-extensions/tree/main/src/azure-changesafety', classifiers=CLASSIFIERS, packages=find_packages(exclude=["tests"]), package_data={'azext_changesafety': ['azext_metadata.json']}, From d02017a50a712eb630dde1fbf298412b3520413e Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Thu, 11 Dec 2025 11:03:39 -0800 Subject: [PATCH 18/25] Update ChangeRecord name --- .../aaz/latest/change_safety/change_state/_create.py | 2 +- .../aaz/latest/change_safety/change_state/_delete.py | 2 +- .../aaz/latest/change_safety/change_state/_show.py | 2 +- .../aaz/latest/change_safety/change_state/_update.py | 2 +- .../aaz/latest/change_safety/stage_progression/_create.py | 2 +- .../aaz/latest/change_safety/stage_progression/_delete.py | 2 +- .../aaz/latest/change_safety/stage_progression/_show.py | 2 +- .../aaz/latest/change_safety/stage_progression/_update.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_create.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_create.py index 0fe7e63f36f..66688e79cfe 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_create.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_create.py @@ -43,7 +43,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( - options=["-n", "--name", "--change-state-name"], + options=["-n", "--name", "--change-record-name"], help="The name of the ChangeState resource.", required=True, fmt=AAZStrArgFormat( diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_delete.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_delete.py index 96893b74400..9022e402037 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_delete.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_delete.py @@ -43,7 +43,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( - options=["-n", "--name", "--change-state-name"], + options=["-n", "--name", "--change-record-name"], help="The name of the ChangeState resource.", required=True, id_part="name", diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_show.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_show.py index aeb51bc3422..33f3c3fcd2f 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_show.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_show.py @@ -43,7 +43,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( - options=["-n", "--name", "--change-state-name"], + options=["-n", "--name", "--change-record-name"], help="The name of the ChangeState resource.", required=True, id_part="name", diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_update.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_update.py index 3de21715670..94388a1f2ad 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_update.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_update.py @@ -45,7 +45,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( - options=["-n", "--name", "--change-state-name"], + options=["-n", "--name", "--change-record-name"], help="The name of the ChangeState resource.", required=True, id_part="name", diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py index ef0d14a24c1..b0675f2703e 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py @@ -43,7 +43,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( - options=["--change-state-name"], + options=["--change-record-name"], help="The name of the ChangeState resource.", required=True, fmt=AAZStrArgFormat( diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py index 95c37ae3537..5ae9b057a26 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py @@ -44,7 +44,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( - options=["--change-state-name"], + options=["--change-record-name"], help="The name of the ChangeState resource.", required=True, id_part="name", diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py index 18d752292ba..33330f89e9b 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py @@ -43,7 +43,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( - options=["--change-state-name"], + options=["--change-record-name"], help="The name of the ChangeState resource.", required=True, id_part="name", diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py index 37e5a3e8e35..3f3bd68b9c9 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py @@ -45,7 +45,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( - options=["--change-state-name"], + options=["--change-record-name"], help="The name of the ChangeState resource.", required=True, id_part="name", From bf2565b085fb0ab739f61f82a8ae8a84d72310e5 Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Fri, 19 Dec 2025 00:21:42 -0800 Subject: [PATCH 19/25] Address comments --- src/azure-changesafety/README.md | 26 ++++++++++++++----- .../azext_changesafety/__init__.py | 4 +-- .../azext_changesafety/_help.py | 18 ++++++------- .../change_safety/change_state/_create.py | 6 ++--- .../change_safety/change_state/_delete.py | 4 +-- .../change_safety/change_state/_show.py | 4 +-- .../change_safety/change_state/_update.py | 6 ++--- .../stage_progression/_create.py | 2 +- .../stage_progression/_delete.py | 2 +- .../change_safety/stage_progression/_show.py | 2 +- .../stage_progression/_update.py | 2 +- 11 files changed, 44 insertions(+), 32 deletions(-) diff --git a/src/azure-changesafety/README.md b/src/azure-changesafety/README.md index da057c8251d..b22204d16f7 100644 --- a/src/azure-changesafety/README.md +++ b/src/azure-changesafety/README.md @@ -1,5 +1,5 @@ # Azure CLI Change Safety Extension -Azure CLI extension for managing Change Safety `ChangeState` resources used to coordinate operational changes across Azure targets. +Azure CLI extension for managing Change Safety `ChangeRecord` resources. A ChangeRecord describes a planned change to one or more Azure resources, enabling coordination, tracking, and safe deployment across your environment. ## Installation ```bash @@ -10,23 +10,35 @@ az extension add --name azure-changesafety ## Commands ```bash -az changesafety changerecord create # Create a ChangeState definition for one or more targets. +az changesafety changerecord create # Create a ChangeRecord for one or more targets. az changesafety changerecord update # Update metadata, rollout configuration, or target definitions. -az changesafety changerecord delete # Delete a ChangeState resource. -az changesafety changerecord show # Display details for a ChangeState resource. +az changesafety changerecord delete # Delete a ChangeRecord resource. +az changesafety changerecord show # Display details for a ChangeRecord resource. ``` Run `az changesafety changerecord -h` to see full parameter details and examples. ## Examples -Create a ChangeState describing a web app rollout: +Create a ChangeRecord for a manual touch operation (e.g., VM maintenance): +```bash +az changesafety changerecord create \ + -g MyResourceGroup \ + -n changerecord-vm-maintenance \ + --change-type ManualTouch \ + --rollout-type Normal \ + --targets "resourceId=/subscriptions//resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH" \ + --stagemap-name myStageMap +``` + +Create a ChangeRecord for an app deployment with a StageMap reference: ```bash az changesafety changerecord create \ -g MyResourceGroup \ -n changerecord-webapp-rollout \ --change-type AppDeployment \ --rollout-type Normal \ - --targets "resourceId=/subscriptions//resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=create" \ + --targets "resourceId=/subscriptions//resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=PUT" \ + --stage-map "{resource-id:/subscriptions//resourceGroups/MyResourceGroup/providers/Microsoft.ChangeSafety/stageMaps/rolloutStageMap}" \ --links name=Runbook uri=https://contoso.com/runbook ``` @@ -39,7 +51,7 @@ az changesafety changerecord update \ --comments "Escalated due to customer impact" ``` -Delete a ChangeState: +Delete a ChangeRecord: ```bash az changesafety changerecord delete -g MyResourceGroup -n changerecord-webapp-rollout --yes ``` diff --git a/src/azure-changesafety/azext_changesafety/__init__.py b/src/azure-changesafety/azext_changesafety/__init__.py index 05751262e4f..46248fb61e1 100644 --- a/src/azure-changesafety/azext_changesafety/__init__.py +++ b/src/azure-changesafety/azext_changesafety/__init__.py @@ -9,7 +9,7 @@ from azext_changesafety._help import helps # pylint: disable=unused-import -class ChangeStateCommandsLoader(AzCommandsLoader): +class ChangeRecordCommandsLoader(AzCommandsLoader): def __init__(self, cli_ctx=None): from azure.cli.core.commands import CliCommandType @@ -39,4 +39,4 @@ def load_arguments(self, command): load_arguments(self, command) -COMMAND_LOADER_CLS = ChangeStateCommandsLoader +COMMAND_LOADER_CLS = ChangeRecordCommandsLoader diff --git a/src/azure-changesafety/azext_changesafety/_help.py b/src/azure-changesafety/azext_changesafety/_help.py index 05d31d347e5..9058c3181e1 100644 --- a/src/azure-changesafety/azext_changesafety/_help.py +++ b/src/azure-changesafety/azext_changesafety/_help.py @@ -17,14 +17,14 @@ helps['changesafety changerecord'] = """ type: group - short-summary: Manage ChangeState resources that describe planned changes across targets. + short-summary: Manage ChangeRecord resources that describe planned changes across targets. """ helps['changesafety changerecord create'] = """ type: command - short-summary: Create a ChangeState resource. + short-summary: Create a ChangeRecord resource. long-summary: > - Provide at least one target definition to describe which resources or operations the ChangeState + Provide at least one target definition to describe which resources or operations the ChangeRecord will affect. Targets are expressed as comma or semicolon separated key=value pairs such as resourceId=RESOURCE_ID,operation=DELETE. The command is also available through the alias `az changesafety changerecord`. If you omit scheduling flags, the anticipated start time defaults @@ -66,10 +66,10 @@ helps['changesafety changerecord update'] = """ type: command - short-summary: Update an existing ChangeState resource. + short-summary: Update an existing ChangeRecord resource. long-summary: > Use this command to modify descriptive metadata, rollout settings, or replace targets for an - existing ChangeState. When you pass --targets, the supplied definitions overwrite the previous set. + existing ChangeRecord. When you pass --targets, the supplied definitions overwrite the previous set. This command is also available through the alias `az change-safety change-record`. parameters: - name: --targets @@ -100,18 +100,18 @@ helps['changesafety changerecord delete'] = """ type: command - short-summary: Delete a ChangeState resource. + short-summary: Delete a ChangeRecord resource. examples: - - name: Delete a change state without confirmation + - name: Delete a ChangeRecord without confirmation text: |- az changesafety changerecord delete -g MyResourceGroup -n changerecord001 --yes """ helps['changesafety changerecord show'] = """ type: command - short-summary: Show details for a ChangeState resource. + short-summary: Show details for a ChangeRecord resource. examples: - - name: Show a change state + - name: Show a ChangeRecord text: |- az changesafety changerecord show -g MyResourceGroup -n changerecord001 """ diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_create.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_create.py index 66688e79cfe..32624a3c2d2 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_create.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_create.py @@ -15,7 +15,7 @@ "changesafety changestate create", ) class Create(AAZCommand): - """Create a ChangeState + """Create a ChangeRecord """ _aaz_info = { @@ -44,7 +44,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( options=["-n", "--name", "--change-record-name"], - help="The name of the ChangeState resource.", + help="The name of the ChangeRecord resource.", required=True, fmt=AAZStrArgFormat( pattern="^[a-zA-Z0-9-]{3,100}$", @@ -88,7 +88,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema.comments = AAZStrArg( options=["--comments"], arg_group="Properties", - help="Comments about the last update to the changeState resource.", + help="Comments about the last update to the ChangeRecord resource.", fmt=AAZStrArgFormat( max_length=2000, ), diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_delete.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_delete.py index 9022e402037..f10cd63643d 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_delete.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_delete.py @@ -16,7 +16,7 @@ confirmation="Are you sure you want to perform this operation?", ) class Delete(AAZCommand): - """Delete a ChangeState + """Delete a ChangeRecord """ _aaz_info = { @@ -44,7 +44,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( options=["-n", "--name", "--change-record-name"], - help="The name of the ChangeState resource.", + help="The name of the ChangeRecord resource.", required=True, id_part="name", fmt=AAZStrArgFormat( diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_show.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_show.py index 33f3c3fcd2f..4ca355981ac 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_show.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_show.py @@ -15,7 +15,7 @@ "changesafety changestate show", ) class Show(AAZCommand): - """Get a ChangeState + """Get a ChangeRecord """ _aaz_info = { @@ -44,7 +44,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( options=["-n", "--name", "--change-record-name"], - help="The name of the ChangeState resource.", + help="The name of the ChangeRecord resource.", required=True, id_part="name", fmt=AAZStrArgFormat( diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_update.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_update.py index 94388a1f2ad..f55d55fa2ff 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_update.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_update.py @@ -15,7 +15,7 @@ "changesafety changestate update", ) class Update(AAZCommand): - """Update a ChangeState + """Update a ChangeRecord """ _aaz_info = { @@ -46,7 +46,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( options=["-n", "--name", "--change-record-name"], - help="The name of the ChangeState resource.", + help="The name of the ChangeRecord resource.", required=True, id_part="name", fmt=AAZStrArgFormat( @@ -92,7 +92,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema.comments = AAZStrArg( options=["--comments"], arg_group="Properties", - help="Comments about the last update to the changeState resource.", + help="Comments about the last update to the ChangeRecord resource.", nullable=True, fmt=AAZStrArgFormat( max_length=2000, diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py index b0675f2703e..90ec9957a0f 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py @@ -44,7 +44,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( options=["--change-record-name"], - help="The name of the ChangeState resource.", + help="The name of the ChangeRecord resource.", required=True, fmt=AAZStrArgFormat( pattern="^[a-zA-Z0-9-]{3,100}$", diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py index 5ae9b057a26..6d5d45b6c50 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py @@ -45,7 +45,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( options=["--change-record-name"], - help="The name of the ChangeState resource.", + help="The name of the ChangeRecord resource.", required=True, id_part="name", fmt=AAZStrArgFormat( diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py index 33330f89e9b..5cd23ca1116 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py @@ -44,7 +44,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( options=["--change-record-name"], - help="The name of the ChangeState resource.", + help="The name of the ChangeRecord resource.", required=True, id_part="name", fmt=AAZStrArgFormat( diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py index 3f3bd68b9c9..9b8c093d0f0 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py @@ -46,7 +46,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( options=["--change-record-name"], - help="The name of the ChangeState resource.", + help="The name of the ChangeRecord resource.", required=True, id_part="name", fmt=AAZStrArgFormat( From 0201ab57cb46533b8668e5af126b0da1068da756 Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Mon, 26 Jan 2026 13:35:47 -0800 Subject: [PATCH 20/25] Update comments --- src/azure-changesafety/setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/azure-changesafety/setup.py b/src/azure-changesafety/setup.py index 9574e32ade5..d687b198f02 100644 --- a/src/azure-changesafety/setup.py +++ b/src/azure-changesafety/setup.py @@ -43,7 +43,8 @@ author_email='azpycli@microsoft.com', url='https://github.com/Azure/azure-cli-extensions/tree/main/src/azure-changesafety', classifiers=CLASSIFIERS, - packages=find_packages(exclude=["tests"]), + packages=find_packages(exclude=["tests", "build", "build.*"]), package_data={'azext_changesafety': ['azext_metadata.json']}, install_requires=DEPENDENCIES ) + From 74531c80d21632fddec42f854ba9631a35537eba Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Fri, 30 Jan 2026 14:17:23 -0800 Subject: [PATCH 21/25] Update CLI to rename changeRecords for 2026-01-01-repview --- src/azure-changesafety/README.md | 5 +- .../__cmd_group.py | 2 +- .../__init__.py | 0 .../changerecord}/__cmd_group.py | 4 +- .../changerecord}/__init__.py | 1 + .../changerecord}/_create.py | 72 +- .../changerecord}/_delete.py | 34 +- .../latest/changesafety/changerecord/_list.py | 615 ++++++++++++++++++ .../changerecord}/_show.py | 34 +- .../changerecord}/_update.py | 176 ++--- .../stagemap}/__cmd_group.py | 0 .../stagemap}/__init__.py | 1 + .../stagemap}/_create.py | 10 +- .../stagemap}/_delete.py | 10 +- .../aaz/latest/changesafety/stagemap/_list.py | 528 +++++++++++++++ .../stagemap}/_show.py | 10 +- .../stagemap}/_update.py | 14 +- .../stageprogression}/__cmd_group.py | 0 .../stageprogression}/__init__.py | 1 + .../stageprogression}/_create.py | 14 +- .../stageprogression}/_delete.py | 14 +- .../changesafety/stageprogression/_list.py | 427 ++++++++++++ .../stageprogression}/_show.py | 16 +- .../stageprogression}/_update.py | 18 +- .../azext_changesafety/commands.py | 10 +- .../azext_changesafety/custom.py | 66 +- 26 files changed, 1860 insertions(+), 222 deletions(-) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety => changesafety}/__cmd_group.py (95%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety => changesafety}/__init__.py (100%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/change_state => changesafety/changerecord}/__cmd_group.py (90%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/stage_progression => changesafety/changerecord}/__init__.py (96%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/change_state => changesafety/changerecord}/_create.py (92%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/change_state => changesafety/changerecord}/_delete.py (84%) create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_list.py rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/change_state => changesafety/changerecord}/_show.py (94%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/change_state => changesafety/changerecord}/_update.py (81%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/stage_map => changesafety/stagemap}/__cmd_group.py (100%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/change_state => changesafety/stagemap}/__init__.py (96%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/stage_map => changesafety/stagemap}/_create.py (99%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/stage_map => changesafety/stagemap}/_delete.py (95%) create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_list.py rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/stage_map => changesafety/stagemap}/_show.py (98%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/stage_map => changesafety/stagemap}/_update.py (98%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/stage_progression => changesafety/stageprogression}/__cmd_group.py (100%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/stage_map => changesafety/stageprogression}/__init__.py (96%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/stage_progression => changesafety/stageprogression}/_create.py (98%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/stage_progression => changesafety/stageprogression}/_delete.py (95%) create mode 100644 src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_list.py rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/stage_progression => changesafety/stageprogression}/_show.py (97%) rename src/azure-changesafety/azext_changesafety/aaz/latest/{change_safety/stage_progression => changesafety/stageprogression}/_update.py (98%) diff --git a/src/azure-changesafety/README.md b/src/azure-changesafety/README.md index b22204d16f7..e45916e1ece 100644 --- a/src/azure-changesafety/README.md +++ b/src/azure-changesafety/README.md @@ -19,15 +19,14 @@ az changesafety changerecord show # Display details for a ChangeRecord resour Run `az changesafety changerecord -h` to see full parameter details and examples. ## Examples -Create a ChangeRecord for a manual touch operation (e.g., VM maintenance): +Create a ChangeRecord for a manual touch operation (e.g., Deletes a traffic manager profile): ```bash az changesafety changerecord create \ -g MyResourceGroup \ -n changerecord-vm-maintenance \ --change-type ManualTouch \ --rollout-type Normal \ - --targets "resourceId=/subscriptions//resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH" \ - --stagemap-name myStageMap + --targets "subscriptionId=,operation=DELETES" \ ``` Create a ChangeRecord for an app deployment with a StageMap reference: diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/__cmd_group.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/__cmd_group.py similarity index 95% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/__cmd_group.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/__cmd_group.py index 1734459314b..e989d427e63 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/__cmd_group.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/__cmd_group.py @@ -15,7 +15,7 @@ "changesafety", ) class __CMDGroup(AAZCommandGroup): - """Manage Change Safety + """Manage Changesafety """ pass diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/__init__.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/__init__.py similarity index 100% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/__init__.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/__init__.py diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/__cmd_group.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/__cmd_group.py similarity index 90% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/__cmd_group.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/__cmd_group.py index 401be3b73af..31dc53638cc 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/__cmd_group.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/__cmd_group.py @@ -12,10 +12,10 @@ @register_command_group( - "changesafety changestate", + "changesafety changerecord", ) class __CMDGroup(AAZCommandGroup): - """Manage Change State + """Manage Change Record """ pass diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/__init__.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/__init__.py similarity index 96% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/__init__.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/__init__.py index a3db3e36481..c401f439385 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/__init__.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/__init__.py @@ -11,5 +11,6 @@ from .__cmd_group import * from ._create import * from ._delete import * +from ._list import * from ._show import * from ._update import * diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_create.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_create.py similarity index 92% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_create.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_create.py index 32624a3c2d2..d3785d855fe 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_create.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_create.py @@ -12,17 +12,17 @@ @register_command( - "changesafety changestate create", + "changesafety changerecord create", ) class Create(AAZCommand): """Create a ChangeRecord """ _aaz_info = { - "version": "2025-09-01-preview", + "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/Microsoft.ChangeSafety/changestates/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/Microsoft.ChangeSafety/changestates/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changerecords/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changerecords/{}", "2026-01-01-preview"], ] } @@ -42,7 +42,7 @@ def _build_arguments_schema(cls, *args, **kwargs): # define Arg Group "" _args_schema = cls._args_schema - _args_schema.change_state_name = AAZStrArg( + _args_schema.change_record_name = AAZStrArg( options=["-n", "--name", "--change-record-name"], help="The name of the ChangeRecord resource.", required=True, @@ -79,6 +79,11 @@ def _build_arguments_schema(cls, *args, **kwargs): protocol="iso", ), ) + _args_schema.change_definition = AAZObjectArg( + options=["--change-definition"], + arg_group="Properties", + help="Change request body and/or resource selection criteria used to identify the targeted resources.", + ) _args_schema.change_type = AAZStrArg( options=["--change-type"], arg_group="Properties", @@ -133,6 +138,25 @@ def _build_arguments_schema(cls, *args, **kwargs): help="Reference to the StageMap, defining progression.", ) + change_definition = cls._args_schema.change_definition + change_definition.details = AAZObjectArg( + options=["details"], + help="Free form object containing additional details for the change definition.", + required=True, + blank={}, + ) + change_definition.kind = AAZStrArg( + options=["kind"], + help="Kind of the change definition.", + required=True, + enum={"ApiOperations": "ApiOperations", "Targets": "Targets"}, + ) + change_definition.name = AAZStrArg( + options=["name"], + help="Name of the change definition.", + required=True, + ) + links = cls._args_schema.links links.Element = AAZObjectArg() @@ -254,12 +278,12 @@ def _build_arguments_schema(cls, *args, **kwargs): def _execute_operations(self): self.pre_operations() - condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True - condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) + condition_0 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) if condition_0: - self.ChangeStatesCreateOrUpdateAtSubscriptionLevel(ctx=self.ctx)() + self.ChangeRecordsCreateOrUpdateAtSubscriptionLevel(ctx=self.ctx)() if condition_1: - self.ChangeStatesCreateOrUpdate(ctx=self.ctx)() + self.ChangeRecordsCreateOrUpdate(ctx=self.ctx)() self.post_operations() @register_callback @@ -274,7 +298,7 @@ def _output(self, *args, **kwargs): result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) return result - class ChangeStatesCreateOrUpdateAtSubscriptionLevel(AAZHttpOperation): + class ChangeRecordsCreateOrUpdateAtSubscriptionLevel(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -288,7 +312,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}", **self.url_parameters ) @@ -304,7 +328,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -318,7 +342,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -350,6 +374,7 @@ def content(self): properties.set_prop("additionalData", AAZObjectType, ".additional_data") properties.set_prop("anticipatedEndTime", AAZStrType, ".anticipated_end_time", typ_kwargs={"flags": {"required": True}}) properties.set_prop("anticipatedStartTime", AAZStrType, ".anticipated_start_time", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("changeDefinition", AAZObjectType, ".change_definition", typ_kwargs={"flags": {"required": True}}) properties.set_prop("changeType", AAZStrType, ".change_type", typ_kwargs={"flags": {"required": True}}) properties.set_prop("comments", AAZStrType, ".comments") properties.set_prop("description", AAZStrType, ".description") @@ -360,6 +385,12 @@ def content(self): properties.set_prop("rolloutType", AAZStrType, ".rollout_type", typ_kwargs={"flags": {"required": True}}) properties.set_prop("stageMap", AAZObjectType, ".stage_map") + change_definition = _builder.get(".properties.changeDefinition") + if change_definition is not None: + change_definition.set_prop("details", AAZObjectType, ".details", typ_kwargs={"flags": {"required": True}}) + change_definition.set_prop("kind", AAZStrType, ".kind", typ_kwargs={"flags": {"required": True}}) + change_definition.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}}) + links = _builder.get(".properties.links") if links is not None: links.set_elements(AAZObjectType, ".") @@ -646,7 +677,7 @@ def _build_schema_on_200_201(cls): return cls._schema_on_200_201 - class ChangeStatesCreateOrUpdate(AAZHttpOperation): + class ChangeRecordsCreateOrUpdate(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -660,7 +691,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}", **self.url_parameters ) @@ -676,7 +707,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -694,7 +725,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -726,6 +757,7 @@ def content(self): properties.set_prop("additionalData", AAZObjectType, ".additional_data") properties.set_prop("anticipatedEndTime", AAZStrType, ".anticipated_end_time", typ_kwargs={"flags": {"required": True}}) properties.set_prop("anticipatedStartTime", AAZStrType, ".anticipated_start_time", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("changeDefinition", AAZObjectType, ".change_definition", typ_kwargs={"flags": {"required": True}}) properties.set_prop("changeType", AAZStrType, ".change_type", typ_kwargs={"flags": {"required": True}}) properties.set_prop("comments", AAZStrType, ".comments") properties.set_prop("description", AAZStrType, ".description") @@ -736,6 +768,12 @@ def content(self): properties.set_prop("rolloutType", AAZStrType, ".rollout_type", typ_kwargs={"flags": {"required": True}}) properties.set_prop("stageMap", AAZObjectType, ".stage_map") + change_definition = _builder.get(".properties.changeDefinition") + if change_definition is not None: + change_definition.set_prop("details", AAZObjectType, ".details", typ_kwargs={"flags": {"required": True}}) + change_definition.set_prop("kind", AAZStrType, ".kind", typ_kwargs={"flags": {"required": True}}) + change_definition.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}}) + links = _builder.get(".properties.links") if links is not None: links.set_elements(AAZObjectType, ".") diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_delete.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_delete.py similarity index 84% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_delete.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_delete.py index f10cd63643d..93c9535ae7a 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_delete.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_delete.py @@ -12,7 +12,7 @@ @register_command( - "changesafety changestate delete", + "changesafety changerecord delete", confirmation="Are you sure you want to perform this operation?", ) class Delete(AAZCommand): @@ -20,10 +20,10 @@ class Delete(AAZCommand): """ _aaz_info = { - "version": "2025-09-01-preview", + "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/Microsoft.ChangeSafety/changestates/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/Microsoft.ChangeSafety/changestates/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changerecords/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changerecords/{}", "2026-01-01-preview"], ] } @@ -42,7 +42,7 @@ def _build_arguments_schema(cls, *args, **kwargs): # define Arg Group "" _args_schema = cls._args_schema - _args_schema.change_state_name = AAZStrArg( + _args_schema.change_record_name = AAZStrArg( options=["-n", "--name", "--change-record-name"], help="The name of the ChangeRecord resource.", required=True, @@ -58,12 +58,12 @@ def _build_arguments_schema(cls, *args, **kwargs): def _execute_operations(self): self.pre_operations() - condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True - condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) + condition_0 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) if condition_0: - yield self.ChangeStatesDeleteAtSubscriptionLevel(ctx=self.ctx)() + yield self.ChangeRecordsDeleteAtSubscriptionLevel(ctx=self.ctx)() if condition_1: - yield self.ChangeStatesDelete(ctx=self.ctx)() + yield self.ChangeRecordsDelete(ctx=self.ctx)() self.post_operations() @register_callback @@ -74,7 +74,7 @@ def pre_operations(self): def post_operations(self): pass - class ChangeStatesDeleteAtSubscriptionLevel(AAZHttpOperation): + class ChangeRecordsDeleteAtSubscriptionLevel(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -113,7 +113,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}", **self.url_parameters ) @@ -129,7 +129,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -143,7 +143,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -155,7 +155,7 @@ def on_204(self, session): def on_200_201(self, session): pass - class ChangeStatesDelete(AAZHttpOperation): + class ChangeRecordsDelete(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -194,7 +194,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}", **self.url_parameters ) @@ -210,7 +210,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -228,7 +228,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_list.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_list.py new file mode 100644 index 00000000000..b9ee25f4a83 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_list.py @@ -0,0 +1,615 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "changesafety changerecord list", +) +class List(AAZCommand): + """List ChangeRecord resources by subscription ID + """ + + _aaz_info = { + "version": "2026-01-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changerecords", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changerecords", "2026-01-01-preview"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.resource_group = AAZResourceGroupNameArg() + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) + if condition_0: + self.ChangeRecordsListBySubscription(ctx=self.ctx)() + if condition_1: + self.ChangeRecordsListByResourceGroup(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance.value, client_flatten=True) + next_link = self.deserialize_output(self.ctx.vars.instance.next_link) + return result, next_link + + class ChangeRecordsListBySubscription(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeRecords", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-01-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + ) + _schema_on_200.value = AAZListType( + flags={"required": True}, + ) + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.additional_data = AAZObjectType( + serialized_name="additionalData", + ) + properties.anticipated_end_time = AAZStrType( + serialized_name="anticipatedEndTime", + flags={"required": True}, + ) + properties.anticipated_start_time = AAZStrType( + serialized_name="anticipatedStartTime", + flags={"required": True}, + ) + properties.change_definition = AAZObjectType( + serialized_name="changeDefinition", + flags={"required": True}, + ) + properties.change_type = AAZStrType( + serialized_name="changeType", + flags={"required": True}, + ) + properties.comments = AAZStrType() + properties.description = AAZStrType() + properties.links = AAZListType() + properties.orchestration_tool = AAZStrType( + serialized_name="orchestrationTool", + ) + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.release_label = AAZStrType( + serialized_name="releaseLabel", + ) + properties.rollout_type = AAZStrType( + serialized_name="rolloutType", + flags={"required": True}, + ) + properties.stage_map = AAZObjectType( + serialized_name="stageMap", + ) + properties.stage_map_snapshot = AAZListType( + serialized_name="stageMapSnapshot", + flags={"read_only": True}, + ) + properties.status = AAZStrType( + flags={"read_only": True}, + ) + + change_definition = cls._schema_on_200.value.Element.properties.change_definition + change_definition.details = AAZObjectType( + flags={"required": True}, + ) + change_definition.kind = AAZStrType( + flags={"required": True}, + ) + change_definition.name = AAZStrType( + flags={"required": True}, + ) + + links = cls._schema_on_200.value.Element.properties.links + links.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.links.Element + _element.description = AAZStrType() + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.uri = AAZStrType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200.value.Element.properties.parameters + parameters.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.parameters.Element + _element.metadata = AAZDictType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + metadata = cls._schema_on_200.value.Element.properties.parameters.Element.metadata + metadata.Element = AAZStrType() + + disc_array = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "array") + disc_array.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_array.default_value = AAZListType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values.Element = AAZAnyType() + + default_value = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value.Element = AAZAnyType() + + disc_number = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "number") + disc_number.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_number.default_value = AAZIntType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values.Element = AAZIntType() + + disc_object = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "object") + disc_object.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_object.default_value = AAZObjectType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values.Element = AAZDictType() + + _element = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element.Element = AAZAnyType() + + disc_string = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "string") + disc_string.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_string.default_value = AAZStrType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values.Element = AAZStrType() + + stage_map = cls._schema_on_200.value.Element.properties.stage_map + stage_map.parameters = AAZDictType() + stage_map.resource_id = AAZStrType( + serialized_name="resourceId", + ) + + parameters = cls._schema_on_200.value.Element.properties.stage_map.parameters + parameters.Element = AAZAnyType() + + stage_map_snapshot = cls._schema_on_200.value.Element.properties.stage_map_snapshot + stage_map_snapshot.Element = AAZAnyType() + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + class ChangeRecordsListByResourceGroup(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeRecords", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-01-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + ) + _schema_on_200.value = AAZListType( + flags={"required": True}, + ) + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.additional_data = AAZObjectType( + serialized_name="additionalData", + ) + properties.anticipated_end_time = AAZStrType( + serialized_name="anticipatedEndTime", + flags={"required": True}, + ) + properties.anticipated_start_time = AAZStrType( + serialized_name="anticipatedStartTime", + flags={"required": True}, + ) + properties.change_definition = AAZObjectType( + serialized_name="changeDefinition", + flags={"required": True}, + ) + properties.change_type = AAZStrType( + serialized_name="changeType", + flags={"required": True}, + ) + properties.comments = AAZStrType() + properties.description = AAZStrType() + properties.links = AAZListType() + properties.orchestration_tool = AAZStrType( + serialized_name="orchestrationTool", + ) + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.release_label = AAZStrType( + serialized_name="releaseLabel", + ) + properties.rollout_type = AAZStrType( + serialized_name="rolloutType", + flags={"required": True}, + ) + properties.stage_map = AAZObjectType( + serialized_name="stageMap", + ) + properties.stage_map_snapshot = AAZListType( + serialized_name="stageMapSnapshot", + flags={"read_only": True}, + ) + properties.status = AAZStrType( + flags={"read_only": True}, + ) + + change_definition = cls._schema_on_200.value.Element.properties.change_definition + change_definition.details = AAZObjectType( + flags={"required": True}, + ) + change_definition.kind = AAZStrType( + flags={"required": True}, + ) + change_definition.name = AAZStrType( + flags={"required": True}, + ) + + links = cls._schema_on_200.value.Element.properties.links + links.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.links.Element + _element.description = AAZStrType() + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.uri = AAZStrType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200.value.Element.properties.parameters + parameters.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.parameters.Element + _element.metadata = AAZDictType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + metadata = cls._schema_on_200.value.Element.properties.parameters.Element.metadata + metadata.Element = AAZStrType() + + disc_array = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "array") + disc_array.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_array.default_value = AAZListType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values.Element = AAZAnyType() + + default_value = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value.Element = AAZAnyType() + + disc_number = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "number") + disc_number.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_number.default_value = AAZIntType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values.Element = AAZIntType() + + disc_object = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "object") + disc_object.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_object.default_value = AAZObjectType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values.Element = AAZDictType() + + _element = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element.Element = AAZAnyType() + + disc_string = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "string") + disc_string.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_string.default_value = AAZStrType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values.Element = AAZStrType() + + stage_map = cls._schema_on_200.value.Element.properties.stage_map + stage_map.parameters = AAZDictType() + stage_map.resource_id = AAZStrType( + serialized_name="resourceId", + ) + + parameters = cls._schema_on_200.value.Element.properties.stage_map.parameters + parameters.Element = AAZAnyType() + + stage_map_snapshot = cls._schema_on_200.value.Element.properties.stage_map_snapshot + stage_map_snapshot.Element = AAZAnyType() + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + +__all__ = ["List"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_show.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_show.py similarity index 94% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_show.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_show.py index 4ca355981ac..43e37cae04b 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_show.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_show.py @@ -12,17 +12,17 @@ @register_command( - "changesafety changestate show", + "changesafety changerecord show", ) class Show(AAZCommand): """Get a ChangeRecord """ _aaz_info = { - "version": "2025-09-01-preview", + "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/Microsoft.ChangeSafety/changestates/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/Microsoft.ChangeSafety/changestates/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changerecords/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changerecords/{}", "2026-01-01-preview"], ] } @@ -42,7 +42,7 @@ def _build_arguments_schema(cls, *args, **kwargs): # define Arg Group "" _args_schema = cls._args_schema - _args_schema.change_state_name = AAZStrArg( + _args_schema.change_record_name = AAZStrArg( options=["-n", "--name", "--change-record-name"], help="The name of the ChangeRecord resource.", required=True, @@ -58,12 +58,12 @@ def _build_arguments_schema(cls, *args, **kwargs): def _execute_operations(self): self.pre_operations() - condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True - condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) + condition_0 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) if condition_0: - self.ChangeStatesGetAtSubscriptionLevel(ctx=self.ctx)() + self.ChangeRecordsGetAtSubscriptionLevel(ctx=self.ctx)() if condition_1: - self.ChangeStatesGet(ctx=self.ctx)() + self.ChangeRecordsGet(ctx=self.ctx)() self.post_operations() @register_callback @@ -78,7 +78,7 @@ def _output(self, *args, **kwargs): result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) return result - class ChangeStatesGetAtSubscriptionLevel(AAZHttpOperation): + class ChangeRecordsGetAtSubscriptionLevel(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -92,7 +92,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}", **self.url_parameters ) @@ -108,7 +108,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -122,7 +122,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -338,7 +338,7 @@ def _build_schema_on_200(cls): return cls._schema_on_200 - class ChangeStatesGet(AAZHttpOperation): + class ChangeRecordsGet(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -352,7 +352,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}", **self.url_parameters ) @@ -368,7 +368,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -386,7 +386,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_update.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_update.py similarity index 81% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_update.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_update.py index f55d55fa2ff..33313c3191c 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/_update.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/changerecord/_update.py @@ -12,17 +12,17 @@ @register_command( - "changesafety changestate update", + "changesafety changerecord update", ) class Update(AAZCommand): """Update a ChangeRecord """ _aaz_info = { - "version": "2025-09-01-preview", + "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changerecords/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changerecords/{}", "2026-01-01-preview"], ] } @@ -44,7 +44,7 @@ def _build_arguments_schema(cls, *args, **kwargs): # define Arg Group "" _args_schema = cls._args_schema - _args_schema.change_state_name = AAZStrArg( + _args_schema.change_record_name = AAZStrArg( options=["-n", "--name", "--change-record-name"], help="The name of the ChangeRecord resource.", required=True, @@ -83,6 +83,11 @@ def _build_arguments_schema(cls, *args, **kwargs): protocol="iso", ), ) + _args_schema.change_definition = AAZObjectArg( + options=["--change-definition"], + arg_group="Properties", + help="Change request body and/or resource selection criteria used to identify the targeted resources.", + ) _args_schema.change_type = AAZStrArg( options=["--change-type"], arg_group="Properties", @@ -144,6 +149,22 @@ def _build_arguments_schema(cls, *args, **kwargs): nullable=True, ) + change_definition = cls._args_schema.change_definition + change_definition.details = AAZObjectArg( + options=["details"], + help="Free form object containing additional details for the change definition.", + blank={}, + ) + change_definition.kind = AAZStrArg( + options=["kind"], + help="Kind of the change definition.", + enum={"ApiOperations": "ApiOperations", "Targets": "Targets"}, + ) + change_definition.name = AAZStrArg( + options=["name"], + help="Name of the change definition.", + ) + links = cls._args_schema.links links.Element = AAZObjectArg( nullable=True, @@ -295,22 +316,22 @@ def _build_arguments_schema(cls, *args, **kwargs): def _execute_operations(self): self.pre_operations() - condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True - condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) - condition_2 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True - condition_3 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) + condition_0 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) + condition_2 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_3 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) if condition_0: - self.ChangeStatesGetAtSubscriptionLevel(ctx=self.ctx)() + self.ChangeRecordsGetAtSubscriptionLevel(ctx=self.ctx)() if condition_1: - self.ChangeStatesGet(ctx=self.ctx)() + self.ChangeRecordsGet(ctx=self.ctx)() self.pre_instance_update(self.ctx.vars.instance) self.InstanceUpdateByJson(ctx=self.ctx)() self.InstanceUpdateByGeneric(ctx=self.ctx)() self.post_instance_update(self.ctx.vars.instance) if condition_2: - self.ChangeStatesCreateOrUpdateAtSubscriptionLevel(ctx=self.ctx)() + self.ChangeRecordsCreateOrUpdateAtSubscriptionLevel(ctx=self.ctx)() if condition_3: - self.ChangeStatesCreateOrUpdate(ctx=self.ctx)() + self.ChangeRecordsCreateOrUpdate(ctx=self.ctx)() self.post_operations() @register_callback @@ -333,7 +354,7 @@ def _output(self, *args, **kwargs): result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) return result - class ChangeStatesGetAtSubscriptionLevel(AAZHttpOperation): + class ChangeRecordsGetAtSubscriptionLevel(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -347,7 +368,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}", **self.url_parameters ) @@ -363,7 +384,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -377,7 +398,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -408,11 +429,11 @@ def _build_schema_on_200(cls): return cls._schema_on_200 cls._schema_on_200 = AAZObjectType() - _UpdateHelper._build_schema_change_state_read(cls._schema_on_200) + _UpdateHelper._build_schema_change_record_read(cls._schema_on_200) return cls._schema_on_200 - class ChangeStatesGet(AAZHttpOperation): + class ChangeRecordsGet(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -426,7 +447,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}", **self.url_parameters ) @@ -442,7 +463,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -460,7 +481,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -491,11 +512,11 @@ def _build_schema_on_200(cls): return cls._schema_on_200 cls._schema_on_200 = AAZObjectType() - _UpdateHelper._build_schema_change_state_read(cls._schema_on_200) + _UpdateHelper._build_schema_change_record_read(cls._schema_on_200) return cls._schema_on_200 - class ChangeStatesCreateOrUpdateAtSubscriptionLevel(AAZHttpOperation): + class ChangeRecordsCreateOrUpdateAtSubscriptionLevel(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -509,7 +530,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}", **self.url_parameters ) @@ -525,7 +546,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -539,7 +560,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -582,11 +603,11 @@ def _build_schema_on_200_201(cls): return cls._schema_on_200_201 cls._schema_on_200_201 = AAZObjectType() - _UpdateHelper._build_schema_change_state_read(cls._schema_on_200_201) + _UpdateHelper._build_schema_change_record_read(cls._schema_on_200_201) return cls._schema_on_200_201 - class ChangeStatesCreateOrUpdate(AAZHttpOperation): + class ChangeRecordsCreateOrUpdate(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -600,7 +621,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}", **self.url_parameters ) @@ -616,7 +637,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -634,7 +655,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -677,7 +698,7 @@ def _build_schema_on_200_201(cls): return cls._schema_on_200_201 cls._schema_on_200_201 = AAZObjectType() - _UpdateHelper._build_schema_change_state_read(cls._schema_on_200_201) + _UpdateHelper._build_schema_change_record_read(cls._schema_on_200_201) return cls._schema_on_200_201 @@ -699,6 +720,7 @@ def _update_instance(self, instance): properties.set_prop("additionalData", AAZObjectType, ".additional_data") properties.set_prop("anticipatedEndTime", AAZStrType, ".anticipated_end_time", typ_kwargs={"flags": {"required": True}}) properties.set_prop("anticipatedStartTime", AAZStrType, ".anticipated_start_time", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("changeDefinition", AAZObjectType, ".change_definition", typ_kwargs={"flags": {"required": True}}) properties.set_prop("changeType", AAZStrType, ".change_type", typ_kwargs={"flags": {"required": True}}) properties.set_prop("comments", AAZStrType, ".comments") properties.set_prop("description", AAZStrType, ".description") @@ -709,6 +731,12 @@ def _update_instance(self, instance): properties.set_prop("rolloutType", AAZStrType, ".rollout_type", typ_kwargs={"flags": {"required": True}}) properties.set_prop("stageMap", AAZObjectType, ".stage_map") + change_definition = _builder.get(".properties.changeDefinition") + if change_definition is not None: + change_definition.set_prop("details", AAZObjectType, ".details", typ_kwargs={"flags": {"required": True}}) + change_definition.set_prop("kind", AAZStrType, ".kind", typ_kwargs={"flags": {"required": True}}) + change_definition.set_prop("name", AAZStrType, ".name", typ_kwargs={"flags": {"required": True}}) + links = _builder.get(".properties.links") if links is not None: links.set_elements(AAZObjectType, ".") @@ -806,37 +834,37 @@ def __call__(self, *args, **kwargs): class _UpdateHelper: """Helper class for Update""" - _schema_change_state_read = None + _schema_change_record_read = None @classmethod - def _build_schema_change_state_read(cls, _schema): - if cls._schema_change_state_read is not None: - _schema.id = cls._schema_change_state_read.id - _schema.name = cls._schema_change_state_read.name - _schema.properties = cls._schema_change_state_read.properties - _schema.system_data = cls._schema_change_state_read.system_data - _schema.type = cls._schema_change_state_read.type + def _build_schema_change_record_read(cls, _schema): + if cls._schema_change_record_read is not None: + _schema.id = cls._schema_change_record_read.id + _schema.name = cls._schema_change_record_read.name + _schema.properties = cls._schema_change_record_read.properties + _schema.system_data = cls._schema_change_record_read.system_data + _schema.type = cls._schema_change_record_read.type return - cls._schema_change_state_read = _schema_change_state_read = AAZObjectType() + cls._schema_change_record_read = _schema_change_record_read = AAZObjectType() - change_state_read = _schema_change_state_read - change_state_read.id = AAZStrType( + change_record_read = _schema_change_record_read + change_record_read.id = AAZStrType( flags={"read_only": True}, ) - change_state_read.name = AAZStrType( + change_record_read.name = AAZStrType( flags={"read_only": True}, ) - change_state_read.properties = AAZObjectType() - change_state_read.system_data = AAZObjectType( + change_record_read.properties = AAZObjectType() + change_record_read.system_data = AAZObjectType( serialized_name="systemData", flags={"read_only": True}, ) - change_state_read.type = AAZStrType( + change_record_read.type = AAZStrType( flags={"read_only": True}, ) - properties = _schema_change_state_read.properties + properties = _schema_change_record_read.properties properties.additional_data = AAZObjectType( serialized_name="additionalData", ) @@ -885,7 +913,7 @@ def _build_schema_change_state_read(cls, _schema): flags={"read_only": True}, ) - change_definition = _schema_change_state_read.properties.change_definition + change_definition = _schema_change_record_read.properties.change_definition change_definition.details = AAZObjectType( flags={"required": True}, ) @@ -896,10 +924,10 @@ def _build_schema_change_state_read(cls, _schema): flags={"required": True}, ) - links = _schema_change_state_read.properties.links + links = _schema_change_record_read.properties.links links.Element = AAZObjectType() - _element = _schema_change_state_read.properties.links.Element + _element = _schema_change_record_read.properties.links.Element _element.description = AAZStrType() _element.name = AAZStrType( flags={"required": True}, @@ -908,19 +936,19 @@ def _build_schema_change_state_read(cls, _schema): flags={"required": True}, ) - parameters = _schema_change_state_read.properties.parameters + parameters = _schema_change_record_read.properties.parameters parameters.Element = AAZObjectType() - _element = _schema_change_state_read.properties.parameters.Element + _element = _schema_change_record_read.properties.parameters.Element _element.metadata = AAZDictType() _element.type = AAZStrType( flags={"required": True}, ) - metadata = _schema_change_state_read.properties.parameters.Element.metadata + metadata = _schema_change_record_read.properties.parameters.Element.metadata metadata.Element = AAZStrType() - disc_array = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "array") + disc_array = _schema_change_record_read.properties.parameters.Element.discriminate_by("type", "array") disc_array.allowed_values = AAZListType( serialized_name="allowedValues", ) @@ -928,13 +956,13 @@ def _build_schema_change_state_read(cls, _schema): serialized_name="defaultValue", ) - allowed_values = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values = _schema_change_record_read.properties.parameters.Element.discriminate_by("type", "array").allowed_values allowed_values.Element = AAZAnyType() - default_value = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value = _schema_change_record_read.properties.parameters.Element.discriminate_by("type", "array").default_value default_value.Element = AAZAnyType() - disc_number = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "number") + disc_number = _schema_change_record_read.properties.parameters.Element.discriminate_by("type", "number") disc_number.allowed_values = AAZListType( serialized_name="allowedValues", ) @@ -942,10 +970,10 @@ def _build_schema_change_state_read(cls, _schema): serialized_name="defaultValue", ) - allowed_values = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values = _schema_change_record_read.properties.parameters.Element.discriminate_by("type", "number").allowed_values allowed_values.Element = AAZIntType() - disc_object = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "object") + disc_object = _schema_change_record_read.properties.parameters.Element.discriminate_by("type", "object") disc_object.allowed_values = AAZListType( serialized_name="allowedValues", ) @@ -953,13 +981,13 @@ def _build_schema_change_state_read(cls, _schema): serialized_name="defaultValue", ) - allowed_values = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values = _schema_change_record_read.properties.parameters.Element.discriminate_by("type", "object").allowed_values allowed_values.Element = AAZDictType() - _element = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element = _schema_change_record_read.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element _element.Element = AAZAnyType() - disc_string = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "string") + disc_string = _schema_change_record_read.properties.parameters.Element.discriminate_by("type", "string") disc_string.allowed_values = AAZListType( serialized_name="allowedValues", ) @@ -967,22 +995,22 @@ def _build_schema_change_state_read(cls, _schema): serialized_name="defaultValue", ) - allowed_values = _schema_change_state_read.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values = _schema_change_record_read.properties.parameters.Element.discriminate_by("type", "string").allowed_values allowed_values.Element = AAZStrType() - stage_map = _schema_change_state_read.properties.stage_map + stage_map = _schema_change_record_read.properties.stage_map stage_map.parameters = AAZDictType() stage_map.resource_id = AAZStrType( serialized_name="resourceId", ) - parameters = _schema_change_state_read.properties.stage_map.parameters + parameters = _schema_change_record_read.properties.stage_map.parameters parameters.Element = AAZAnyType() - stage_map_snapshot = _schema_change_state_read.properties.stage_map_snapshot + stage_map_snapshot = _schema_change_record_read.properties.stage_map_snapshot stage_map_snapshot.Element = AAZAnyType() - system_data = _schema_change_state_read.system_data + system_data = _schema_change_record_read.system_data system_data.created_at = AAZStrType( serialized_name="createdAt", ) @@ -1002,11 +1030,11 @@ def _build_schema_change_state_read(cls, _schema): serialized_name="lastModifiedByType", ) - _schema.id = cls._schema_change_state_read.id - _schema.name = cls._schema_change_state_read.name - _schema.properties = cls._schema_change_state_read.properties - _schema.system_data = cls._schema_change_state_read.system_data - _schema.type = cls._schema_change_state_read.type + _schema.id = cls._schema_change_record_read.id + _schema.name = cls._schema_change_record_read.name + _schema.properties = cls._schema_change_record_read.properties + _schema.system_data = cls._schema_change_record_read.system_data + _schema.type = cls._schema_change_record_read.type __all__ = ["Update"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/__cmd_group.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/__cmd_group.py similarity index 100% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/__cmd_group.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/__cmd_group.py diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/__init__.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/__init__.py similarity index 96% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/__init__.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/__init__.py index a3db3e36481..c401f439385 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/__init__.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/__init__.py @@ -11,5 +11,6 @@ from .__cmd_group import * from ._create import * from ._delete import * +from ._list import * from ._show import * from ._update import * diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_create.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_create.py similarity index 99% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_create.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_create.py index a0621b795ea..e9466c3f7fa 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_create.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_create.py @@ -19,10 +19,10 @@ class Create(AAZCommand): """ _aaz_info = { - "version": "2025-09-01-preview", + "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/managementgroups/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], + ["mgmt-plane", "/managementgroups/{}/providers/microsoft.changesafety/stagemaps/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/stagemaps/{}", "2026-01-01-preview"], ] } @@ -272,7 +272,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -594,7 +594,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_delete.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_delete.py similarity index 95% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_delete.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_delete.py index 57b3de942df..b6c4811162e 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_delete.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_delete.py @@ -20,10 +20,10 @@ class Delete(AAZCommand): """ _aaz_info = { - "version": "2025-09-01-preview", + "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/managementgroups/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], + ["mgmt-plane", "/managementgroups/{}/providers/microsoft.changesafety/stagemaps/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/stagemaps/{}", "2026-01-01-preview"], ] } @@ -127,7 +127,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -185,7 +185,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_list.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_list.py new file mode 100644 index 00000000000..2975a8f9cd1 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_list.py @@ -0,0 +1,528 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "changesafety stagemap list", +) +class List(AAZCommand): + """List StageMap resources by resource group + """ + + _aaz_info = { + "version": "2026-01-01-preview", + "resources": [ + ["mgmt-plane", "/managementgroups/{}/providers/microsoft.changesafety/stagemaps", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/stagemaps", "2026-01-01-preview"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.management_group_name = AAZStrArg( + options=["--management-group-name"], + help="The name of the management group. The name is case insensitive.", + fmt=AAZStrArgFormat( + max_length=90, + min_length=1, + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.args.management_group_name) + condition_1 = has_value(self.ctx.subscription_id) + if condition_0: + self.StageMapsListByManagementGroup(ctx=self.ctx)() + if condition_1: + self.StageMapsListBySubscription(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance.value, client_flatten=True) + next_link = self.deserialize_output(self.ctx.vars.instance.next_link) + return result, next_link + + class StageMapsListByManagementGroup(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/managementGroups/{managementGroupName}/providers/Microsoft.ChangeSafety/stageMaps", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "managementGroupName", self.ctx.args.management_group_name, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-01-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + ) + _schema_on_200.value = AAZListType( + flags={"required": True}, + ) + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.stages = AAZListType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200.value.Element.properties.parameters + parameters.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.parameters.Element + _element.metadata = AAZDictType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + metadata = cls._schema_on_200.value.Element.properties.parameters.Element.metadata + metadata.Element = AAZStrType() + + disc_array = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "array") + disc_array.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_array.default_value = AAZListType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values.Element = AAZAnyType() + + default_value = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value.Element = AAZAnyType() + + disc_number = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "number") + disc_number.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_number.default_value = AAZIntType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values.Element = AAZIntType() + + disc_object = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "object") + disc_object.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_object.default_value = AAZObjectType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values.Element = AAZDictType() + + _element = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element.Element = AAZAnyType() + + disc_string = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "string") + disc_string.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_string.default_value = AAZStrType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values.Element = AAZStrType() + + stages = cls._schema_on_200.value.Element.properties.stages + stages.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.stages.Element + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.nested_stage_map = AAZObjectType( + serialized_name="nestedStageMap", + ) + _element.sequence = AAZIntType( + flags={"required": True}, + ) + _element.stage_variables = AAZDictType( + serialized_name="stageVariables", + ) + + nested_stage_map = cls._schema_on_200.value.Element.properties.stages.Element.nested_stage_map + nested_stage_map.parameters = AAZDictType() + nested_stage_map.resource_id = AAZStrType( + serialized_name="resourceId", + ) + + parameters = cls._schema_on_200.value.Element.properties.stages.Element.nested_stage_map.parameters + parameters.Element = AAZAnyType() + + stage_variables = cls._schema_on_200.value.Element.properties.stages.Element.stage_variables + stage_variables.Element = AAZAnyType() + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + class StageMapsListBySubscription(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/stageMaps", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-01-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + ) + _schema_on_200.value = AAZListType( + flags={"required": True}, + ) + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.stages = AAZListType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200.value.Element.properties.parameters + parameters.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.parameters.Element + _element.metadata = AAZDictType() + _element.type = AAZStrType( + flags={"required": True}, + ) + + metadata = cls._schema_on_200.value.Element.properties.parameters.Element.metadata + metadata.Element = AAZStrType() + + disc_array = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "array") + disc_array.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_array.default_value = AAZListType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "array").allowed_values + allowed_values.Element = AAZAnyType() + + default_value = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "array").default_value + default_value.Element = AAZAnyType() + + disc_number = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "number") + disc_number.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_number.default_value = AAZIntType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "number").allowed_values + allowed_values.Element = AAZIntType() + + disc_object = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "object") + disc_object.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_object.default_value = AAZObjectType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "object").allowed_values + allowed_values.Element = AAZDictType() + + _element = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "object").allowed_values.Element + _element.Element = AAZAnyType() + + disc_string = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "string") + disc_string.allowed_values = AAZListType( + serialized_name="allowedValues", + ) + disc_string.default_value = AAZStrType( + serialized_name="defaultValue", + ) + + allowed_values = cls._schema_on_200.value.Element.properties.parameters.Element.discriminate_by("type", "string").allowed_values + allowed_values.Element = AAZStrType() + + stages = cls._schema_on_200.value.Element.properties.stages + stages.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.stages.Element + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.nested_stage_map = AAZObjectType( + serialized_name="nestedStageMap", + ) + _element.sequence = AAZIntType( + flags={"required": True}, + ) + _element.stage_variables = AAZDictType( + serialized_name="stageVariables", + ) + + nested_stage_map = cls._schema_on_200.value.Element.properties.stages.Element.nested_stage_map + nested_stage_map.parameters = AAZDictType() + nested_stage_map.resource_id = AAZStrType( + serialized_name="resourceId", + ) + + parameters = cls._schema_on_200.value.Element.properties.stages.Element.nested_stage_map.parameters + parameters.Element = AAZAnyType() + + stage_variables = cls._schema_on_200.value.Element.properties.stages.Element.stage_variables + stage_variables.Element = AAZAnyType() + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + +__all__ = ["List"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_show.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_show.py similarity index 98% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_show.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_show.py index fe3cc4289b6..d6bb7b1e65b 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_show.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_show.py @@ -19,10 +19,10 @@ class Show(AAZCommand): """ _aaz_info = { - "version": "2025-09-01-preview", + "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/managementgroups/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], + ["mgmt-plane", "/managementgroups/{}/providers/microsoft.changesafety/stagemaps/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/stagemaps/{}", "2026-01-01-preview"], ] } @@ -128,7 +128,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -343,7 +343,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_update.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_update.py similarity index 98% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_update.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_update.py index 81f3de1cea2..dbafa71b2f5 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/_update.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stagemap/_update.py @@ -19,10 +19,10 @@ class Update(AAZCommand): """ _aaz_info = { - "version": "2025-09-01-preview", + "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/managementgroups/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/stagemaps/{}", "2025-09-01-preview"], + ["mgmt-plane", "/managementgroups/{}/providers/microsoft.changesafety/stagemaps/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/stagemaps/{}", "2026-01-01-preview"], ] } @@ -326,7 +326,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -405,7 +405,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -484,7 +484,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -575,7 +575,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/__cmd_group.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/__cmd_group.py similarity index 100% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/__cmd_group.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/__cmd_group.py diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/__init__.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/__init__.py similarity index 96% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/__init__.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/__init__.py index a3db3e36481..c401f439385 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_map/__init__.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/__init__.py @@ -11,5 +11,6 @@ from .__cmd_group import * from ._create import * from ._delete import * +from ._list import * from ._show import * from ._update import * diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_create.py similarity index 98% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_create.py index 90ec9957a0f..061da768fd8 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_create.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_create.py @@ -19,10 +19,10 @@ class Create(AAZCommand): """ _aaz_info = { - "version": "2025-09-01-preview", + "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], ] } @@ -43,8 +43,8 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( - options=["--change-record-name"], - help="The name of the ChangeRecord resource.", + options=["--change-state-name"], + help="The name of the ChangeState resource.", required=True, fmt=AAZStrArgFormat( pattern="^[a-zA-Z0-9-]{3,100}$", @@ -211,7 +211,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -418,7 +418,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_delete.py similarity index 95% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_delete.py index 6d5d45b6c50..f323f0f8ec0 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_delete.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_delete.py @@ -20,10 +20,10 @@ class Delete(AAZCommand): """ _aaz_info = { - "version": "2025-09-01-preview", + "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], ] } @@ -44,8 +44,8 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( - options=["--change-record-name"], - help="The name of the ChangeRecord resource.", + options=["--change-state-name"], + help="The name of the ChangeState resource.", required=True, id_part="name", fmt=AAZStrArgFormat( @@ -136,7 +136,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -202,7 +202,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_list.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_list.py new file mode 100644 index 00000000000..5becaa4e614 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_list.py @@ -0,0 +1,427 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from azure.cli.core.aaz import * + + +@register_command( + "changesafety stageprogression list", +) +class List(AAZCommand): + """List ChangeRecordStageProgression resources by ChangeRecord + """ + + _aaz_info = { + "version": "2026-01-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changerecords/{}/stageprogressions", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changerecords/{}/stageprogressions", "2026-01-01-preview"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.change_record_name = AAZStrArg( + options=["--change-record-name"], + help="The name of the ChangeRecord resource.", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,100}$", + max_length=100, + min_length=3, + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg() + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) + if condition_0: + self.ChangeRecordStageProgressionsListByParentAtSubscriptionLevel(ctx=self.ctx)() + if condition_1: + self.ChangeRecordStageProgressionsListByParent(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance.value, client_flatten=True) + next_link = self.deserialize_output(self.ctx.vars.instance.next_link) + return result, next_link + + class ChangeRecordStageProgressionsListByParentAtSubscriptionLevel(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}/stageProgressions", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeRecordName", self.ctx.args.change_record_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-01-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + ) + _schema_on_200.value = AAZListType( + flags={"required": True}, + ) + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.additional_data = AAZObjectType( + serialized_name="additionalData", + ) + properties.comments = AAZStrType() + properties.links = AAZListType() + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.sequence = AAZIntType( + flags={"read_only": True}, + ) + properties.stage_reference = AAZStrType( + serialized_name="stageReference", + flags={"required": True}, + ) + properties.stage_variables = AAZDictType( + serialized_name="stageVariables", + ) + properties.status = AAZStrType( + flags={"required": True}, + ) + + links = cls._schema_on_200.value.Element.properties.links + links.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.links.Element + _element.description = AAZStrType() + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.uri = AAZStrType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200.value.Element.properties.parameters + parameters.Element = AAZAnyType() + + stage_variables = cls._schema_on_200.value.Element.properties.stage_variables + stage_variables.Element = AAZAnyType() + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + class ChangeRecordStageProgressionsListByParent(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}/stageProgressions", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "changeRecordName", self.ctx.args.change_record_name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-01-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + ) + _schema_on_200.value = AAZListType( + flags={"required": True}, + ) + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.additional_data = AAZObjectType( + serialized_name="additionalData", + ) + properties.comments = AAZStrType() + properties.links = AAZListType() + properties.parameters = AAZDictType() + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.sequence = AAZIntType( + flags={"read_only": True}, + ) + properties.stage_reference = AAZStrType( + serialized_name="stageReference", + flags={"required": True}, + ) + properties.stage_variables = AAZDictType( + serialized_name="stageVariables", + ) + properties.status = AAZStrType( + flags={"required": True}, + ) + + links = cls._schema_on_200.value.Element.properties.links + links.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.links.Element + _element.description = AAZStrType() + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.uri = AAZStrType( + flags={"required": True}, + ) + + parameters = cls._schema_on_200.value.Element.properties.parameters + parameters.Element = AAZAnyType() + + stage_variables = cls._schema_on_200.value.Element.properties.stage_variables + stage_variables.Element = AAZAnyType() + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + +__all__ = ["List"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_show.py similarity index 97% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_show.py index 5cd23ca1116..ed69e0c3643 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_show.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_show.py @@ -12,17 +12,17 @@ @register_command( - "change-safety stage-progression show", + "changesafety stageprogression show", ) class Show(AAZCommand): """Get a StageProgression """ _aaz_info = { - "version": "2025-09-01-preview", + "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], ] } @@ -43,8 +43,8 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( - options=["--change-record-name"], - help="The name of the ChangeRecord resource.", + options=["--change-state-name"], + help="The name of the ChangeState resource.", required=True, id_part="name", fmt=AAZStrArgFormat( @@ -137,7 +137,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -302,7 +302,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_update.py similarity index 98% rename from src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py rename to src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_update.py index 9b8c093d0f0..007d5e520eb 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/stage_progression/_update.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_update.py @@ -19,10 +19,10 @@ class Update(AAZCommand): """ _aaz_info = { - "version": "2025-09-01-preview", + "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2025-09-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], ] } @@ -45,8 +45,8 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema = cls._args_schema _args_schema.change_state_name = AAZStrArg( - options=["--change-record-name"], - help="The name of the ChangeRecord resource.", + options=["--change-state-name"], + help="The name of the ChangeState resource.", required=True, id_part="name", fmt=AAZStrArgFormat( @@ -243,7 +243,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -330,7 +330,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -413,7 +413,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } @@ -512,7 +512,7 @@ def url_parameters(self): def query_parameters(self): parameters = { **self.serialize_query_param( - "api-version", "2025-09-01-preview", + "api-version", "2026-01-01-preview", required=True, ), } diff --git a/src/azure-changesafety/azext_changesafety/commands.py b/src/azure-changesafety/azext_changesafety/commands.py index a093c096786..d256f3ec854 100644 --- a/src/azure-changesafety/azext_changesafety/commands.py +++ b/src/azure-changesafety/azext_changesafety/commands.py @@ -9,12 +9,12 @@ # pylint: disable=too-many-statements def load_command_table(self, _): # pylint: disable=unused-argument - from .custom import ChangeStateCreate, ChangeStateUpdate, ChangeStateDelete, ChangeStateShow + from .custom import ChangeRecordCreate, ChangeRecordUpdate, ChangeRecordDelete, ChangeRecordShow - create_command = ChangeStateCreate(loader=self) - update_command = ChangeStateUpdate(loader=self) - delete_command = ChangeStateDelete(loader=self) - show_command = ChangeStateShow(loader=self) + create_command = ChangeRecordCreate(loader=self) + update_command = ChangeRecordUpdate(loader=self) + delete_command = ChangeRecordDelete(loader=self) + show_command = ChangeRecordShow(loader=self) self.command_table['changesafety changerecord create'] = create_command self.command_table['changesafety changerecord update'] = update_command diff --git a/src/azure-changesafety/azext_changesafety/custom.py b/src/azure-changesafety/azext_changesafety/custom.py index 0c21530622b..bd608c83aa4 100644 --- a/src/azure-changesafety/azext_changesafety/custom.py +++ b/src/azure-changesafety/azext_changesafety/custom.py @@ -29,11 +29,11 @@ _ELEMENT_APPEND_KEY, ) from azure.cli.core.azclierror import InvalidArgumentValueError -from azext_changesafety.aaz.latest.change_safety.change_state import ( - Create as _ChangeStateCreate, - Update as _ChangeStateUpdate, - Show as _ChangeStateShow, - Delete as _ChangeStateDelete, +from azext_changesafety.aaz.latest.changesafety.changerecord import ( + Create as _ChangeRecordCreate, + Update as _ChangeRecordUpdate, + Show as _ChangeRecordShow, + Delete as _ChangeRecordDelete, ) @@ -148,10 +148,10 @@ def process(item): def _custom_show_schema_builder(): # Import the generated Show class - from azext_changesafety.aaz.latest.change_safety.change_state._show import Show as GeneratedShow + from azext_changesafety.aaz.latest.changesafety.changerecord._show import Show as GeneratedShow # Get the base schema from the generated code - base_schema = GeneratedShow.ChangeStatesGet._build_schema_on_200() + base_schema = GeneratedShow.ChangeRecordsGet._build_schema_on_200() # Inject/override the targets schema change_definition = base_schema.properties.change_definition @@ -168,7 +168,7 @@ def _custom_show_schema_builder(): return base_schema -class ChangeStateCreate(_ChangeStateCreate): +class ChangeRecordCreate(_ChangeRecordCreate): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._raw_targets = [] @@ -312,7 +312,7 @@ def _build_change_definition(self): """Build the changeDefinition object with targets""" targets = self._parse_targets(self._raw_targets) self._parsed_targets = targets - change_arg = self.ctx.args.change_state_name + change_arg = self.ctx.args.change_record_name change_name = ( change_arg.to_serialized_data() if has_value(change_arg) @@ -390,22 +390,22 @@ def pre_instance_create(self): # The changeDefinition will be set in the content property of the HTTP operations pass - class ChangeStatesCreateOrUpdateAtSubscriptionLevel( - _ChangeStateCreate.ChangeStatesCreateOrUpdateAtSubscriptionLevel): + class ChangeRecordsCreateOrUpdateAtSubscriptionLevel( + _ChangeRecordCreate.ChangeRecordsCreateOrUpdateAtSubscriptionLevel): @property def content(self): content = super().content return _inject_change_definition_into_content(content, self.ctx) - class ChangeStatesCreateOrUpdate( - _ChangeStateCreate.ChangeStatesCreateOrUpdate): + class ChangeRecordsCreateOrUpdate( + _ChangeRecordCreate.ChangeRecordsCreateOrUpdate): @property def content(self): content = super().content return _inject_change_definition_into_content(content, self.ctx) -class ChangeStateUpdate(_ChangeStateUpdate): +class ChangeRecordUpdate(_ChangeRecordUpdate): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._raw_targets = [] @@ -485,7 +485,7 @@ def _build_change_definition(self): """Build the changeDefinition object with targets""" targets = self._parse_targets(self._raw_targets) self._parsed_targets = targets - change_arg = self.ctx.args.change_state_name + change_arg = self.ctx.args.change_record_name change_name = ( change_arg.to_serialized_data() if has_value(change_arg) @@ -609,28 +609,28 @@ def pre_instance_update(self, instance): # The changeDefinition will be set in the content property of the HTTP operations pass - class ChangeStatesCreateOrUpdateAtSubscriptionLevel( - _ChangeStateUpdate.ChangeStatesCreateOrUpdateAtSubscriptionLevel): + class ChangeRecordsCreateOrUpdateAtSubscriptionLevel( + _ChangeRecordUpdate.ChangeRecordsCreateOrUpdateAtSubscriptionLevel): @property def content(self): content = super().content return _inject_change_definition_into_content(content, self.ctx) - class ChangeStatesCreateOrUpdate( - _ChangeStateUpdate.ChangeStatesCreateOrUpdate): + class ChangeRecordsCreateOrUpdate( + _ChangeRecordUpdate.ChangeRecordsCreateOrUpdate): @property def content(self): content = super().content return _inject_change_definition_into_content(content, self.ctx) -class ChangeStateShow(_ChangeStateShow): +class ChangeRecordShow(_ChangeRecordShow): def _output(self, *args, **kwargs): result = super()._output(*args, **kwargs) # Optionally inject targets schema into result if needed return result - class ChangeStatesGetAtSubscriptionLevel(_ChangeStateShow.ChangeStatesGetAtSubscriptionLevel): + class ChangeRecordsGetAtSubscriptionLevel(_ChangeRecordShow.ChangeRecordsGetAtSubscriptionLevel): def on_200(self, session): data = self.deserialize_http_content(session) self.ctx.set_var( @@ -639,7 +639,7 @@ def on_200(self, session): schema_builder=_custom_show_schema_builder ) - class ChangeStatesGet(_ChangeStateShow.ChangeStatesGet): + class ChangeRecordsGet(_ChangeRecordShow.ChangeRecordsGet): def on_200(self, session): data = self.deserialize_http_content(session) self.ctx.set_var( @@ -649,12 +649,12 @@ def on_200(self, session): ) -class ChangeStateDelete(_ChangeStateDelete): +class ChangeRecordDelete(_ChangeRecordDelete): pass -ChangeStateCreate.AZ_HELP = { - **ChangeStateCreate.AZ_HELP, +ChangeRecordCreate.AZ_HELP = { + **ChangeRecordCreate.AZ_HELP, "examples": [ { "name": "Create with StageMap reference and status link", @@ -699,8 +699,8 @@ class ChangeStateDelete(_ChangeStateDelete): ], } -ChangeStateUpdate.AZ_HELP = { - **ChangeStateUpdate.AZ_HELP, +ChangeRecordUpdate.AZ_HELP = { + **ChangeRecordUpdate.AZ_HELP, "examples": [ { "name": "Adjust rollout type and add a comment", @@ -728,21 +728,21 @@ class ChangeStateDelete(_ChangeStateDelete): ], } -ChangeStateDelete.AZ_HELP = { - **ChangeStateDelete.AZ_HELP, +ChangeRecordDelete.AZ_HELP = { + **ChangeRecordDelete.AZ_HELP, "examples": [ { - "name": "Delete a change state without confirmation", + "name": "Delete a change record without confirmation", "text": "az changesafety changerecord delete -g MyResourceGroup -n changerecord001 --yes", }, ], } -ChangeStateShow.AZ_HELP = { - **ChangeStateShow.AZ_HELP, +ChangeRecordShow.AZ_HELP = { + **ChangeRecordShow.AZ_HELP, "examples": [ { - "name": "Show a change state", + "name": "Show a change record", "text": "az changesafety changerecord show -g MyResourceGroup -n changerecord001", }, ], From 094e6bf4edcf3dac6b8a67042aadc0cd8a968276 Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Fri, 30 Jan 2026 15:20:38 -0800 Subject: [PATCH 22/25] Update CLI to rename changeRecords for 2026-01-01-repview --- src/azure-changesafety/README.md | 9 +- .../azext_changesafety/_help.py | 34 +- .../azext_changesafety/custom.py | 442 +++++++----------- 3 files changed, 203 insertions(+), 282 deletions(-) diff --git a/src/azure-changesafety/README.md b/src/azure-changesafety/README.md index e45916e1ece..de895acbdcc 100644 --- a/src/azure-changesafety/README.md +++ b/src/azure-changesafety/README.md @@ -19,14 +19,15 @@ az changesafety changerecord show # Display details for a ChangeRecord resour Run `az changesafety changerecord -h` to see full parameter details and examples. ## Examples -Create a ChangeRecord for a manual touch operation (e.g., Deletes a traffic manager profile): +Create a ChangeRecord for a manual touch operation (e.g., delete a Traffic Manager profile): ```bash az changesafety changerecord create \ -g MyResourceGroup \ - -n changerecord-vm-maintenance \ + -n changerecord-delete-tm \ --change-type ManualTouch \ - --rollout-type Normal \ - --targets "subscriptionId=,operation=DELETES" \ + --rollout-type Hotfix \ + --targets "resourceId=/subscriptions//resourceGroups/MyResourceGroup/providers/Microsoft.Network/trafficManagerProfiles/myProfile,operation=DELETE" \ + --description "Delete Traffic Manager profile for maintenance" ``` Create a ChangeRecord for an app deployment with a StageMap reference: diff --git a/src/azure-changesafety/azext_changesafety/_help.py b/src/azure-changesafety/azext_changesafety/_help.py index 9058c3181e1..570ca32cd2a 100644 --- a/src/azure-changesafety/azext_changesafety/_help.py +++ b/src/azure-changesafety/azext_changesafety/_help.py @@ -27,13 +27,15 @@ Provide at least one target definition to describe which resources or operations the ChangeRecord will affect. Targets are expressed as comma or semicolon separated key=value pairs such as resourceId=RESOURCE_ID,operation=DELETE. The command is also available through the alias - `az changesafety changerecord`. If you omit scheduling flags, the anticipated start time defaults - to now and the anticipated end time defaults to eight hours later (UTC). + `az changesafety changerecord`. If you omit scheduling flags, the anticipated start time + defaults to now and the anticipated end time defaults to eight hours later (UTC). parameters: - name: --targets short-summary: > One or more target definitions expressed as key=value pairs (for example - resourceId=RESOURCE_ID,operation=CREATE,resourceType=Microsoft.Compute/virtualMachines). + resourceId=RESOURCE_ID,operation=DELETE,resourceType=Microsoft.Compute/virtualMachines). + - name: --description + short-summary: A description of the change being performed. - name: --anticipated-start-time short-summary: Expected start time in ISO 8601 format. Defaults to current UTC time when omitted. - name: --anticipated-end-time @@ -41,7 +43,7 @@ - name: --change-type short-summary: Classify the change such as AppDeployment, Config, ManualTouch, or PolicyDeployment. - name: --rollout-type - short-summary: Specify the rollout urgency (Normal, Hotfix, or Emergency). + short-summary: Specify the rollout type (Normal, Hotfix, or Emergency). - name: --stage-map-name --stagemap-name short-summary: StageMap name in the current subscription scope; the resource ID is built for you. - name: --stage-map @@ -53,12 +55,15 @@ text: |- az changesafety changerecord create -g MyResourceGroup -n changerecord002 --change-type ManualTouch --rollout-type Normal --stage-map "{resource-id:/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.ChangeSafety/stageMaps/rolloutStageMap}" --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH" --links "[{name:status,uri:'https://contoso.com/change/rollout-002'}]" az changesafety changerecord delete -g MyResourceGroup -n changerecord002 --yes - - name: Create a change state for a VM rollout + - name: Create a ChangeRecord for a VM rollout text: |- az changesafety changerecord create -g MyResourceGroup -n changerecord001 --change-type AppDeployment --rollout-type Normal --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PUT" + - name: Create a ChangeRecord for deleting a Traffic Manager profile + text: |- + az changesafety changerecord create -g MyResourceGroup -n delete-trafficmanager --change-type ManualTouch --rollout-type Hotfix --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Network/trafficManagerProfiles/myProfile,operation=DELETE" --description "Delete Traffic Manager profile" - name: Create with staging rollout configuration text: |- - az changesafety changerecord create -g MyResourceGroup -n changerecord-ops01 --rollout-type Hotfix --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST" + az changesafety changerecord create -g MyResourceGroup -n changerecord-ops01 --change-type AppDeployment --rollout-type Hotfix --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST" - name: Reference a StageMap by name text: |- az changesafety changerecord create -g MyResourceGroup -n changerecord003 --change-type ManualTouch --rollout-type Normal --stagemap-name rolloutStageMap --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=DELETE" @@ -68,20 +73,17 @@ type: command short-summary: Update an existing ChangeRecord resource. long-summary: > - Use this command to modify descriptive metadata, rollout settings, or replace targets for an - existing ChangeRecord. When you pass --targets, the supplied definitions overwrite the previous set. - This command is also available through the alias `az change-safety change-record`. + Use this command to modify descriptive metadata, rollout settings, or scheduling for an + existing ChangeRecord. Note: The changeDefinition (targets) cannot be modified after creation. parameters: - - name: --targets - short-summary: > - Optional target definitions to replace the existing list. Provide key=value pairs such as - resourceId=RESOURCE_ID,operation=DELETE. - name: --stage-map-name --stagemap-name short-summary: StageMap name in the current subscription scope; the resource ID is built for you. - name: --stage-map short-summary: Reference an existing StageMap resource using resource-id=RESOURCE_ID and optional parameters key=value pairs. - name: --comments - short-summary: Provide notes about the latest update to the change state. + short-summary: Provide notes about the latest update to the ChangeRecord. + - name: --description + short-summary: Update the description of the change. - name: --anticipated-start-time short-summary: Update the expected start time in ISO 8601 format. If omitted, the current value is preserved. - name: --anticipated-end-time @@ -93,9 +95,9 @@ - name: Update scheduling window text: |- az changesafety changerecord update -g MyResourceGroup -n changerecord001 --anticipated-start-time "2024-09-01T08:00:00Z" --anticipated-end-time "2024-09-01T12:00:00Z" - - name: Replace the target definition + - name: Update description text: |- - az changesafety changerecord update -g MyResourceGroup -n changerecord001 --targets "resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/myServer,operation=PATCH" + az changesafety changerecord update -g MyResourceGroup -n changerecord001 --description "Updated rollout for production deployment" """ helps['changesafety changerecord delete'] = """ diff --git a/src/azure-changesafety/azext_changesafety/custom.py b/src/azure-changesafety/azext_changesafety/custom.py index bd608c83aa4..eea5c5b5b27 100644 --- a/src/azure-changesafety/azext_changesafety/custom.py +++ b/src/azure-changesafety/azext_changesafety/custom.py @@ -5,12 +5,18 @@ # Code generated by aaz-dev-tools # -------------------------------------------------------------------------------------------- +"""Custom command overrides for azure-changesafety CLI extension. + +This module provides customizations for ChangeRecord CRUD operations: +- ChangeRecordCreate: Custom --targets parsing, --stagemap-name shortcut, schedule defaults +- ChangeRecordUpdate: Preserves changeDefinition during GET+PUT cycle (AAZ schema workaround) +- ChangeRecordShow: Custom schema to expose details.targets array +- ChangeRecordDelete: Help examples only +""" + import datetime from argparse import SUPPRESS -# pylint: disable=too-many-lines -# pylint: disable=too-many-statements -# pylint: disable=protected-access from knack.log import get_logger from azure.cli.core.aaz import ( has_value, @@ -23,7 +29,7 @@ AAZStrType, AAZListType, ) -from azure.cli.core.aaz._arg_action import ( +from azure.cli.core.aaz._arg_action import ( # pylint: disable=protected-access AAZArgActionOperations, AAZPromptInputOperation, _ELEMENT_APPEND_KEY, @@ -35,16 +41,88 @@ Show as _ChangeRecordShow, Delete as _ChangeRecordDelete, ) +# Import for schema building - used by _build_custom_show_schema +from azext_changesafety.aaz.latest.changesafety.changerecord._show import ( + Show as _GeneratedShow, +) logger = get_logger(__name__) +# ----------------------------------------------------------------------------- +# Constants +# ----------------------------------------------------------------------------- +CHANGE_DEFINITION_KIND_TARGETS = "Targets" +CHANGE_DEFINITION_KIND_API_OPERATIONS = "ApiOperations" + +# Mapping of user-friendly key names to API field names for --targets parsing +TARGET_KEY_MAPPING = { + 'resourceid': 'resourceId', + 'subscriptionid': 'subscriptionId', + 'resourcegroupname': 'resourceGroupName', + 'resourcegroup': 'resourceGroupName', + 'rg': 'resourceGroupName', + 'resourcetype': 'resourceType', + 'resourcename': 'resourceName', + 'httpmethod': 'httpMethod', + 'method': 'httpMethod', + 'operation': 'httpMethod', +} + + +# ----------------------------------------------------------------------------- +# Helper Functions +# ----------------------------------------------------------------------------- def _build_any_type(): - """Utility to satisfy schema_builder callsites while keeping lint happy.""" + """Return an AAZAnyType instance for schema_builder callbacks. + + This is needed because schema_builder expects a callable that returns a schema type. + Using a function instead of a lambda keeps linters happy. + """ return AAZAnyType() +def _apply_stage_map_shortcut(ctx): + """Translate --stagemap-name into the stage_map resourceId payload. + + This shared helper allows users to specify just the StageMap name instead of + the full resource ID. It builds the resource ID using the current subscription. + + Args: + ctx: The AAZ command context containing args and subscription_id. + + Raises: + InvalidArgumentValueError: If both --stage-map and --stagemap-name are provided, + or if subscription_id is not available. + """ + stage_map_arg = getattr(ctx.args, "stage_map", None) + stage_map_name_arg = getattr(ctx.args, "stagemap_name", None) + has_stage_map = has_value(stage_map_arg) + has_stage_map_name = has_value(stage_map_name_arg) + + if has_stage_map and has_stage_map_name: + raise InvalidArgumentValueError( + "Use either --stage-map or --stagemap-name/--stage-map-name, not both." + ) + + if not has_stage_map_name: + return + + subscription_id = getattr(ctx, "subscription_id", None) + if not subscription_id: + raise InvalidArgumentValueError( + "A subscription is required to resolve the StageMap scope." + ) + + resource_id = ( + f"/subscriptions/{subscription_id}/providers/" + f"Microsoft.ChangeSafety/stageMaps/{stage_map_name_arg.to_serialized_data()}" + ) + logger.debug("Resolved StageMap resourceId from name: %s", resource_id) + ctx.args.stage_map = {"resource_id": resource_id} + + def _inject_change_definition_into_content(content, ctx): """Attach the computed changeDefinition payload to the serialized request content.""" change_definition_value = getattr(ctx.vars, "change_definition", None) @@ -62,6 +140,29 @@ def _inject_change_definition_into_content(content, ctx): return content +def _preserve_change_definition_in_content(content, ctx): + """Preserve the original changeDefinition from GET response in the update request. + + The changeDefinition cannot be modified after creation, so we must preserve + the original value from the GET response to avoid sending an empty/invalid + changeDefinition in the PUT request. + """ + if content is None: + return content + + # Get the original changeDefinition captured from the raw GET response + original_change_definition = getattr(ctx, "_original_change_definition", None) + + if original_change_definition is not None: + properties = content.get("properties") + if properties is not None: + # Replace the corrupted changeDefinition with the original from the GET response + properties["changeDefinition"] = original_change_definition + logger.debug("Preserved original changeDefinition in update request") + + return content + + def _normalize_targets_arg(raw_targets): """Return a list of raw target strings from the parsed CLI argument.""" if raw_targets is None: @@ -146,14 +247,19 @@ def process(item): process(data) -def _custom_show_schema_builder(): - # Import the generated Show class - from azext_changesafety.aaz.latest.changesafety.changerecord._show import Show as GeneratedShow +def _build_custom_show_schema(): + """Build a custom schema for Show command that includes details.targets. + + The generated AAZ schema doesn't know about the free-form 'details' object + structure, so we extend it to properly deserialize the targets array. + Returns: + AAZObjectType: The extended schema with targets array definition. + """ # Get the base schema from the generated code - base_schema = GeneratedShow.ChangeRecordsGet._build_schema_on_200() + base_schema = _GeneratedShow.ChangeRecordsGet._build_schema_on_200() - # Inject/override the targets schema + # Inject schema for details.targets - the generated code doesn't know this structure change_definition = base_schema.properties.change_definition details = change_definition.details details.targets = AAZListType(flags={"read_only": True}) @@ -168,7 +274,13 @@ def _custom_show_schema_builder(): return base_schema +# ----------------------------------------------------------------------------- +# Command Classes +# ----------------------------------------------------------------------------- + class ChangeRecordCreate(_ChangeRecordCreate): + """Custom Create command with --targets parsing and --stagemap-name shortcut.""" + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._raw_targets = [] @@ -217,9 +329,8 @@ def _handler(self, command_args): def pre_operations(self): super().pre_operations() - self._ensure_schedule_defaults() - self._apply_stage_map_shortcut() + _apply_stage_map_shortcut(self.ctx) change_definition_arg = getattr(self.ctx.args, "change_definition", None) change_definition_value = None @@ -274,32 +385,6 @@ def _parse_datetime_value(value): except ValueError: return None - def _apply_stage_map_shortcut(self): - """Translate --stagemap-name into the stage_map resourceId payload.""" - stage_map_arg = getattr(self.ctx.args, "stage_map", None) - stage_map_name_arg = getattr(self.ctx.args, "stagemap_name", None) - has_stage_map = has_value(stage_map_arg) - has_stage_map_name = has_value(stage_map_name_arg) - - if has_stage_map and has_stage_map_name: - raise InvalidArgumentValueError("Use either --stage-map or --stagemap-name/--stage-map-name, not both.") - - if not has_stage_map_name: - return - - scope_prefix = self._resolve_stage_map_scope() - resource_id = f"{scope_prefix}/providers/Microsoft.ChangeSafety/stageMaps/{stage_map_name_arg.to_serialized_data()}" - logger.debug("Resolved StageMap resourceId from name: %s", resource_id) - self.ctx.args.stage_map = { - "resource_id": resource_id - } - - def _resolve_stage_map_scope(self): - subscription_id = getattr(self.ctx, "subscription_id", None) - if not subscription_id: - raise InvalidArgumentValueError("A subscription is required to resolve the StageMap scope.") - return f"/subscriptions/{subscription_id}" - def _parse_change_definition_arg(self, change_definition_arg): data = change_definition_arg.to_serialized_data() if data is None: @@ -320,7 +405,7 @@ def _build_change_definition(self): ) return { - 'kind': 'Targets', + 'kind': CHANGE_DEFINITION_KIND_TARGETS, 'name': change_name, 'details': { 'targets': targets @@ -351,21 +436,9 @@ def _parse_targets(raw_targets): value = value.strip() if not key or not value: raise InvalidArgumentValueError('Each --targets entry must include a non-empty key and value.') - key_mapping = { - 'resourceid': 'resourceId', - 'subscriptionid': 'subscriptionId', - 'resourcegroupname': 'resourceGroupName', - 'resourcegroup': 'resourceGroupName', - 'rg': 'resourceGroupName', - 'resourcetype': 'resourceType', - 'resourcename': 'resourceName', - 'httpmethod': 'httpMethod', - 'method': 'httpMethod', - 'operation': 'httpMethod' - } normalized_key = key.lower() - if normalized_key in key_mapping: - mapped_key = key_mapping[normalized_key] + if normalized_key in TARGET_KEY_MAPPING: + mapped_key = TARGET_KEY_MAPPING[normalized_key] if mapped_key == 'httpMethod' and value: value = value.upper() target_entry[mapped_key] = value @@ -383,13 +456,6 @@ def _output(self, *args, **kwargs): _inject_targets_into_result(result, self._parsed_targets) return result - def pre_instance_create(self): - """Set the changeDefinition in the request body before creating the instance""" - change_definition = getattr(self.ctx.vars, 'change_definition', None) - if change_definition is not None: - # The changeDefinition will be set in the content property of the HTTP operations - pass - class ChangeRecordsCreateOrUpdateAtSubscriptionLevel( _ChangeRecordCreate.ChangeRecordsCreateOrUpdateAtSubscriptionLevel): @property @@ -406,22 +472,22 @@ def content(self): class ChangeRecordUpdate(_ChangeRecordUpdate): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._raw_targets = [] - self._parsed_targets = None + """Custom update command that preserves changeDefinition from GET response. + + The changeDefinition cannot be modified after creation due to API constraints. + This class works around an AAZ framework limitation where the GET+PUT update + pattern corrupts the free-form 'details' object (it becomes empty {}). + + Solution: We capture the raw changeDefinition from the GET HTTP response before + the AAZ schema loses the details, then restore it in the PUT request content. + """ @classmethod def _build_arguments_schema(cls, *args, **kwargs): schema = super()._build_arguments_schema(*args, **kwargs) - if not hasattr(schema, "change_definition"): - schema.change_definition = AAZAnyTypeArg( # type: ignore[attr-defined] - options=["--change-definition"], - arg_group="Properties", - help=SUPPRESS, - nullable=True, - blank={}, - ) + # Hide --change-definition since it cannot be updated after creation + if hasattr(schema, "change_definition"): + setattr(schema.change_definition, '_registered', False) # pylint: disable=protected-access if not hasattr(schema, "stagemap_name"): schema.stagemap_name = AAZStrArg( # type: ignore[attr-defined] options=["--stagemap-name", "--stage-map-name"], @@ -431,226 +497,78 @@ def _build_arguments_schema(cls, *args, **kwargs): "the stage map resource ID." ), ) - if not hasattr(schema, "targets"): - schema.targets = AAZListArg( - options=["--targets"], - help=( - "Optional target definitions expressed as key=value pairs separated by commas or semicolons. " - "Example: --targets \"resourceId=RESOURCE_ID,operation=delete\"" - ), - ) - schema.targets.Element = AAZStrArg() return schema - def _handler(self, command_args): - # Extract targets before calling parent handler so we can accept flexible input formats. - self._raw_targets = [] - self._parsed_targets = None - command_args = dict(command_args) if command_args else {} - raw_targets = command_args.pop('targets', None) - if raw_targets is not None: - self._raw_targets = _normalize_targets_arg(raw_targets) - return super()._handler(command_args) - def pre_operations(self): super().pre_operations() + _apply_stage_map_shortcut(self.ctx) - self._apply_stage_map_shortcut() - - change_definition_arg = getattr(self.ctx.args, "change_definition", None) - change_definition_value = None - self._raw_targets = [t for t in (self._raw_targets or []) if t and str(t) != 'Undefined'] - if has_value(change_definition_arg): - change_definition_value = self._parse_change_definition_arg(change_definition_arg) - if self._raw_targets: - raise InvalidArgumentValueError("Use either --change-definition or --targets, not both.") - - # Build and set the changeDefinition with targets if targets are provided - if change_definition_value is not None: - change_definition = change_definition_value - elif self._raw_targets: - change_definition = self._build_change_definition() - else: - change_definition = None - - if change_definition: - logger.debug("Final changeDefinition for update: %s", change_definition) - self.ctx.set_var( - 'change_definition', - change_definition, - schema_builder=_build_any_type, - ) - - def _build_change_definition(self): - """Build the changeDefinition object with targets""" - targets = self._parse_targets(self._raw_targets) - self._parsed_targets = targets - change_arg = self.ctx.args.change_record_name - change_name = ( - change_arg.to_serialized_data() - if has_value(change_arg) - else "Change Definition" - ) - - return { - 'kind': 'Targets', - 'name': change_name, - 'details': { - 'targets': targets - } - } - - def _parse_targets(self, raw_targets): - """Parse target strings into structured objects""" - if not raw_targets: - return None # For update, targets may be optional - - parsed_targets = [] - for token in raw_targets: - if not token: - continue - - # Split by semicolon or comma to handle multiple key-value pairs in one token - segments = [] - for part in str(token).replace(';', ',').split(','): - segment = part.strip() - if segment: - segments.append(segment) - - if not segments: - continue - - target_entry = {} - for segment in segments: - if '=' not in segment: - error_message = ( - "Each --targets entry must be in key=value format. " - f"Invalid: '{segment}'" - ) - raise InvalidArgumentValueError(error_message) - - key, value = segment.split('=', 1) - key = key.strip() - value = value.strip() - - if not key or not value: - raise InvalidArgumentValueError('Each --targets entry must include a non-empty key and value.') - - # Map keys to the correct property names - key_mapping = { - 'resourceid': 'resourceId', - 'subscriptionid': 'subscriptionId', - 'resourcegroupname': 'resourceGroupName', - 'resourcegroup': 'resourceGroupName', # Allow shorter alias - 'rg': 'resourceGroupName', # Allow shorter alias - 'resourcetype': 'resourceType', - 'resourcename': 'resourceName', - 'httpmethod': 'httpMethod', - 'method': 'httpMethod', # Allow shorter alias - 'operation': 'httpMethod' # Allow 'operation' as alias for httpMethod - } - - normalized_key = key.lower() - if normalized_key in key_mapping: - mapped_key = key_mapping[normalized_key] - # Normalize HTTP method values to uppercase - if mapped_key == 'httpMethod' and value: - value = value.upper() - target_entry[mapped_key] = value - else: - target_entry[key] = value - - if target_entry: - parsed_targets.append(target_entry) - - return parsed_targets if parsed_targets else None - - def _parse_change_definition_arg(self, change_definition_arg): - data = change_definition_arg.to_serialized_data() - if data is None: - return None - if not isinstance(data, dict): - raise InvalidArgumentValueError("--change-definition must be valid JSON object.") - return data - - def _apply_stage_map_shortcut(self): - """Translate --stagemap-name into the stage_map resourceId payload.""" - stage_map_arg = getattr(self.ctx.args, "stage_map", None) - stage_map_name_arg = getattr(self.ctx.args, "stagemap_name", None) - has_stage_map = has_value(stage_map_arg) - has_stage_map_name = has_value(stage_map_name_arg) - - if has_stage_map and has_stage_map_name: - raise InvalidArgumentValueError("Use either --stage-map or --stagemap-name/--stage-map-name, not both.") - - if not has_stage_map_name: - return - - subscription_id = getattr(self.ctx, "subscription_id", None) - if not subscription_id: - raise InvalidArgumentValueError("A subscription is required to resolve the StageMap scope.") - - resource_id = f"/subscriptions/{subscription_id}/providers/Microsoft.ChangeSafety/stageMaps/{stage_map_name_arg.to_serialized_data()}" - logger.debug("Resolved StageMap resourceId from name: %s", resource_id) - self.ctx.args.stage_map = { - "resource_id": resource_id - } - - def _output(self, *args, **kwargs): - result = super()._output(*args, **kwargs) - _inject_targets_into_result(result, self._parsed_targets) - return result + class ChangeRecordsGetAtSubscriptionLevel( + _ChangeRecordUpdate.ChangeRecordsGetAtSubscriptionLevel): + def on_200(self, session): + # Capture raw changeDefinition from HTTP response before schema loses details + data = self.deserialize_http_content(session) + if data and "properties" in data and "changeDefinition" in data["properties"]: + self.ctx._original_change_definition = data["properties"]["changeDefinition"] + logger.debug("Captured raw changeDefinition from GET response: %s", + self.ctx._original_change_definition) + return super().on_200(session) - def pre_instance_update(self, instance): - """Set the changeDefinition in the request body before updating the instance""" - del instance - change_definition = getattr(self.ctx.vars, 'change_definition', None) - if change_definition is not None: - # The changeDefinition will be set in the content property of the HTTP operations - pass + class ChangeRecordsGet(_ChangeRecordUpdate.ChangeRecordsGet): + def on_200(self, session): + # Capture raw changeDefinition from HTTP response before schema loses details + data = self.deserialize_http_content(session) + if data and "properties" in data and "changeDefinition" in data["properties"]: + self.ctx._original_change_definition = data["properties"]["changeDefinition"] + logger.debug("Captured raw changeDefinition from GET response: %s", + self.ctx._original_change_definition) + return super().on_200(session) class ChangeRecordsCreateOrUpdateAtSubscriptionLevel( _ChangeRecordUpdate.ChangeRecordsCreateOrUpdateAtSubscriptionLevel): @property def content(self): content = super().content - return _inject_change_definition_into_content(content, self.ctx) + # Preserve original changeDefinition - it cannot be updated + return _preserve_change_definition_in_content(content, self.ctx) class ChangeRecordsCreateOrUpdate( _ChangeRecordUpdate.ChangeRecordsCreateOrUpdate): @property def content(self): content = super().content - return _inject_change_definition_into_content(content, self.ctx) + # Preserve original changeDefinition - it cannot be updated + return _preserve_change_definition_in_content(content, self.ctx) class ChangeRecordShow(_ChangeRecordShow): - def _output(self, *args, **kwargs): - result = super()._output(*args, **kwargs) - # Optionally inject targets schema into result if needed - return result + """Custom Show command with extended schema for details.targets.""" class ChangeRecordsGetAtSubscriptionLevel(_ChangeRecordShow.ChangeRecordsGetAtSubscriptionLevel): + """Override to use custom schema that includes targets array.""" + def on_200(self, session): data = self.deserialize_http_content(session) self.ctx.set_var( "instance", data, - schema_builder=_custom_show_schema_builder + schema_builder=_build_custom_show_schema ) class ChangeRecordsGet(_ChangeRecordShow.ChangeRecordsGet): + """Override to use custom schema that includes targets array.""" + def on_200(self, session): data = self.deserialize_http_content(session) self.ctx.set_var( "instance", data, - schema_builder=_custom_show_schema_builder + schema_builder=_build_custom_show_schema ) class ChangeRecordDelete(_ChangeRecordDelete): - pass + """Delete command - only customized for help examples.""" ChangeRecordCreate.AZ_HELP = { @@ -670,7 +588,7 @@ class ChangeRecordDelete(_ChangeRecordDelete): ), }, { - "name": "Create a change state for a VM rollout", + "name": "Create a ChangeRecord for a VM rollout", "text": ( "az changesafety changerecord create -g MyResourceGroup -n changerecord001 " "--change-type AppDeployment --rollout-type Normal " @@ -679,12 +597,13 @@ class ChangeRecordDelete(_ChangeRecordDelete): ), }, { - "name": "Create with staging rollout configuration", + "name": "Create a ChangeRecord for deleting a Traffic Manager profile", "text": ( - "az changesafety changerecord create -g MyResourceGroup -n changerecord-ops01 " - "--rollout-type Hotfix " + "az changesafety changerecord create -g MyResourceGroup -n delete-trafficmanager " + "--change-type ManualTouch --rollout-type Hotfix " "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" - "resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=POST\"" + "resourceGroups/MyResourceGroup/providers/Microsoft.Network/trafficManagerProfiles/myProfile,operation=DELETE\" " + "--description \"Delete Traffic Manager profile\"" ), }, { @@ -718,11 +637,10 @@ class ChangeRecordDelete(_ChangeRecordDelete): ), }, { - "name": "Replace the target definition", + "name": "Update description", "text": ( "az changesafety changerecord update -g MyResourceGroup -n changerecord001 " - "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" - "resourceGroups/MyResourceGroup/providers/Microsoft.Sql/servers/myServer,operation=PATCH\"" + "--description \"Updated rollout for production deployment\"" ), }, ], @@ -732,7 +650,7 @@ class ChangeRecordDelete(_ChangeRecordDelete): **ChangeRecordDelete.AZ_HELP, "examples": [ { - "name": "Delete a change record without confirmation", + "name": "Delete a ChangeRecord without confirmation", "text": "az changesafety changerecord delete -g MyResourceGroup -n changerecord001 --yes", }, ], @@ -742,7 +660,7 @@ class ChangeRecordDelete(_ChangeRecordDelete): **ChangeRecordShow.AZ_HELP, "examples": [ { - "name": "Show a change record", + "name": "Show a ChangeRecord", "text": "az changesafety changerecord show -g MyResourceGroup -n changerecord001", }, ], From 8abf6138dac07b7205804531fb84b09209390a4a Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Fri, 30 Jan 2026 16:13:07 -0800 Subject: [PATCH 23/25] Fix custom --- src/azure-changesafety/README.md | 57 +- .../azext_changesafety/__init__.py | 2 + .../azext_changesafety/_help.py | 71 ++ .../changesafety/stageprogression/_create.py | 32 +- .../changesafety/stageprogression/_delete.py | 32 +- .../changesafety/stageprogression/_list.py | 2 +- .../changesafety/stageprogression/_show.py | 32 +- .../changesafety/stageprogression/_update.py | 112 +- .../azext_changesafety/commands.py | 2 +- .../azext_changesafety/custom.py | 381 +++++- .../test_change_state_cli_scenario.yaml | 106 -- .../test_changesafety_full_scenario.yaml | 735 +++++++++++ .../tests/latest/test_change_state.py | 480 ------- .../tests/latest/test_changesafety.py | 1136 +++++++++++++++++ 14 files changed, 2424 insertions(+), 756 deletions(-) delete mode 100644 src/azure-changesafety/azext_changesafety/tests/latest/recordings/test_change_state_cli_scenario.yaml create mode 100644 src/azure-changesafety/azext_changesafety/tests/latest/recordings/test_changesafety_full_scenario.yaml delete mode 100644 src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py create mode 100644 src/azure-changesafety/azext_changesafety/tests/latest/test_changesafety.py diff --git a/src/azure-changesafety/README.md b/src/azure-changesafety/README.md index de895acbdcc..3da614ce5cd 100644 --- a/src/azure-changesafety/README.md +++ b/src/azure-changesafety/README.md @@ -1,5 +1,5 @@ # Azure CLI Change Safety Extension -Azure CLI extension for managing Change Safety `ChangeRecord` resources. A ChangeRecord describes a planned change to one or more Azure resources, enabling coordination, tracking, and safe deployment across your environment. +Azure CLI extension for managing Change Safety resources. This includes `ChangeRecord`, `StageMap`, and `StageProgression` resources for coordinating, tracking, and safely deploying changes across your Azure environment. ## Installation ```bash @@ -9,16 +9,47 @@ az extension add --name azure-changesafety ``` ## Commands + +### ChangeRecord ```bash az changesafety changerecord create # Create a ChangeRecord for one or more targets. az changesafety changerecord update # Update metadata, rollout configuration, or target definitions. az changesafety changerecord delete # Delete a ChangeRecord resource. az changesafety changerecord show # Display details for a ChangeRecord resource. +az changesafety changerecord list # List ChangeRecord resources. +``` + +### StageMap +```bash +az changesafety stagemap create # Create a StageMap defining rollout stages. +az changesafety stagemap update # Update StageMap stages. +az changesafety stagemap delete # Delete a StageMap resource. +az changesafety stagemap show # Display details for a StageMap resource. +az changesafety stagemap list # List StageMap resources. +``` + +### StageProgression +```bash +az changesafety stageprogression create # Create a StageProgression to track stage execution. +az changesafety stageprogression update # Update StageProgression status or comments. +az changesafety stageprogression delete # Delete a StageProgression resource. +az changesafety stageprogression show # Display details for a StageProgression resource. +az changesafety stageprogression list # List StageProgression resources for a ChangeRecord. ``` -Run `az changesafety changerecord -h` to see full parameter details and examples. +Run `az changesafety -h` to see full command groups and examples. ## Examples + +### StageMap Examples +Create a two-stage StageMap for rollout: +```bash +az changesafety stagemap create \ + --stage-map-name rolloutStageMap \ + --stages "[{name:Canary,sequence:1},{name:Production,sequence:2}]" +``` + +### ChangeRecord Examples Create a ChangeRecord for a manual touch operation (e.g., delete a Traffic Manager profile): ```bash az changesafety changerecord create \ @@ -38,7 +69,7 @@ az changesafety changerecord create \ --change-type AppDeployment \ --rollout-type Normal \ --targets "resourceId=/subscriptions//resourceGroups/MyResourceGroup/providers/Microsoft.Web/sites/myApp,operation=PUT" \ - --stage-map "{resource-id:/subscriptions//resourceGroups/MyResourceGroup/providers/Microsoft.ChangeSafety/stageMaps/rolloutStageMap}" \ + --stagemap-name rolloutStageMap \ --links name=Runbook uri=https://contoso.com/runbook ``` @@ -51,11 +82,25 @@ az changesafety changerecord update \ --comments "Escalated due to customer impact" ``` -Delete a ChangeRecord: +### StageProgression Examples +Create a StageProgression for the Canary stage: +```bash +az changesafety stageprogression create \ + --change-record-name changerecord-webapp-rollout \ + -n canary-progression \ + --stage-reference Canary \ + --status InProgress +``` + +Update StageProgression to mark stage as completed: ```bash -az changesafety changerecord delete -g MyResourceGroup -n changerecord-webapp-rollout --yes +az changesafety stageprogression update \ + --change-record-name changerecord-webapp-rollout \ + -n canary-progression \ + --status Completed \ + --comments "Canary validation passed" ``` ## Additional Information -- View command documentation: `az changesafety changerecord -h` +- View command documentation: `az changesafety -h` - Remove the extension when no longer needed: `az extension remove --name azure-changesafety` diff --git a/src/azure-changesafety/azext_changesafety/__init__.py b/src/azure-changesafety/azext_changesafety/__init__.py index 46248fb61e1..ef81dcb0076 100644 --- a/src/azure-changesafety/azext_changesafety/__init__.py +++ b/src/azure-changesafety/azext_changesafety/__init__.py @@ -7,6 +7,8 @@ from azure.cli.core import AzCommandsLoader from azext_changesafety._help import helps # pylint: disable=unused-import +# Import custom to apply AZ_HELP patches for StageMap commands +from azext_changesafety import custom as _custom # pylint: disable=unused-import class ChangeRecordCommandsLoader(AzCommandsLoader): diff --git a/src/azure-changesafety/azext_changesafety/_help.py b/src/azure-changesafety/azext_changesafety/_help.py index 570ca32cd2a..416bfb8d5b0 100644 --- a/src/azure-changesafety/azext_changesafety/_help.py +++ b/src/azure-changesafety/azext_changesafety/_help.py @@ -117,3 +117,74 @@ text: |- az changesafety changerecord show -g MyResourceGroup -n changerecord001 """ + +helps['changesafety stagemap'] = """ + type: group + short-summary: Manage StageMap resources that define rollout stages for orchestrated changes. +""" + +helps['changesafety stagemap create'] = """ + type: command + short-summary: Create a StageMap resource. + long-summary: > + A StageMap defines the stages through which a change progresses during rollout. + Each stage has a name and sequence number. Stages are executed in order of their + sequence values. StageMap resources can be referenced by ChangeRecord resources + to enforce staged rollout patterns. + parameters: + - name: --stage-map-name + short-summary: The name of the StageMap resource to create. + - name: --stages + short-summary: > + Array of stage definitions. Each stage requires a name and sequence number. + Use shorthand syntax or JSON format. + examples: + - name: Create a simple two-stage StageMap + text: |- + az changesafety stagemap create --subscription 00000000-0000-0000-0000-000000000000 --stage-map-name rolloutStageMap --stages "[{name:Canary,sequence:1},{name:Production,sequence:2}]" + - name: Create a StageMap with three stages + text: |- + az changesafety stagemap create --subscription 00000000-0000-0000-0000-000000000000 --stage-map-name regional-rollout --stages "[{name:WestUS,sequence:1},{name:EastUS,sequence:2},{name:Global,sequence:3}]" +""" + +helps['changesafety stagemap update'] = """ + type: command + short-summary: Update an existing StageMap resource. + long-summary: > + Modify the stages defined in a StageMap. When updating, provide the complete + list of stages as the update replaces the existing stages array. + examples: + - name: Add a new stage to an existing StageMap + text: |- + az changesafety stagemap update --subscription 00000000-0000-0000-0000-000000000000 --stage-map-name rolloutStageMap --stages "[{name:Canary,sequence:1},{name:Pilot,sequence:2},{name:Production,sequence:3}]" +""" + +helps['changesafety stagemap show'] = """ + type: command + short-summary: Get details for a StageMap resource. + examples: + - name: Show a StageMap + text: |- + az changesafety stagemap show --subscription 00000000-0000-0000-0000-000000000000 --stage-map-name rolloutStageMap +""" + +helps['changesafety stagemap delete'] = """ + type: command + short-summary: Delete a StageMap resource. + long-summary: > + Delete a StageMap that is no longer needed. Note that StageMap resources + that are currently referenced by active ChangeRecord resources cannot be deleted. + examples: + - name: Delete a StageMap + text: |- + az changesafety stagemap delete --subscription 00000000-0000-0000-0000-000000000000 --stage-map-name rolloutStageMap --yes +""" + +helps['changesafety stagemap list'] = """ + type: command + short-summary: List StageMap resources in a subscription. + examples: + - name: List all StageMaps in the subscription + text: |- + az changesafety stagemap list --subscription 00000000-0000-0000-0000-000000000000 +""" diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_create.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_create.py index 061da768fd8..48009803213 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_create.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_create.py @@ -21,8 +21,8 @@ class Create(AAZCommand): _aaz_info = { "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changerecords/{}/stageprogressions/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changerecords/{}/stageprogressions/{}", "2026-01-01-preview"], ] } @@ -42,9 +42,9 @@ def _build_arguments_schema(cls, *args, **kwargs): # define Arg Group "" _args_schema = cls._args_schema - _args_schema.change_state_name = AAZStrArg( - options=["--change-state-name"], - help="The name of the ChangeState resource.", + _args_schema.change_record_name = AAZStrArg( + options=["--change-record-name"], + help="The name of the ChangeRecord resource.", required=True, fmt=AAZStrArgFormat( pattern="^[a-zA-Z0-9-]{3,100}$", @@ -55,7 +55,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema.resource_group = AAZResourceGroupNameArg() _args_schema.stage_progression_name = AAZStrArg( options=["-n", "--name", "--stage-progression-name"], - help="name of the stageProgression", + help="Name of the stageProgression", required=True, fmt=AAZStrArgFormat( pattern="^[a-zA-Z0-9-|]{3,100}$", @@ -143,12 +143,12 @@ def _build_arguments_schema(cls, *args, **kwargs): def _execute_operations(self): self.pre_operations() - condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True - condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) + condition_0 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) if condition_0: - self.StageProgressionsCreateOrUpdateAtSubscriptionLevel(ctx=self.ctx)() + self.ChangeRecordStageProgressionsCreateOrUpdateAtSubscriptionLevel(ctx=self.ctx)() if condition_1: - self.StageProgressionsCreateOrUpdate(ctx=self.ctx)() + self.ChangeRecordStageProgressionsCreateOrUpdate(ctx=self.ctx)() self.post_operations() @register_callback @@ -163,7 +163,7 @@ def _output(self, *args, **kwargs): result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) return result - class StageProgressionsCreateOrUpdateAtSubscriptionLevel(AAZHttpOperation): + class ChangeRecordStageProgressionsCreateOrUpdateAtSubscriptionLevel(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -177,7 +177,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}/stageProgressions/{stageProgressionName}", **self.url_parameters ) @@ -193,7 +193,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -366,7 +366,7 @@ def _build_schema_on_200_201(cls): return cls._schema_on_200_201 - class StageProgressionsCreateOrUpdate(AAZHttpOperation): + class ChangeRecordStageProgressionsCreateOrUpdate(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -380,7 +380,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}/stageProgressions/{stageProgressionName}", **self.url_parameters ) @@ -396,7 +396,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_delete.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_delete.py index f323f0f8ec0..3b971b1196c 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_delete.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_delete.py @@ -22,8 +22,8 @@ class Delete(AAZCommand): _aaz_info = { "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changerecords/{}/stageprogressions/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changerecords/{}/stageprogressions/{}", "2026-01-01-preview"], ] } @@ -43,9 +43,9 @@ def _build_arguments_schema(cls, *args, **kwargs): # define Arg Group "" _args_schema = cls._args_schema - _args_schema.change_state_name = AAZStrArg( - options=["--change-state-name"], - help="The name of the ChangeState resource.", + _args_schema.change_record_name = AAZStrArg( + options=["--change-record-name"], + help="The name of the ChangeRecord resource.", required=True, id_part="name", fmt=AAZStrArgFormat( @@ -57,7 +57,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema.resource_group = AAZResourceGroupNameArg() _args_schema.stage_progression_name = AAZStrArg( options=["-n", "--name", "--stage-progression-name"], - help="name of the stageProgression", + help="Name of the stageProgression", required=True, id_part="child_name_1", fmt=AAZStrArgFormat( @@ -70,12 +70,12 @@ def _build_arguments_schema(cls, *args, **kwargs): def _execute_operations(self): self.pre_operations() - condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True - condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) + condition_0 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) if condition_0: - self.StageProgressionsDeleteAtSubscriptionLevel(ctx=self.ctx)() + self.ChangeRecordStageProgressionsDeleteAtSubscriptionLevel(ctx=self.ctx)() if condition_1: - self.StageProgressionsDelete(ctx=self.ctx)() + self.ChangeRecordStageProgressionsDelete(ctx=self.ctx)() self.post_operations() @register_callback @@ -86,7 +86,7 @@ def pre_operations(self): def post_operations(self): pass - class StageProgressionsDeleteAtSubscriptionLevel(AAZHttpOperation): + class ChangeRecordStageProgressionsDeleteAtSubscriptionLevel(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -102,7 +102,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}/stageProgressions/{stageProgressionName}", **self.url_parameters ) @@ -118,7 +118,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -148,7 +148,7 @@ def on_200(self, session): def on_204(self, session): pass - class StageProgressionsDelete(AAZHttpOperation): + class ChangeRecordStageProgressionsDelete(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -164,7 +164,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}/stageProgressions/{stageProgressionName}", **self.url_parameters ) @@ -180,7 +180,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_list.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_list.py index 5becaa4e614..790dc494f4e 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_list.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_list.py @@ -15,7 +15,7 @@ "changesafety stageprogression list", ) class List(AAZCommand): - """List ChangeRecordStageProgression resources by ChangeRecord + """List StageProgression resources by ChangeRecord """ _aaz_info = { diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_show.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_show.py index ed69e0c3643..5eea4109963 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_show.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_show.py @@ -21,8 +21,8 @@ class Show(AAZCommand): _aaz_info = { "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changerecords/{}/stageprogressions/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changerecords/{}/stageprogressions/{}", "2026-01-01-preview"], ] } @@ -42,9 +42,9 @@ def _build_arguments_schema(cls, *args, **kwargs): # define Arg Group "" _args_schema = cls._args_schema - _args_schema.change_state_name = AAZStrArg( - options=["--change-state-name"], - help="The name of the ChangeState resource.", + _args_schema.change_record_name = AAZStrArg( + options=["--change-record-name"], + help="The name of the ChangeRecord resource.", required=True, id_part="name", fmt=AAZStrArgFormat( @@ -56,7 +56,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema.resource_group = AAZResourceGroupNameArg() _args_schema.stage_progression_name = AAZStrArg( options=["-n", "--name", "--stage-progression-name"], - help="name of the stageProgression", + help="Name of the stageProgression", required=True, id_part="child_name_1", fmt=AAZStrArgFormat( @@ -69,12 +69,12 @@ def _build_arguments_schema(cls, *args, **kwargs): def _execute_operations(self): self.pre_operations() - condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True - condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) + condition_0 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) if condition_0: - self.StageProgressionsGetAtSubscriptionLevel(ctx=self.ctx)() + self.ChangeRecordStageProgressionsGetAtSubscriptionLevel(ctx=self.ctx)() if condition_1: - self.StageProgressionsGet(ctx=self.ctx)() + self.ChangeRecordStageProgressionsGet(ctx=self.ctx)() self.post_operations() @register_callback @@ -89,7 +89,7 @@ def _output(self, *args, **kwargs): result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) return result - class StageProgressionsGetAtSubscriptionLevel(AAZHttpOperation): + class ChangeRecordStageProgressionsGetAtSubscriptionLevel(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -103,7 +103,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}/stageProgressions/{stageProgressionName}", **self.url_parameters ) @@ -119,7 +119,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -250,7 +250,7 @@ def _build_schema_on_200(cls): return cls._schema_on_200 - class StageProgressionsGet(AAZHttpOperation): + class ChangeRecordStageProgressionsGet(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -264,7 +264,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}/stageProgressions/{stageProgressionName}", **self.url_parameters ) @@ -280,7 +280,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_update.py b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_update.py index 007d5e520eb..842b1532800 100644 --- a/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_update.py +++ b/src/azure-changesafety/azext_changesafety/aaz/latest/changesafety/stageprogression/_update.py @@ -21,8 +21,8 @@ class Update(AAZCommand): _aaz_info = { "version": "2026-01-01-preview", "resources": [ - ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], - ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changestates/{}/stageprogressions/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.changesafety/changerecords/{}/stageprogressions/{}", "2026-01-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.changesafety/changerecords/{}/stageprogressions/{}", "2026-01-01-preview"], ] } @@ -44,9 +44,9 @@ def _build_arguments_schema(cls, *args, **kwargs): # define Arg Group "" _args_schema = cls._args_schema - _args_schema.change_state_name = AAZStrArg( - options=["--change-state-name"], - help="The name of the ChangeState resource.", + _args_schema.change_record_name = AAZStrArg( + options=["--change-record-name"], + help="The name of the ChangeRecord resource.", required=True, id_part="name", fmt=AAZStrArgFormat( @@ -58,7 +58,7 @@ def _build_arguments_schema(cls, *args, **kwargs): _args_schema.resource_group = AAZResourceGroupNameArg() _args_schema.stage_progression_name = AAZStrArg( options=["-n", "--name", "--stage-progression-name"], - help="name of the stageProgression", + help="Name of the stageProgression", required=True, id_part="child_name_1", fmt=AAZStrArgFormat( @@ -157,22 +157,22 @@ def _build_arguments_schema(cls, *args, **kwargs): def _execute_operations(self): self.pre_operations() - condition_0 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True - condition_1 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) - condition_2 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True - condition_3 = has_value(self.ctx.args.change_state_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) + condition_0 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) + condition_2 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_3 = has_value(self.ctx.args.change_record_name) and has_value(self.ctx.args.resource_group) and has_value(self.ctx.args.stage_progression_name) and has_value(self.ctx.subscription_id) if condition_0: - self.StageProgressionsGetAtSubscriptionLevel(ctx=self.ctx)() + self.ChangeRecordStageProgressionsGetAtSubscriptionLevel(ctx=self.ctx)() if condition_1: - self.StageProgressionsGet(ctx=self.ctx)() + self.ChangeRecordStageProgressionsGet(ctx=self.ctx)() self.pre_instance_update(self.ctx.vars.instance) self.InstanceUpdateByJson(ctx=self.ctx)() self.InstanceUpdateByGeneric(ctx=self.ctx)() self.post_instance_update(self.ctx.vars.instance) if condition_2: - self.StageProgressionsCreateOrUpdateAtSubscriptionLevel(ctx=self.ctx)() + self.ChangeRecordStageProgressionsCreateOrUpdateAtSubscriptionLevel(ctx=self.ctx)() if condition_3: - self.StageProgressionsCreateOrUpdate(ctx=self.ctx)() + self.ChangeRecordStageProgressionsCreateOrUpdate(ctx=self.ctx)() self.post_operations() @register_callback @@ -195,7 +195,7 @@ def _output(self, *args, **kwargs): result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True) return result - class StageProgressionsGetAtSubscriptionLevel(AAZHttpOperation): + class ChangeRecordStageProgressionsGetAtSubscriptionLevel(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -209,7 +209,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}/stageProgressions/{stageProgressionName}", **self.url_parameters ) @@ -225,7 +225,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -274,11 +274,11 @@ def _build_schema_on_200(cls): return cls._schema_on_200 cls._schema_on_200 = AAZObjectType() - _UpdateHelper._build_schema_stage_progression_read(cls._schema_on_200) + _UpdateHelper._build_schema_change_record_stage_progression_read(cls._schema_on_200) return cls._schema_on_200 - class StageProgressionsGet(AAZHttpOperation): + class ChangeRecordStageProgressionsGet(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -292,7 +292,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}/stageProgressions/{stageProgressionName}", **self.url_parameters ) @@ -308,7 +308,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -361,11 +361,11 @@ def _build_schema_on_200(cls): return cls._schema_on_200 cls._schema_on_200 = AAZObjectType() - _UpdateHelper._build_schema_stage_progression_read(cls._schema_on_200) + _UpdateHelper._build_schema_change_record_stage_progression_read(cls._schema_on_200) return cls._schema_on_200 - class StageProgressionsCreateOrUpdateAtSubscriptionLevel(AAZHttpOperation): + class ChangeRecordStageProgressionsCreateOrUpdateAtSubscriptionLevel(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -379,7 +379,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + "/subscriptions/{subscriptionId}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}/stageProgressions/{stageProgressionName}", **self.url_parameters ) @@ -395,7 +395,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -456,11 +456,11 @@ def _build_schema_on_200_201(cls): return cls._schema_on_200_201 cls._schema_on_200_201 = AAZObjectType() - _UpdateHelper._build_schema_stage_progression_read(cls._schema_on_200_201) + _UpdateHelper._build_schema_change_record_stage_progression_read(cls._schema_on_200_201) return cls._schema_on_200_201 - class StageProgressionsCreateOrUpdate(AAZHttpOperation): + class ChangeRecordStageProgressionsCreateOrUpdate(AAZHttpOperation): CLIENT_TYPE = "MgmtClient" def __call__(self, *args, **kwargs): @@ -474,7 +474,7 @@ def __call__(self, *args, **kwargs): @property def url(self): return self.client.format_url( - "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeStates/{changeStateName}/stageProgressions/{stageProgressionName}", + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ChangeSafety/changeRecords/{changeRecordName}/stageProgressions/{stageProgressionName}", **self.url_parameters ) @@ -490,7 +490,7 @@ def error_format(self): def url_parameters(self): parameters = { **self.serialize_url_param( - "changeStateName", self.ctx.args.change_state_name, + "changeRecordName", self.ctx.args.change_record_name, required=True, ), **self.serialize_url_param( @@ -555,7 +555,7 @@ def _build_schema_on_200_201(cls): return cls._schema_on_200_201 cls._schema_on_200_201 = AAZObjectType() - _UpdateHelper._build_schema_stage_progression_read(cls._schema_on_200_201) + _UpdateHelper._build_schema_change_record_stage_progression_read(cls._schema_on_200_201) return cls._schema_on_200_201 @@ -614,37 +614,37 @@ def __call__(self, *args, **kwargs): class _UpdateHelper: """Helper class for Update""" - _schema_stage_progression_read = None + _schema_change_record_stage_progression_read = None @classmethod - def _build_schema_stage_progression_read(cls, _schema): - if cls._schema_stage_progression_read is not None: - _schema.id = cls._schema_stage_progression_read.id - _schema.name = cls._schema_stage_progression_read.name - _schema.properties = cls._schema_stage_progression_read.properties - _schema.system_data = cls._schema_stage_progression_read.system_data - _schema.type = cls._schema_stage_progression_read.type + def _build_schema_change_record_stage_progression_read(cls, _schema): + if cls._schema_change_record_stage_progression_read is not None: + _schema.id = cls._schema_change_record_stage_progression_read.id + _schema.name = cls._schema_change_record_stage_progression_read.name + _schema.properties = cls._schema_change_record_stage_progression_read.properties + _schema.system_data = cls._schema_change_record_stage_progression_read.system_data + _schema.type = cls._schema_change_record_stage_progression_read.type return - cls._schema_stage_progression_read = _schema_stage_progression_read = AAZObjectType() + cls._schema_change_record_stage_progression_read = _schema_change_record_stage_progression_read = AAZObjectType() - stage_progression_read = _schema_stage_progression_read - stage_progression_read.id = AAZStrType( + change_record_stage_progression_read = _schema_change_record_stage_progression_read + change_record_stage_progression_read.id = AAZStrType( flags={"read_only": True}, ) - stage_progression_read.name = AAZStrType( + change_record_stage_progression_read.name = AAZStrType( flags={"read_only": True}, ) - stage_progression_read.properties = AAZObjectType() - stage_progression_read.system_data = AAZObjectType( + change_record_stage_progression_read.properties = AAZObjectType() + change_record_stage_progression_read.system_data = AAZObjectType( serialized_name="systemData", flags={"read_only": True}, ) - stage_progression_read.type = AAZStrType( + change_record_stage_progression_read.type = AAZStrType( flags={"read_only": True}, ) - properties = _schema_stage_progression_read.properties + properties = _schema_change_record_stage_progression_read.properties properties.additional_data = AAZObjectType( serialized_name="additionalData", ) @@ -669,10 +669,10 @@ def _build_schema_stage_progression_read(cls, _schema): flags={"required": True}, ) - links = _schema_stage_progression_read.properties.links + links = _schema_change_record_stage_progression_read.properties.links links.Element = AAZObjectType() - _element = _schema_stage_progression_read.properties.links.Element + _element = _schema_change_record_stage_progression_read.properties.links.Element _element.description = AAZStrType() _element.name = AAZStrType( flags={"required": True}, @@ -681,13 +681,13 @@ def _build_schema_stage_progression_read(cls, _schema): flags={"required": True}, ) - parameters = _schema_stage_progression_read.properties.parameters + parameters = _schema_change_record_stage_progression_read.properties.parameters parameters.Element = AAZAnyType() - stage_variables = _schema_stage_progression_read.properties.stage_variables + stage_variables = _schema_change_record_stage_progression_read.properties.stage_variables stage_variables.Element = AAZAnyType() - system_data = _schema_stage_progression_read.system_data + system_data = _schema_change_record_stage_progression_read.system_data system_data.created_at = AAZStrType( serialized_name="createdAt", ) @@ -707,11 +707,11 @@ def _build_schema_stage_progression_read(cls, _schema): serialized_name="lastModifiedByType", ) - _schema.id = cls._schema_stage_progression_read.id - _schema.name = cls._schema_stage_progression_read.name - _schema.properties = cls._schema_stage_progression_read.properties - _schema.system_data = cls._schema_stage_progression_read.system_data - _schema.type = cls._schema_stage_progression_read.type + _schema.id = cls._schema_change_record_stage_progression_read.id + _schema.name = cls._schema_change_record_stage_progression_read.name + _schema.properties = cls._schema_change_record_stage_progression_read.properties + _schema.system_data = cls._schema_change_record_stage_progression_read.system_data + _schema.type = cls._schema_change_record_stage_progression_read.type __all__ = ["Update"] diff --git a/src/azure-changesafety/azext_changesafety/commands.py b/src/azure-changesafety/azext_changesafety/commands.py index d256f3ec854..c9322712e93 100644 --- a/src/azure-changesafety/azext_changesafety/commands.py +++ b/src/azure-changesafety/azext_changesafety/commands.py @@ -19,4 +19,4 @@ def load_command_table(self, _): # pylint: disable=unused-argument self.command_table['changesafety changerecord create'] = create_command self.command_table['changesafety changerecord update'] = update_command self.command_table['changesafety changerecord delete'] = delete_command - self.command_table['changesafety changerecord show'] = show_command \ No newline at end of file + self.command_table['changesafety changerecord show'] = show_command diff --git a/src/azure-changesafety/azext_changesafety/custom.py b/src/azure-changesafety/azext_changesafety/custom.py index eea5c5b5b27..16dddcd83db 100644 --- a/src/azure-changesafety/azext_changesafety/custom.py +++ b/src/azure-changesafety/azext_changesafety/custom.py @@ -23,7 +23,6 @@ AAZAnyType, AAZAnyTypeArg, AAZListArg, - AAZObjectArg, AAZStrArg, AAZObjectType, AAZStrType, @@ -163,46 +162,13 @@ def _preserve_change_definition_in_content(content, ctx): return content -def _normalize_targets_arg(raw_targets): +def _normalize_targets_arg(raw_targets): # pylint: disable=too-many-branches """Return a list of raw target strings from the parsed CLI argument.""" if raw_targets is None: return [] if isinstance(raw_targets, AAZArgActionOperations): - elements = [] - for keys, data in raw_targets._ops: - logger.debug("Processing target op keys=%s data=%s", keys, data) - if isinstance(data, AAZPromptInputOperation): - data = data() - - normalized_value = '' - if isinstance(data, (list, tuple)): - normalized_value = ','.join(str(v) for v in data if v is not None) - elif data is not None: - normalized_value = str(data) - - idx = None - key_name = None - for key in keys: - if key == _ELEMENT_APPEND_KEY: - idx = len(elements) - elif isinstance(key, int): - idx = key - elif isinstance(key, str): - key_name = key - - if idx is None: - idx = len(elements) - 1 if elements else 0 - while len(elements) <= idx: - elements.append('') - - if key_name: - combined = f"{key_name}={normalized_value}" if normalized_value else key_name - elements[idx] = f"{elements[idx]},{combined}" if elements[idx] else combined - else: - elements[idx] = normalized_value - - return [value for value in elements if value] + return _normalize_aaz_operations(raw_targets) if hasattr(raw_targets, 'to_serialized_data'): values = raw_targets.to_serialized_data() @@ -211,14 +177,59 @@ def _normalize_targets_arg(raw_targets): else: values = [raw_targets] - normalized_values = [] - for value in values: - if value is None: - continue - text = str(value).strip() - if text: - normalized_values.append(text) - return normalized_values + return [str(v).strip() for v in values if v is not None and str(v).strip()] + + +def _normalize_aaz_operations(raw_targets): + """Process AAZArgActionOperations to extract target strings.""" + elements = [] + for keys, data in raw_targets._ops: # pylint: disable=protected-access + logger.debug("Processing target op keys=%s data=%s", keys, data) + if isinstance(data, AAZPromptInputOperation): + data = data() + + normalized_value = _normalize_data_value(data) + idx, key_name = _extract_index_and_key(keys, len(elements)) + + if key_name: + # AAZ parsed "key=value,..." - reconstruct the full target string + full_target = f"{key_name}={normalized_value}" if normalized_value else key_name + _append_or_set_element(elements, idx, full_target) + else: + _append_or_set_element(elements, idx, normalized_value) + + return [value for value in elements if value] + + +def _normalize_data_value(data): + """Convert data to a normalized string value.""" + if isinstance(data, (list, tuple)): + return ','.join(str(v) for v in data if v is not None) + return str(data) if data is not None else '' + + +def _extract_index_and_key(keys, current_length): + """Extract index and key name from AAZ operation keys.""" + idx = None + key_name = None + for key in keys: + if key == _ELEMENT_APPEND_KEY: + idx = current_length + elif isinstance(key, int): + idx = key + elif isinstance(key, str): + key_name = key + return idx, key_name + + +def _append_or_set_element(elements, idx, value): + """Append or set an element in the list at the given index.""" + if idx is not None: + while len(elements) <= idx: + elements.append('') + elements[idx] = value + else: + elements.append(value) def _inject_targets_into_result(data, targets): @@ -257,6 +268,7 @@ def _build_custom_show_schema(): AAZObjectType: The extended schema with targets array definition. """ # Get the base schema from the generated code + # pylint: disable-next=protected-access base_schema = _GeneratedShow.ChangeRecordsGet._build_schema_on_200() # Inject schema for details.targets - the generated code doesn't know this structure @@ -509,9 +521,10 @@ def on_200(self, session): # Capture raw changeDefinition from HTTP response before schema loses details data = self.deserialize_http_content(session) if data and "properties" in data and "changeDefinition" in data["properties"]: + # pylint: disable-next=protected-access self.ctx._original_change_definition = data["properties"]["changeDefinition"] - logger.debug("Captured raw changeDefinition from GET response: %s", - self.ctx._original_change_definition) + logger.debug("Captured raw changeDefinition from GET response: %s", + self.ctx._original_change_definition) # pylint: disable=protected-access return super().on_200(session) class ChangeRecordsGet(_ChangeRecordUpdate.ChangeRecordsGet): @@ -519,9 +532,10 @@ def on_200(self, session): # Capture raw changeDefinition from HTTP response before schema loses details data = self.deserialize_http_content(session) if data and "properties" in data and "changeDefinition" in data["properties"]: + # pylint: disable-next=protected-access self.ctx._original_change_definition = data["properties"]["changeDefinition"] - logger.debug("Captured raw changeDefinition from GET response: %s", - self.ctx._original_change_definition) + logger.debug("Captured raw changeDefinition from GET response: %s", + self.ctx._original_change_definition) # pylint: disable=protected-access return super().on_200(session) class ChangeRecordsCreateOrUpdateAtSubscriptionLevel( @@ -574,6 +588,17 @@ class ChangeRecordDelete(_ChangeRecordDelete): ChangeRecordCreate.AZ_HELP = { **ChangeRecordCreate.AZ_HELP, "examples": [ + { + "name": "Create a ChangeRecord for deleting a Traffic Manager profile", + "text": ( + "az changesafety changerecord create -g MyResourceGroup -n delete-trafficmanager " + "--change-type ManualTouch --rollout-type Hotfix " + "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" + "resourceGroups/MyResourceGroup/providers/Microsoft.Network/" + "trafficManagerProfiles/myProfile,operation=DELETE\" " + "--description \"Delete Traffic Manager profile\"" + ), + }, { "name": "Create with StageMap reference and status link", "text": ( @@ -584,7 +609,6 @@ class ChangeRecordDelete(_ChangeRecordDelete): "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" "resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=PATCH\" " "--links \"[{name:status,uri:'https://contoso.com/change/rollout-002'}]\"\n" - "az changesafety changerecord delete -g MyResourceGroup -n changerecord002 --yes" ), }, { @@ -597,22 +621,42 @@ class ChangeRecordDelete(_ChangeRecordDelete): ), }, { - "name": "Create a ChangeRecord for deleting a Traffic Manager profile", + "name": "Create with StageMap name and default schedule", "text": ( - "az changesafety changerecord create -g MyResourceGroup -n delete-trafficmanager " - "--change-type ManualTouch --rollout-type Hotfix " + "az changesafety changerecord create -g MyResourceGroup -n changerecord003 " + "--change-type ManualTouch --rollout-type Normal --stagemap-name rolloutStageMap " "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" - "resourceGroups/MyResourceGroup/providers/Microsoft.Network/trafficManagerProfiles/myProfile,operation=DELETE\" " - "--description \"Delete Traffic Manager profile\"" + "resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=DELETE\"" ), }, { - "name": "Create with StageMap name and default schedule", + "name": "Create targeting an entire subscription (broad scope)", "text": ( - "az changesafety changerecord create -g MyResourceGroup -n changerecord003 " - "--change-type ManualTouch --rollout-type Normal --stagemap-name rolloutStageMap " + "az changesafety changerecord create -g MyResourceGroup -n subscription-wide-change " + "--change-type ManualTouch --rollout-type Normal " + "--targets \"subscriptionId=00000000-0000-0000-0000-000000000000,operation=PUT\"" + ), + }, + { + "name": "Create targeting a resource group (medium scope)", + "text": ( + "az changesafety changerecord create -g MyResourceGroup -n rg-level-change " + "--change-type ManualTouch --rollout-type Normal " + "--targets \"subscriptionId=00000000-0000-0000-0000-000000000000," + "resourceGroupName=MyResourceGroup,operation=DELETE\"" + ), + }, + { + "name": "Create with multiple targets at different scopes", + "text": ( + "az changesafety changerecord create -g MyResourceGroup -n multi-scope-change " + "--change-type ManualTouch --rollout-type Normal " + "--targets \"subscriptionId=00000000-0000-0000-0000-000000000000,operation=PUT\" " + "--targets \"subscriptionId=00000000-0000-0000-0000-000000000000," + "resourceGroupName=MyResourceGroup,operation=DELETE\" " "--targets \"resourceId=/subscriptions/00000000-0000-0000-0000-000000000000/" - "resourceGroups/MyResourceGroup/providers/Microsoft.Compute/virtualMachines/myVm,operation=DELETE\"" + "resourceGroups/MyResourceGroup/providers/Microsoft.Compute/" + "virtualMachines/myVm,operation=PATCH\"" ), }, ], @@ -665,3 +709,224 @@ class ChangeRecordDelete(_ChangeRecordDelete): }, ], } + + +# ----------------------------------------------------------------------------- +# StageMap Help Examples +# ----------------------------------------------------------------------------- +# pylint: disable=wrong-import-position +from azext_changesafety.aaz.latest.changesafety.stagemap import ( + Create as _StageMapCreate, + Show as _StageMapShow, + Update as _StageMapUpdate, + Delete as _StageMapDelete, + List as _StageMapList, +) + +_StageMapCreate.AZ_HELP = { + **_StageMapCreate.AZ_HELP, + "short-summary": "Create a StageMap resource.", + "long-summary": ( + "A StageMap defines the stages through which a change progresses during rollout. " + "Each stage has a name and sequence number. Stages are executed in order of their " + "sequence values." + ), + "examples": [ + { + "name": "Create a simple two-stage StageMap", + "text": ( + "az changesafety stagemap create --subscription 00000000-0000-0000-0000-000000000000 " + "--stage-map-name rolloutStageMap " + "--stages \"[{name:Canary,sequence:1},{name:Production,sequence:2}]\"" + ), + }, + { + "name": "Create a StageMap with three regional stages", + "text": ( + "az changesafety stagemap create --subscription 00000000-0000-0000-0000-000000000000 " + "--stage-map-name regional-rollout " + "--stages \"[{name:WestUS,sequence:1},{name:EastUS,sequence:2},{name:Global,sequence:3}]\"" + ), + }, + ], +} + +_StageMapShow.AZ_HELP = { + **_StageMapShow.AZ_HELP, + "short-summary": "Get details for a StageMap resource.", + "examples": [ + { + "name": "Show a StageMap", + "text": ( + "az changesafety stagemap show --subscription 00000000-0000-0000-0000-000000000000 " + "--stage-map-name rolloutStageMap" + ), + }, + ], +} + +_StageMapUpdate.AZ_HELP = { + **_StageMapUpdate.AZ_HELP, + "short-summary": "Update an existing StageMap resource.", + "long-summary": ( + "Modify the stages defined in a StageMap. When updating, provide the complete " + "list of stages as the update replaces the existing stages array." + ), + "examples": [ + { + "name": "Add a new stage to an existing StageMap", + "text": ( + "az changesafety stagemap update --subscription 00000000-0000-0000-0000-000000000000 " + "--stage-map-name rolloutStageMap " + "--stages \"[{name:Canary,sequence:1},{name:Pilot,sequence:2},{name:Production,sequence:3}]\"" + ), + }, + ], +} + +_StageMapDelete.AZ_HELP = { + **_StageMapDelete.AZ_HELP, + "short-summary": "Delete a StageMap resource.", + "long-summary": ( + "Delete a StageMap that is no longer needed. Note that StageMap resources " + "that are currently referenced by active ChangeRecord resources cannot be deleted." + ), + "examples": [ + { + "name": "Delete a StageMap", + "text": ( + "az changesafety stagemap delete --subscription 00000000-0000-0000-0000-000000000000 " + "--stage-map-name rolloutStageMap --yes" + ), + }, + ], +} + +_StageMapList.AZ_HELP = { + **_StageMapList.AZ_HELP, + "short-summary": "List StageMap resources in a subscription.", + "examples": [ + { + "name": "List all StageMaps in the subscription", + "text": "az changesafety stagemap list --subscription 00000000-0000-0000-0000-000000000000", + }, + ], +} + + +# ----------------------------------------------------------------------------- +# StageProgression Help Examples +# ----------------------------------------------------------------------------- +# pylint: disable=wrong-import-position +from azext_changesafety.aaz.latest.changesafety.stageprogression import ( + Create as _StageProgressionCreate, + Show as _StageProgressionShow, + Update as _StageProgressionUpdate, + Delete as _StageProgressionDelete, + List as _StageProgressionList, +) + +_StageProgressionCreate.AZ_HELP = { + **_StageProgressionCreate.AZ_HELP, + "short-summary": "Create a StageProgression to track the progress of a stage in a ChangeRecord.", + "long-summary": ( + "A StageProgression records the execution status of a specific stage defined " + "in a StageMap. Use this to track which stages have started, completed, or failed " + "during a change rollout." + ), + "examples": [ + { + "name": "Create a StageProgression for the Canary stage", + "text": ( + "az changesafety stageprogression create " + "--subscription 00000000-0000-0000-0000-000000000000 " + "--change-record-name myChangeRecord " + "-n canary-progression " + "--stage-reference Canary " + "--status InProgress" + ), + }, + { + "name": "Create a StageProgression with comments", + "text": ( + "az changesafety stageprogression create " + "--subscription 00000000-0000-0000-0000-000000000000 " + "--change-record-name myChangeRecord " + "-n prod-stage " + "--stage-reference Production " + "--status InProgress " + "--comments \"Starting production rollout\"" + ), + }, + ], +} + +_StageProgressionShow.AZ_HELP = { + **_StageProgressionShow.AZ_HELP, + "short-summary": "Get details for a StageProgression.", + "examples": [ + { + "name": "Show a StageProgression", + "text": ( + "az changesafety stageprogression show " + "--subscription 00000000-0000-0000-0000-000000000000 " + "--change-record-name myChangeRecord " + "-n canary-progression" + ), + }, + ], +} + +_StageProgressionUpdate.AZ_HELP = { + **_StageProgressionUpdate.AZ_HELP, + "short-summary": "Update a StageProgression status or comments.", + "long-summary": ( + "Update the status of a StageProgression to reflect the current state of " + "the stage execution. Common status transitions are InProgress -> Completed " + "or InProgress -> Failed." + ), + "examples": [ + { + "name": "Mark a stage as completed", + "text": ( + "az changesafety stageprogression update " + "--subscription 00000000-0000-0000-0000-000000000000 " + "--change-record-name myChangeRecord " + "-n canary-progression " + "--status Completed " + "--comments \"Canary validation passed\"" + ), + } + ], +} + +_StageProgressionDelete.AZ_HELP = { + **_StageProgressionDelete.AZ_HELP, + "short-summary": "Delete a StageProgression.", + "examples": [ + { + "name": "Delete a StageProgression", + "text": ( + "az changesafety stageprogression delete " + "--subscription 00000000-0000-0000-0000-000000000000 " + "--change-record-name myChangeRecord " + "-n canary-progression --yes" + ), + }, + ], +} + +_StageProgressionList.AZ_HELP = { + **_StageProgressionList.AZ_HELP, + "short-summary": "List all StageProgressions for a ChangeRecord.", + "examples": [ + { + "name": "List StageProgressions for a ChangeRecord", + "text": ( + "az changesafety stageprogression list " + "--subscription 00000000-0000-0000-0000-000000000000 " + "--change-record-name myChangeRecord" + ), + }, + ], +} diff --git a/src/azure-changesafety/azext_changesafety/tests/latest/recordings/test_change_state_cli_scenario.yaml b/src/azure-changesafety/azext_changesafety/tests/latest/recordings/test_change_state_cli_scenario.yaml deleted file mode 100644 index f218d0e082d..00000000000 --- a/src/azure-changesafety/azext_changesafety/tests/latest/recordings/test_change_state_cli_scenario.yaml +++ /dev/null @@ -1,106 +0,0 @@ -interactions: -- request: - body: '{"location": "eastus", "properties": {"changeType": "ManualTouch", "rolloutType": "Normal", "changeDefinition": {"kind": "Targets", "name": "changestate002", "details": {"targets": [{"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.Compute/virtualMachines/myVm", "httpMethod": "PATCH"}]}}}, "tags": null}' - headers: - Accept: - - application/json - CommandName: - - changesafety changestate create - Connection: - - keep-alive - Content-Type: - - application/json - ParameterSetName: - - -g -n --change-type --rollout-type --targets --stage-map --links --comments - method: PUT - uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview - response: - body: - string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002", "name": "changestate002", "type": "Microsoft.ChangeSafety/changeStates", "location": "eastus", "properties": {"changeType": "ManualTouch", "rolloutType": "Normal", "comments": "Initial deployment", "changeDefinition": {"kind": "Targets", "name": "changestate002", "details": {"targets": [{"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.Compute/virtualMachines/myVm", "httpMethod": "PATCH"}]}}}}' - headers: - Content-Type: - - application/json - Date: - - Mon, 03 Nov 2025 18:00:00 GMT - status: - code: 200 - message: OK - url: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview -- request: - body: '{"properties": {"rolloutType": "Emergency", "comments": "Escalated rollout"}}' - headers: - Accept: - - application/json - CommandName: - - changesafety changestate update - Connection: - - keep-alive - Content-Type: - - application/json - ParameterSetName: - - -g -n --rollout-type --comments - method: PATCH - uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview - response: - body: - string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002", "name": "changestate002", "type": "Microsoft.ChangeSafety/changeStates", "location": "eastus", "properties": {"changeType": "ManualTouch", "rolloutType": "Emergency", "comments": "Escalated rollout", "changeDefinition": {"kind": "Targets", "name": "changestate002", "details": {"targets": [{"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.Compute/virtualMachines/myVm", "httpMethod": "PATCH"}]}}}}' - headers: - Content-Type: - - application/json - Date: - - Mon, 03 Nov 2025 18:01:00 GMT - status: - code: 200 - message: OK - url: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview -- request: - body: '' - headers: - Accept: - - application/json - CommandName: - - changesafety changestate show - Connection: - - keep-alive - ParameterSetName: - - -g -n - method: GET - uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview - response: - body: - string: '{"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002", "name": "changestate002", "type": "Microsoft.ChangeSafety/changeStates", "location": "eastus", "properties": {"changeType": "ManualTouch", "rolloutType": "Emergency", "comments": "Escalated rollout", "changeDefinition": {"kind": "Targets", "name": "changestate002", "details": {"targets": [{"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.Compute/virtualMachines/myVm", "httpMethod": "PATCH"}]}}}}' - headers: - Content-Type: - - application/json - Date: - - Mon, 03 Nov 2025 18:02:00 GMT - status: - code: 200 - message: OK - url: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview -- request: - body: '' - headers: - Accept: - - application/json - CommandName: - - changesafety changestate delete - Connection: - - keep-alive - ParameterSetName: - - -g -n --yes - method: DELETE - uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview - response: - body: - string: '' - headers: - Content-Type: - - application/json - Date: - - Mon, 03 Nov 2025 18:03:00 GMT - status: - code: 204 - message: No Content - url: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rgChangeSafetyScenario/providers/Microsoft.ChangeSafety/changeStates/changestate002?api-version=2025-09-01-preview -version: 1 diff --git a/src/azure-changesafety/azext_changesafety/tests/latest/recordings/test_changesafety_full_scenario.yaml b/src/azure-changesafety/azext_changesafety/tests/latest/recordings/test_changesafety_full_scenario.yaml new file mode 100644 index 00000000000..6de1f892933 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/tests/latest/recordings/test_changesafety_full_scenario.yaml @@ -0,0 +1,735 @@ +interactions: +- request: + body: '{"properties": {"stages": [{"name": "Canary", "sequence": 1}, {"name": + "Production", "sequence": 2}]}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - changesafety stagemap create + Connection: + - keep-alive + Content-Length: + - '102' + Content-Type: + - application/json + ParameterSetName: + - --stage-map-name --stages + User-Agent: + - AZURECLI/2.82.0 azsdk-python-core/1.37.0 Python/3.11.9 (Windows-10-10.0.26200-SP0) + method: PUT + uri: https://centraluseuap.management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001?api-version=2026-01-01-preview + response: + body: + string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001","name":"stgmap000001","type":"microsoft.changesafety/stagemaps","systemData":{"createdBy":"henrydai@microsoft.com","createdByType":"User","createdAt":"2026-01-30T23:57:16.4783953Z","lastModifiedBy":"henrydai@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2026-01-30T23:57:16.4783953Z"},"properties":{"parameters":null,"stages":[{"name":"Canary","sequence":1,"stageVariables":null,"nestedStageMap":null},{"name":"Production","sequence":2,"stageVariables":null,"nestedStageMap":null}],"provisioningState":"Succeeded"}}' + headers: + cache-control: + - no-cache + content-length: + - '646' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 30 Jan 2026 23:57:16 GMT + etag: + - '"0700d98d-0000-0600-0000-697d455c0000"' + expires: + - '-1' + mise-correlation-id: + - 02a93635-22eb-46ec-8e9c-c092b71d7ecc + pragma: + - no-cache + request-context: + - appId=cid-v1:0b4319bc-3dec-4ac3-88c8-c1b86ec8e1cf + set-cookie: + - ARRAffinity=a1dd3bd4b2e949edd11f1ea24dd6378e16e4ac0eff19d14c7d7df3c670446536;Path=/;HttpOnly;Secure;Domain=canary.changecontrol.changesafety.msft.net + - ARRAffinitySameSite=a1dd3bd4b2e949edd11f1ea24dd6378e16e4ac0eff19d14c7d7df3c670446536;Path=/;HttpOnly;SameSite=None;Secure;Domain=canary.changecontrol.changesafety.msft.net + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Accept-Encoding + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-operation-identifier: + - tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47,objectId=bfa18db0-78d5-4d4d-8796-642151886af4/centraluseuap/6ccbacd0-16a6-4748-b796-8d541c5cde41 + x-ms-providerhub-traffic: + - 'True' + x-ms-ratelimit-remaining-subscription-writes: + - '799' + x-ms-throttling-version: + - v2 + x-msedge-ref: + - 'Ref A: E7D8906C0FE045E08924890D8267EE25 Ref B: BY1AA1072319062 Ref C: 2026-01-30T23:57:16Z' + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - changesafety stagemap show + Connection: + - keep-alive + ParameterSetName: + - --stage-map-name + User-Agent: + - AZURECLI/2.82.0 azsdk-python-core/1.37.0 Python/3.11.9 (Windows-10-10.0.26200-SP0) + method: GET + uri: https://centraluseuap.management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001?api-version=2026-01-01-preview + response: + body: + string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001","name":"stgmap000001","type":"microsoft.changesafety/stagemaps","systemData":{"createdBy":"henrydai@microsoft.com","createdByType":"User","createdAt":"2026-01-30T23:57:16.4783953Z","lastModifiedBy":"henrydai@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2026-01-30T23:57:16.4783953Z"},"properties":{"parameters":null,"stages":[{"name":"Canary","sequence":1,"stageVariables":null,"nestedStageMap":null},{"name":"Production","sequence":2,"stageVariables":null,"nestedStageMap":null}],"provisioningState":"Succeeded"}}' + headers: + cache-control: + - no-cache + content-length: + - '646' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 30 Jan 2026 23:57:17 GMT + etag: + - '"0700d98d-0000-0600-0000-697d455c0000"' + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-operation-identifier: + - tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47,objectId=bfa18db0-78d5-4d4d-8796-642151886af4/centraluseuap/d3bb5cfe-e466-47cc-ab3a-51816bf86506 + x-ms-providerhub-traffic: + - 'True' + x-ms-throttling-version: + - v2 + x-msedge-ref: + - 'Ref A: 6BA4DCBBB3A744CBBDEA42CB2E2B7539 Ref B: BY1AA1072317031 Ref C: 2026-01-30T23:57:17Z' + status: + code: 200 + message: OK +- request: + body: '{"properties": {"anticipatedEndTime": "2026-01-31T07:57:17Z", "anticipatedStartTime": + "2026-01-30T23:57:17Z", "changeType": "AppDeployment", "rolloutType": "Normal", + "stageMap": {"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001"}, + "changeDefinition": {"kind": "Targets", "name": "cr000002", "details": {"targets": + [{"subscriptionId": "$(az account show --query id -o tsv)"}]}}}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - changesafety changerecord create + Connection: + - keep-alive + Content-Length: + - '452' + Content-Type: + - application/json + ParameterSetName: + - -n --change-type --rollout-type --stagemap-name --targets + User-Agent: + - AZURECLI/2.82.0 azsdk-python-core/1.37.0 Python/3.11.9 (Windows-10-10.0.26200-SP0) + method: PUT + uri: https://centraluseuap.management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002?api-version=2026-01-01-preview + response: + body: + string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002","name":"cr000002","type":"microsoft.changesafety/changerecords","systemData":{"createdBy":"henrydai@microsoft.com","createdByType":"User","createdAt":"2026-01-30T23:57:18.6736676Z","lastModifiedBy":"henrydai@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2026-01-30T23:57:18.6736676Z"},"properties":{"changeType":"AppDeployment","rolloutType":"Normal","description":null,"stageMap":{"resourceId":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001","parameters":null},"parameters":null,"comments":null,"changeDefinition":{"name":"cr000002","kind":"Targets","details":{"targets":[{"subscriptionId":"$(az + account show --query id -o tsv)"}]}},"releaseLabel":null,"anticipatedStartTime":"2026-01-30T23:57:17Z","anticipatedEndTime":"2026-01-31T07:57:17Z","orchestrationTool":null,"links":null,"additionalData":null,"stageMapSnapshot":[{"key":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001","value":{"properties":{"parameters":null,"stages":[{"name":"Canary","sequence":1,"stageVariables":null,"nestedStageMap":null},{"name":"Production","sequence":2,"stageVariables":null,"nestedStageMap":null}]},"id":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001","name":"stgmap000001","type":"microsoft.changesafety/stagemaps"}}],"status":"Initialized","provisioningState":"Succeeded"}}' + headers: + cache-control: + - no-cache + content-length: + - '1579' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 30 Jan 2026 23:57:18 GMT + etag: + - '"01008f92-0000-0600-0000-697d455f0000"' + expires: + - '-1' + mise-correlation-id: + - 6f2c6588-e397-4df7-81cf-002300a56cf6 + pragma: + - no-cache + request-context: + - appId=cid-v1:0b4319bc-3dec-4ac3-88c8-c1b86ec8e1cf + set-cookie: + - ARRAffinity=1fef50c7b13f746942d4dbd0dbb97673928df1f977cb37e394a2ffeda3575190;Path=/;HttpOnly;Secure;Domain=canary.changecontrol.changesafety.msft.net + - ARRAffinitySameSite=1fef50c7b13f746942d4dbd0dbb97673928df1f977cb37e394a2ffeda3575190;Path=/;HttpOnly;SameSite=None;Secure;Domain=canary.changecontrol.changesafety.msft.net + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Accept-Encoding + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-operation-identifier: + - tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47,objectId=bfa18db0-78d5-4d4d-8796-642151886af4/centraluseuap/0aa62c13-fed2-422c-98d0-02a4e7250e1e + x-ms-providerhub-traffic: + - 'True' + x-ms-ratelimit-remaining-subscription-writes: + - '799' + x-ms-throttling-version: + - v2 + x-msedge-ref: + - 'Ref A: AAC31D799D4740EF902A0CCC627F1760 Ref B: SJC211051204025 Ref C: 2026-01-30T23:57:18Z' + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - changesafety changerecord show + Connection: + - keep-alive + ParameterSetName: + - -n + User-Agent: + - AZURECLI/2.82.0 azsdk-python-core/1.37.0 Python/3.11.9 (Windows-10-10.0.26200-SP0) + method: GET + uri: https://centraluseuap.management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002?api-version=2026-01-01-preview + response: + body: + string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002","name":"cr000002","type":"microsoft.changesafety/changerecords","systemData":{"createdBy":"henrydai@microsoft.com","createdByType":"User","createdAt":"2026-01-30T23:57:18.6736676Z","lastModifiedBy":"henrydai@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2026-01-30T23:57:18.6736676Z"},"properties":{"changeType":"AppDeployment","rolloutType":"Normal","description":null,"stageMap":{"resourceId":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001","parameters":null},"parameters":null,"comments":null,"changeDefinition":{"name":"cr000002","kind":"Targets","details":{"targets":[{"subscriptionId":"$(az + account show --query id -o tsv)"}]}},"releaseLabel":null,"anticipatedStartTime":"2026-01-30T23:57:17Z","anticipatedEndTime":"2026-01-31T07:57:17Z","orchestrationTool":null,"links":null,"additionalData":null,"stageMapSnapshot":[{"key":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001","value":{"properties":{"parameters":null,"stages":[{"name":"Canary","sequence":1,"stageVariables":null,"nestedStageMap":null},{"name":"Production","sequence":2,"stageVariables":null,"nestedStageMap":null}]},"id":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001","name":"stgmap000001","type":"microsoft.changesafety/stagemaps"}}],"status":"Initialized","provisioningState":"Succeeded"}}' + headers: + cache-control: + - no-cache + content-length: + - '1579' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 30 Jan 2026 23:57:19 GMT + etag: + - '"01008f92-0000-0600-0000-697d455f0000"' + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-operation-identifier: + - tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47,objectId=bfa18db0-78d5-4d4d-8796-642151886af4/centraluseuap/833ca244-4b27-4cb1-8b4a-d1a719e30d36 + x-ms-providerhub-traffic: + - 'True' + x-ms-throttling-version: + - v2 + x-msedge-ref: + - 'Ref A: 8BD8E929C7A6401EA8E1E8B445131E1C Ref B: SJC211051203039 Ref C: 2026-01-30T23:57:20Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - changesafety changerecord update + Connection: + - keep-alive + ParameterSetName: + - -n --rollout-type --comments + User-Agent: + - AZURECLI/2.82.0 azsdk-python-core/1.37.0 Python/3.11.9 (Windows-10-10.0.26200-SP0) + method: GET + uri: https://centraluseuap.management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002?api-version=2026-01-01-preview + response: + body: + string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002","name":"cr000002","type":"microsoft.changesafety/changerecords","systemData":{"createdBy":"henrydai@microsoft.com","createdByType":"User","createdAt":"2026-01-30T23:57:18.6736676Z","lastModifiedBy":"henrydai@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2026-01-30T23:57:18.6736676Z"},"properties":{"changeType":"AppDeployment","rolloutType":"Normal","description":null,"stageMap":{"resourceId":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001","parameters":null},"parameters":null,"comments":null,"changeDefinition":{"name":"cr000002","kind":"Targets","details":{"targets":[{"subscriptionId":"$(az + account show --query id -o tsv)"}]}},"releaseLabel":null,"anticipatedStartTime":"2026-01-30T23:57:17Z","anticipatedEndTime":"2026-01-31T07:57:17Z","orchestrationTool":null,"links":null,"additionalData":null,"stageMapSnapshot":[{"key":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001","value":{"properties":{"parameters":null,"stages":[{"name":"Canary","sequence":1,"stageVariables":null,"nestedStageMap":null},{"name":"Production","sequence":2,"stageVariables":null,"nestedStageMap":null}]},"id":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001","name":"stgmap000001","type":"microsoft.changesafety/stagemaps"}}],"status":"Initialized","provisioningState":"Succeeded"}}' + headers: + cache-control: + - no-cache + content-length: + - '1579' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 30 Jan 2026 23:57:20 GMT + etag: + - '"01008f92-0000-0600-0000-697d455f0000"' + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-operation-identifier: + - tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47,objectId=bfa18db0-78d5-4d4d-8796-642151886af4/centraluseuap/9e5b9843-4d8a-483e-87dd-a72943bdb81a + x-ms-providerhub-traffic: + - 'True' + x-ms-throttling-version: + - v2 + x-msedge-ref: + - 'Ref A: F14C0102D53E438581AE5243388258BF Ref B: BY1AA1072320062 Ref C: 2026-01-30T23:57:20Z' + status: + code: 200 + message: OK +- request: + body: '{"properties": {"anticipatedEndTime": "2026-01-31T07:57:17Z", "anticipatedStartTime": + "2026-01-30T23:57:17Z", "changeDefinition": {"name": "cr000002", "kind": "Targets", + "details": {"targets": [{"subscriptionId": "$(az account show --query id -o + tsv)"}]}}, "changeType": "AppDeployment", "comments": "Escalated", "rolloutType": + "Emergency", "stageMap": {"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001"}}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - changesafety changerecord update + Connection: + - keep-alive + Content-Length: + - '480' + Content-Type: + - application/json + ParameterSetName: + - -n --rollout-type --comments + User-Agent: + - AZURECLI/2.82.0 azsdk-python-core/1.37.0 Python/3.11.9 (Windows-10-10.0.26200-SP0) + method: PUT + uri: https://centraluseuap.management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002?api-version=2026-01-01-preview + response: + body: + string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002","name":"cr000002","type":"microsoft.changesafety/changerecords","systemData":{"createdBy":"henrydai@microsoft.com","createdByType":"User","createdAt":"2026-01-30T23:57:18.6736676Z","lastModifiedBy":"henrydai@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2026-01-30T23:57:21.736435Z"},"properties":{"changeType":"AppDeployment","rolloutType":"Emergency","description":null,"stageMap":{"resourceId":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001","parameters":null},"parameters":null,"comments":"Escalated","changeDefinition":{"name":"cr000002","kind":"Targets","details":{"targets":[{"subscriptionId":"$(az + account show --query id -o tsv)"}]}},"releaseLabel":null,"anticipatedStartTime":"2026-01-30T23:57:17Z","anticipatedEndTime":"2026-01-31T07:57:17Z","orchestrationTool":null,"links":null,"additionalData":null,"stageMapSnapshot":[{"key":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001","value":{"properties":{"parameters":null,"stages":[{"name":"Canary","sequence":1,"stageVariables":null,"nestedStageMap":null},{"name":"Production","sequence":2,"stageVariables":null,"nestedStageMap":null}]},"id":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001","name":"stgmap000001","type":"microsoft.changesafety/stagemaps"}}],"status":"Initialized","provisioningState":"Succeeded"}}' + headers: + cache-control: + - no-cache + content-length: + - '1588' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 30 Jan 2026 23:57:21 GMT + etag: + - '"01009292-0000-0600-0000-697d45620000"' + expires: + - '-1' + mise-correlation-id: + - 5ab41fa4-e0cb-4fd1-b2fb-d1f0a255187d + pragma: + - no-cache + request-context: + - appId=cid-v1:0b4319bc-3dec-4ac3-88c8-c1b86ec8e1cf + set-cookie: + - ARRAffinity=1fef50c7b13f746942d4dbd0dbb97673928df1f977cb37e394a2ffeda3575190;Path=/;HttpOnly;Secure;Domain=canary.changecontrol.changesafety.msft.net + - ARRAffinitySameSite=1fef50c7b13f746942d4dbd0dbb97673928df1f977cb37e394a2ffeda3575190;Path=/;HttpOnly;SameSite=None;Secure;Domain=canary.changecontrol.changesafety.msft.net + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Accept-Encoding + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-operation-identifier: + - tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47,objectId=bfa18db0-78d5-4d4d-8796-642151886af4/centraluseuap/7c7c4a42-c057-4bff-8be3-5bb3e675fe44 + x-ms-providerhub-traffic: + - 'True' + x-ms-ratelimit-remaining-subscription-writes: + - '798' + x-ms-throttling-version: + - v2 + x-msedge-ref: + - 'Ref A: 48E5E9B82C2A4954AC28D8AFF37CB1EE Ref B: SJC211051204049 Ref C: 2026-01-30T23:57:21Z' + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +- request: + body: '{"properties": {"stageReference": "Canary", "status": "InProgress"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - changesafety stageprogression create + Connection: + - keep-alive + Content-Length: + - '68' + Content-Type: + - application/json + ParameterSetName: + - --change-record-name -n --stage-reference --status + User-Agent: + - AZURECLI/2.82.0 azsdk-python-core/1.37.0 Python/3.11.9 (Windows-10-10.0.26200-SP0) + method: PUT + uri: https://centraluseuap.management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002/stageProgressions/prog000003?api-version=2026-01-01-preview + response: + body: + string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002/stageProgressions/prog000003","name":"prog000003","type":"microsoft.changesafety/changerecords/stageprogressions","systemData":{"createdBy":"henrydai@microsoft.com","createdByType":"User","createdAt":"2026-01-30T23:57:23.0985529Z","lastModifiedBy":"henrydai@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2026-01-30T23:57:23.0985529Z"},"properties":{"stageReference":"Canary","sequence":1,"status":"InProgress","comments":null,"stageVariables":{},"links":null,"additionalData":null,"parameters":null,"provisioningState":"Succeeded"}}' + headers: + cache-control: + - no-cache + content-length: + - '662' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 30 Jan 2026 23:57:23 GMT + etag: + - '"0100cf36-0000-0600-0000-697d45640000"' + expires: + - '-1' + mise-correlation-id: + - 21315742-e793-4da9-b429-126bf08952e4 + pragma: + - no-cache + request-context: + - appId=cid-v1:0b4319bc-3dec-4ac3-88c8-c1b86ec8e1cf + set-cookie: + - ARRAffinity=1fef50c7b13f746942d4dbd0dbb97673928df1f977cb37e394a2ffeda3575190;Path=/;HttpOnly;Secure;Domain=canary.changecontrol.changesafety.msft.net + - ARRAffinitySameSite=1fef50c7b13f746942d4dbd0dbb97673928df1f977cb37e394a2ffeda3575190;Path=/;HttpOnly;SameSite=None;Secure;Domain=canary.changecontrol.changesafety.msft.net + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Accept-Encoding + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-operation-identifier: + - tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47,objectId=bfa18db0-78d5-4d4d-8796-642151886af4/centraluseuap/ee4a3223-66da-41a1-a160-ddd37e0606ad + x-ms-providerhub-traffic: + - 'True' + x-ms-ratelimit-remaining-subscription-writes: + - '799' + x-ms-throttling-version: + - v2 + x-msedge-ref: + - 'Ref A: AC7C7262D2294BC8BDE6C93ADE14FED0 Ref B: SJC211051205029 Ref C: 2026-01-30T23:57:22Z' + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - changesafety stageprogression show + Connection: + - keep-alive + ParameterSetName: + - --change-record-name -n + User-Agent: + - AZURECLI/2.82.0 azsdk-python-core/1.37.0 Python/3.11.9 (Windows-10-10.0.26200-SP0) + method: GET + uri: https://centraluseuap.management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002/stageProgressions/prog000003?api-version=2026-01-01-preview + response: + body: + string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002/stageProgressions/prog000003","name":"prog000003","type":"microsoft.changesafety/changerecords/stageprogressions","systemData":{"createdBy":"henrydai@microsoft.com","createdByType":"User","createdAt":"2026-01-30T23:57:23.0985529Z","lastModifiedBy":"henrydai@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2026-01-30T23:57:23.0985529Z"},"properties":{"stageReference":"Canary","sequence":1,"status":"InProgress","comments":null,"stageVariables":{},"links":null,"additionalData":null,"parameters":null,"provisioningState":"Succeeded"}}' + headers: + cache-control: + - no-cache + content-length: + - '662' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 30 Jan 2026 23:57:24 GMT + etag: + - '"0100cf36-0000-0600-0000-697d45640000"' + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-operation-identifier: + - tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47,objectId=bfa18db0-78d5-4d4d-8796-642151886af4/centraluseuap/4d267494-c0e0-4a53-a422-59e8348c9c33 + x-ms-providerhub-traffic: + - 'True' + x-ms-throttling-version: + - v2 + x-msedge-ref: + - 'Ref A: 152AD7AF2C604838BB8C00DF3798C2A6 Ref B: BY1AA1072317052 Ref C: 2026-01-30T23:57:24Z' + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - changesafety stageprogression update + Connection: + - keep-alive + ParameterSetName: + - --change-record-name -n --status --comments + User-Agent: + - AZURECLI/2.82.0 azsdk-python-core/1.37.0 Python/3.11.9 (Windows-10-10.0.26200-SP0) + method: GET + uri: https://centraluseuap.management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002/stageProgressions/prog000003?api-version=2026-01-01-preview + response: + body: + string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002/stageProgressions/prog000003","name":"prog000003","type":"microsoft.changesafety/changerecords/stageprogressions","systemData":{"createdBy":"henrydai@microsoft.com","createdByType":"User","createdAt":"2026-01-30T23:57:23.0985529Z","lastModifiedBy":"henrydai@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2026-01-30T23:57:23.0985529Z"},"properties":{"stageReference":"Canary","sequence":1,"status":"InProgress","comments":null,"stageVariables":{},"links":null,"additionalData":null,"parameters":null,"provisioningState":"Succeeded"}}' + headers: + cache-control: + - no-cache + content-length: + - '662' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 30 Jan 2026 23:57:25 GMT + etag: + - '"0100cf36-0000-0600-0000-697d45640000"' + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-operation-identifier: + - tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47,objectId=bfa18db0-78d5-4d4d-8796-642151886af4/centraluseuap/2f69979d-5b21-47c4-9cf1-86fcde22b471 + x-ms-providerhub-traffic: + - 'True' + x-ms-throttling-version: + - v2 + x-msedge-ref: + - 'Ref A: 4E3F60CE46794D0CA380EF0F53F76118 Ref B: BY1AA1072317029 Ref C: 2026-01-30T23:57:25Z' + status: + code: 200 + message: OK +- request: + body: '{"properties": {"comments": "Canary passed", "stageReference": "Canary", + "stageVariables": {}, "status": "Completed"}}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + CommandName: + - changesafety stageprogression update + Connection: + - keep-alive + Content-Length: + - '118' + Content-Type: + - application/json + ParameterSetName: + - --change-record-name -n --status --comments + User-Agent: + - AZURECLI/2.82.0 azsdk-python-core/1.37.0 Python/3.11.9 (Windows-10-10.0.26200-SP0) + method: PUT + uri: https://centraluseuap.management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002/stageProgressions/prog000003?api-version=2026-01-01-preview + response: + body: + string: '{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002/stageProgressions/prog000003","name":"prog000003","type":"microsoft.changesafety/changerecords/stageprogressions","systemData":{"createdBy":"henrydai@microsoft.com","createdByType":"User","createdAt":"2026-01-30T23:57:23.0985529Z","lastModifiedBy":"henrydai@microsoft.com","lastModifiedByType":"User","lastModifiedAt":"2026-01-30T23:57:26.5050099Z"},"properties":{"stageReference":"Canary","sequence":1,"status":"Completed","comments":"Canary + passed","stageVariables":{},"links":null,"additionalData":null,"parameters":null,"provisioningState":"Succeeded"}}' + headers: + cache-control: + - no-cache + content-length: + - '672' + content-type: + - application/json; charset=utf-8 + date: + - Fri, 30 Jan 2026 23:57:26 GMT + etag: + - '"0100d636-0000-0600-0000-697d45670000"' + expires: + - '-1' + mise-correlation-id: + - e7ec8798-0e74-4e56-836b-14b263179d07 + pragma: + - no-cache + request-context: + - appId=cid-v1:0b4319bc-3dec-4ac3-88c8-c1b86ec8e1cf + set-cookie: + - ARRAffinity=1fef50c7b13f746942d4dbd0dbb97673928df1f977cb37e394a2ffeda3575190;Path=/;HttpOnly;Secure;Domain=canary.changecontrol.changesafety.msft.net + - ARRAffinitySameSite=1fef50c7b13f746942d4dbd0dbb97673928df1f977cb37e394a2ffeda3575190;Path=/;HttpOnly;SameSite=None;Secure;Domain=canary.changecontrol.changesafety.msft.net + strict-transport-security: + - max-age=31536000; includeSubDomains + vary: + - Accept-Encoding + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-operation-identifier: + - tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47,objectId=bfa18db0-78d5-4d4d-8796-642151886af4/centraluseuap/0971d18a-c831-4e07-ab10-b82042bb1687 + x-ms-providerhub-traffic: + - 'True' + x-ms-ratelimit-remaining-subscription-writes: + - '799' + x-ms-throttling-version: + - v2 + x-msedge-ref: + - 'Ref A: 5FBEF40650D64B8D92791CA299F3B0BE Ref B: BY1AA1072317054 Ref C: 2026-01-30T23:57:26Z' + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - changesafety stageprogression delete + Connection: + - keep-alive + Content-Length: + - '0' + ParameterSetName: + - --change-record-name -n --yes + User-Agent: + - AZURECLI/2.82.0 azsdk-python-core/1.37.0 Python/3.11.9 (Windows-10-10.0.26200-SP0) + method: DELETE + uri: https://centraluseuap.management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/changeRecords/cr000002/stageProgressions/prog000003?api-version=2026-01-01-preview + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Fri, 30 Jan 2026 23:57:28 GMT + expires: + - '-1' + mise-correlation-id: + - ce63e030-75ec-4f6d-94ca-604304b1bf28 + pragma: + - no-cache + request-context: + - appId=cid-v1:0b4319bc-3dec-4ac3-88c8-c1b86ec8e1cf + set-cookie: + - ARRAffinity=a1dd3bd4b2e949edd11f1ea24dd6378e16e4ac0eff19d14c7d7df3c670446536;Path=/;HttpOnly;Secure;Domain=canary.changecontrol.changesafety.msft.net + - ARRAffinitySameSite=a1dd3bd4b2e949edd11f1ea24dd6378e16e4ac0eff19d14c7d7df3c670446536;Path=/;HttpOnly;SameSite=None;Secure;Domain=canary.changecontrol.changesafety.msft.net + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-operation-identifier: + - tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47,objectId=bfa18db0-78d5-4d4d-8796-642151886af4/centraluseuap/1e9ff96c-7ab2-44fe-bcef-d2f59833500b + x-ms-providerhub-traffic: + - 'True' + x-ms-ratelimit-remaining-subscription-deletes: + - '799' + x-ms-throttling-version: + - v2 + x-msedge-ref: + - 'Ref A: 65C659F6A60D4FFCB01C954F5F794CC9 Ref B: BY1AA1072316060 Ref C: 2026-01-30T23:57:28Z' + x-powered-by: + - ASP.NET + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + CommandName: + - changesafety stagemap delete + Connection: + - keep-alive + Content-Length: + - '0' + ParameterSetName: + - --stage-map-name --yes + User-Agent: + - AZURECLI/2.82.0 azsdk-python-core/1.37.0 Python/3.11.9 (Windows-10-10.0.26200-SP0) + method: DELETE + uri: https://centraluseuap.management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ChangeSafety/stageMaps/stgmap000001?api-version=2026-01-01-preview + response: + body: + string: '' + headers: + cache-control: + - no-cache + content-length: + - '0' + date: + - Fri, 30 Jan 2026 23:57:28 GMT + expires: + - '-1' + pragma: + - no-cache + strict-transport-security: + - max-age=31536000; includeSubDomains + x-cache: + - CONFIG_NOCACHE + x-content-type-options: + - nosniff + x-ms-operation-identifier: + - tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47,objectId=bfa18db0-78d5-4d4d-8796-642151886af4/centraluseuap/f1530df3-c537-48cc-8684-c584eb160025 + x-ms-providerhub-traffic: + - 'True' + x-ms-ratelimit-remaining-subscription-deletes: + - '799' + x-ms-throttling-version: + - v2 + x-msedge-ref: + - 'Ref A: 8FA563CFF1054372AC560B958933804F Ref B: BY1AA1072317054 Ref C: 2026-01-30T23:57:29Z' + status: + code: 200 + message: OK +version: 1 diff --git a/src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py b/src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py deleted file mode 100644 index 94415fca1fa..00000000000 --- a/src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py +++ /dev/null @@ -1,480 +0,0 @@ -# -------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -# Code generated by aaz-dev-tools -# -------------------------------------------------------------------------------------------- - -import copy -import datetime -import json -import os -import sys -import tempfile -import types -from types import SimpleNamespace -from unittest import mock - -from azure.cli.testsdk import * # pylint: disable=wildcard-import,unused-wildcard-import - -from azext_changesafety.custom import ( - ChangeStateCreate, - ChangeStateDelete, - ChangeStateShow, - ChangeStateUpdate, - _inject_change_definition_into_content, - _inject_targets_into_result, - _normalize_targets_arg, -) -from azure.cli.core.aaz import AAZAnyType, has_value -from azure.cli.core.aaz._arg_action import AAZArgActionOperations, _ELEMENT_APPEND_KEY - - -class ChangeStateScenario(ScenarioTest): - FAKE_SUBSCRIPTION_ID = "00000000-0000-0000-0000-000000000000" - _SCENARIO_STATE = {} - - class _DummyPoller: # pylint: disable=too-few-public-methods - def result(self, timeout=None): # pylint: disable=unused-argument - return None - - def wait(self, timeout=None): # pylint: disable=unused-argument - return None - - def done(self): - return True - - def add_done_callback(self, func): - if func: - func(self) - - @staticmethod - def _dummy_ctx_with_change_definition(payload): - dummy = SimpleNamespace() - dummy.to_serialized_data = lambda: payload - return SimpleNamespace(vars=SimpleNamespace(change_definition=dummy)) - - @classmethod - def _ensure_msrestazure_stub(cls): - if 'msrestazure' in sys.modules: - return - - msrestazure = types.ModuleType('msrestazure') - azure_operation = types.ModuleType('msrestazure.azure_operation') - - class AzureOperationPoller: # pylint: disable=too-few-public-methods - def _delay(self, *args, **kwargs): # pylint: disable=unused-argument - return - - azure_operation.AzureOperationPoller = AzureOperationPoller - arm_polling = types.ModuleType('msrestazure.polling.arm_polling') - - class ARMPolling: # pylint: disable=too-few-public-methods - def _delay(self, *args, **kwargs): # pylint: disable=unused-argument - return - - arm_polling.ARMPolling = ARMPolling - polling = types.ModuleType('msrestazure.polling') - polling.arm_polling = arm_polling - - msrestazure.azure_operation = azure_operation - msrestazure.polling = polling - - sys.modules['msrestazure'] = msrestazure - sys.modules['msrestazure.azure_operation'] = azure_operation - sys.modules['msrestazure.polling'] = polling - sys.modules['msrestazure.polling.arm_polling'] = arm_polling - - @staticmethod - def _get_arg_value(cmd, arg_name, default=None): - arg = getattr(cmd.ctx.args, arg_name, None) - if arg is None or not has_value(arg): - return default - return arg.to_serialized_data() - - @staticmethod - def _build_mock_instance( - name, - resource_group, - subscription_id, - change_type, - rollout_type, - targets, - comments=None, - change_definition=None, - stage_map=None, - anticipated_start_time=None, - anticipated_end_time=None): - return { - "id": f"/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.ChangeSafety/changeStates/{name}", - "name": name, - "type": "Microsoft.ChangeSafety/changeStates", - "location": "eastus", - "properties": { - "changeType": change_type, - "rolloutType": rollout_type, - "comments": comments, - "anticipatedStartTime": anticipated_start_time, - "anticipatedEndTime": anticipated_end_time, - "stageMap": stage_map, - "changeDefinition": change_definition or { - "kind": "Targets", - "name": name, - "details": { - "targets": targets or [] - } - } - } - } - - @staticmethod - def _mock_create_execute(cmd): - cls = ChangeStateScenario - cmd.pre_operations() - name = cls._get_arg_value(cmd, "change_state_name", "mock-change") - resource_group = cls._get_arg_value(cmd, "resource_group", "mock-rg") - subscription_id = cmd.ctx.subscription_id or cls.FAKE_SUBSCRIPTION_ID - change_type = cls._get_arg_value(cmd, "change_type", "ManualTouch") - rollout_type = cls._get_arg_value(cmd, "rollout_type", "Normal") - comments = cls._get_arg_value(cmd, "comments") - targets = copy.deepcopy(cmd._parsed_targets or []) - change_definition_var = getattr(cmd.ctx.vars, "change_definition", None) - change_definition_value = change_definition_var.to_serialized_data() if change_definition_var else None - stage_map_arg = getattr(cmd.ctx.args, "stage_map", None) - stage_map_value = None - if stage_map_arg is not None: - if hasattr(stage_map_arg, "to_serialized_data") and has_value(stage_map_arg): - stage_map_value = stage_map_arg.to_serialized_data() - elif isinstance(stage_map_arg, dict): - stage_map_value = stage_map_arg - if isinstance(stage_map_value, dict) and "resource_id" in stage_map_value and "resourceId" not in stage_map_value: - stage_map_value = {**stage_map_value} - stage_map_value["resourceId"] = stage_map_value.pop("resource_id") - start_time = cls._get_arg_value(cmd, "anticipated_start_time") - end_time = cls._get_arg_value(cmd, "anticipated_end_time") - instance = cls._build_mock_instance( - name=name, - resource_group=resource_group, - subscription_id=subscription_id, - change_type=change_type, - rollout_type=rollout_type, - targets=targets, - comments=comments, - change_definition=change_definition_value, - stage_map=stage_map_value, - anticipated_start_time=start_time, - anticipated_end_time=end_time, - ) - cls._SCENARIO_STATE["instance"] = copy.deepcopy(instance) - cmd.ctx.set_var("instance", copy.deepcopy(instance), schema_builder=lambda: AAZAnyType()) - cmd.post_operations() - return iter(()) - - @staticmethod - def _mock_update_execute(cmd): - cls = ChangeStateScenario - cmd._raw_targets = [token for token in (cmd._raw_targets or []) if token and token != 'Undefined'] # pylint: disable=protected-access - cmd.pre_operations() - current = copy.deepcopy(cls._SCENARIO_STATE.get("instance")) - if current is None: - name = cls._get_arg_value(cmd, "change_state_name", "mock-change") - resource_group = cls._get_arg_value(cmd, "resource_group", "mock-rg") - subscription_id = cmd.ctx.subscription_id or cls.FAKE_SUBSCRIPTION_ID - current = cls._build_mock_instance( - name=name, - resource_group=resource_group, - subscription_id=subscription_id, - change_type="ManualTouch", - rollout_type="Normal", - targets=[], - ) - new_change_type = cls._get_arg_value(cmd, "change_type") - new_rollout = cls._get_arg_value(cmd, "rollout_type") - new_comments = cls._get_arg_value(cmd, "comments") - new_start = cls._get_arg_value(cmd, "anticipated_start_time") - new_end = cls._get_arg_value(cmd, "anticipated_end_time") - stage_map_arg = getattr(cmd.ctx.args, "stage_map", None) - stage_map_value = None - if stage_map_arg is not None: - if hasattr(stage_map_arg, "to_serialized_data") and has_value(stage_map_arg): - stage_map_value = stage_map_arg.to_serialized_data() - elif isinstance(stage_map_arg, dict): - stage_map_value = stage_map_arg - if isinstance(stage_map_value, dict) and "resource_id" in stage_map_value and "resourceId" not in stage_map_value: - stage_map_value = {**stage_map_value} - stage_map_value["resourceId"] = stage_map_value.pop("resource_id") - change_definition_var = getattr(cmd.ctx.vars, "change_definition", None) - change_definition_value = change_definition_var.to_serialized_data() if change_definition_var else None - if new_change_type: - current["properties"]["changeType"] = new_change_type - if new_rollout: - current["properties"]["rolloutType"] = new_rollout - if new_comments is not None: - current["properties"]["comments"] = new_comments - if new_start is not None: - current["properties"]["anticipatedStartTime"] = new_start - if new_end is not None: - current["properties"]["anticipatedEndTime"] = new_end - if stage_map_value is not None: - current["properties"]["stageMap"] = stage_map_value - if change_definition_value is not None: - current["properties"]["changeDefinition"] = change_definition_value - elif cmd._parsed_targets: # pylint: disable=protected-access - current["properties"]["changeDefinition"]["details"]["targets"] = copy.deepcopy(cmd._parsed_targets) # pylint: disable=protected-access - cls._SCENARIO_STATE["instance"] = copy.deepcopy(current) - cmd.ctx.set_var("instance", copy.deepcopy(current), schema_builder=lambda: AAZAnyType()) - cmd.post_operations() - return iter(()) - - @staticmethod - def _mock_show_execute(cmd): - cls = ChangeStateScenario - cmd.pre_operations() - instance = copy.deepcopy(cls._SCENARIO_STATE.get("instance")) - cmd.ctx.set_var("instance", instance, schema_builder=lambda: AAZAnyType()) - cmd.post_operations() - return iter(()) - - @staticmethod - def _mock_delete_execute(cmd): - cls = ChangeStateScenario - cmd.pre_operations() - cls._SCENARIO_STATE.pop("instance", None) - cmd.post_operations() - return iter(()) - - @staticmethod - def _mock_build_lro_poller(cmd, executor, extract_result): # pylint: disable=unused-argument - executor() - return ChangeStateScenario._DummyPoller() # pylint: disable=protected-access - - def setUp(self): - type(self)._ensure_msrestazure_stub() - super().setUp() - type(self)._SCENARIO_STATE.clear() - self._patchers = [ - mock.patch('azext_changesafety.custom.ChangeStateCreate._execute_operations', new=type(self)._mock_create_execute), - mock.patch('azext_changesafety.custom.ChangeStateUpdate._execute_operations', new=type(self)._mock_update_execute), - mock.patch('azext_changesafety.custom.ChangeStateShow._execute_operations', new=type(self)._mock_show_execute), - mock.patch('azext_changesafety.custom.ChangeStateDelete._execute_operations', new=type(self)._mock_delete_execute), - mock.patch('azext_changesafety.custom.ChangeStateDelete.build_lro_poller', new=type(self)._mock_build_lro_poller), - ] - for patcher in self._patchers: - patcher.start() - self.addCleanup(patcher.stop) - - def test_normalize_targets_from_operations(self): - operations = AAZArgActionOperations.__new__(AAZArgActionOperations) - operations._ops = [ # pylint: disable=protected-access - ((_ELEMENT_APPEND_KEY,), "env=prod"), - ((0, "resourceId"), "/subscriptions/000/resourceGroups/rg/providers/Microsoft.Web/sites/app"), - ((0, "operation"), "delete"), - ((1,), "subscriptionId=00000000-0000-0000-0000-000000000000"), - ] - - normalized = _normalize_targets_arg(operations) - - assert normalized == [ - "env=prod,resourceId=/subscriptions/000/resourceGroups/rg/providers/Microsoft.Web/sites/app,operation=delete", - "subscriptionId=00000000-0000-0000-0000-000000000000", - ] - - def test_normalize_targets_from_serializable_value(self): - class DummySerializable: - def to_serialized_data(self): - return ["rg=my-rg", None, "", "operation=show"] - - normalized = _normalize_targets_arg(DummySerializable()) - - assert normalized == ["rg=my-rg", "operation=show"] - - def test_normalize_targets_from_list_of_strings(self): - raw_targets = [" resourceId=/foo ", "", "operation=PUT", None] - - normalized = _normalize_targets_arg(raw_targets) - - assert normalized == ["resourceId=/foo", "operation=PUT"] - - def test_normalize_targets_with_none_returns_empty(self): - assert _normalize_targets_arg(None) == [] - - def test_inject_change_definition_into_content_adds_properties(self): - ctx = self._dummy_ctx_with_change_definition({"details": {"targets": []}}) - content = {"properties": {"existing": "value"}} - - result = _inject_change_definition_into_content(content, ctx) - - assert result["properties"]["existing"] == "value" - assert result["properties"]["changeDefinition"] == {"details": {"targets": []}} - - def test_inject_change_definition_with_empty_payload_noop(self): - ctx = self._dummy_ctx_with_change_definition({}) - original = {"properties": {"foo": "bar"}} - - result = _inject_change_definition_into_content(original.copy(), ctx) - - assert result == original - - def test_inject_targets_into_result_updates_nested_properties(self): - data = {"properties": {"changeDefinition": {"details": {}}}} - targets = [{"resourceId": "/foo"}] - - _inject_targets_into_result(data, targets) - - assert data["properties"]["changeDefinition"]["details"]["targets"] == targets - - def test_inject_targets_does_not_override_existing(self): - existing = [{"resourceId": "/existing"}] - data = {"changeDefinition": {"details": {"targets": existing.copy()}}} - new_targets = [{"resourceId": "/new"}] - - _inject_targets_into_result(data, new_targets) - - assert data["changeDefinition"]["details"]["targets"] == existing - - def test_default_schedule_times_applied_on_create(self): - resource_group = "rgScheduleDefaults" - change_state_name = self.create_random_name('chg', 12) - target_resource = ( - f"/subscriptions/{self.FAKE_SUBSCRIPTION_ID}/resourceGroups/{resource_group}/" - "providers/Microsoft.Compute/virtualMachines/myVm" - ) - self.kwargs.update({ - "rg": resource_group, - "name": change_state_name, - "change_type": "ManualTouch", - "rollout_type": "Normal", - "targets": f"resourceId={target_resource},operation=PATCH", - }) - - result = self.cmd( - 'az changesafety changestate create -g {rg} -n {name} ' - '--change-type {change_type} --rollout-type {rollout_type} ' - '--targets "{targets}"', - ).get_output_in_json() - - start = datetime.datetime.fromisoformat(result['properties']['anticipatedStartTime'].replace('Z', '+00:00')) - end = datetime.datetime.fromisoformat(result['properties']['anticipatedEndTime'].replace('Z', '+00:00')) - delta_seconds = abs((end - start).total_seconds() - 8 * 3600) - self.assertLessEqual(delta_seconds, 5) - - def test_create_with_change_definition_without_targets(self): - resource_group = "rgChangeDefinition" - change_state_name = self.create_random_name('chg', 12) - target_resource = ( - f"/subscriptions/{self.FAKE_SUBSCRIPTION_ID}/resourceGroups/{resource_group}/" - "providers/Microsoft.Compute/virtualMachines/myVm" - ) - change_definition = { - "kind": "Targets", - "name": change_state_name, - "details": { - "targets": [ - {"resourceId": target_resource, "httpMethod": "DELETE"} - ] - } - } - with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as handle: - json.dump(change_definition, handle) - change_def_path = handle.name - self.addCleanup(lambda: os.path.exists(change_def_path) and os.remove(change_def_path)) - self.kwargs.update({ - "rg": resource_group, - "name": change_state_name, - "change_type": "ManualTouch", - "rollout_type": "Hotfix", - "change_definition": change_def_path, - }) - - result = self.cmd( - 'az changesafety changestate create -g {rg} -n {name} ' - '--change-type {change_type} --rollout-type {rollout_type} ' - '--change-definition "@{change_definition}"', - ).get_output_in_json() - - self.assertEqual( - result["properties"]["changeDefinition"]["details"]["targets"][0]["resourceId"], - target_resource, - ) - self.assertEqual( - result["properties"]["changeDefinition"]["details"]["targets"][0]["httpMethod"], - "DELETE", - ) - - def test_stage_map_name_shortcut(self): - resource_group = "rgStageMapShortcut" - change_state_name = self.create_random_name('chg', 12) - stage_map_name = "rollout-plan" - target_resource = ( - f"/subscriptions/{self.FAKE_SUBSCRIPTION_ID}/resourceGroups/{resource_group}/" - "providers/Microsoft.Storage/storageAccounts/demo" - ) - expected_stage_map = f"/subscriptions/{self.FAKE_SUBSCRIPTION_ID}/providers/Microsoft.ChangeSafety/stageMaps/{stage_map_name}" - self.kwargs.update({ - "rg": resource_group, - "name": change_state_name, - "change_type": "ManualTouch", - "rollout_type": "Normal", - "targets": f"resourceId={target_resource},operation=PATCH", - "stage_map_name": stage_map_name, - "subscription": self.FAKE_SUBSCRIPTION_ID, - }) - - result = self.cmd( - 'az changesafety changestate create -g {rg} -n {name} ' - '--change-type {change_type} --rollout-type {rollout_type} ' - '--targets "{targets}" --stagemap-name {stage_map_name} --subscription {subscription}', - ).get_output_in_json() - - self.assertEqual(result["properties"]["stageMap"]["resourceId"], expected_stage_map) - - def test_change_state_cli_scenario(self): - resource_group = "rgChangeSafetyScenario" - change_state_name = self.create_random_name('chg', 12) - target_resource = ( - f"/subscriptions/{self.FAKE_SUBSCRIPTION_ID}/resourceGroups/{resource_group}/" - "providers/Microsoft.Compute/virtualMachines/myVm" - ) - self.kwargs.update({ - "rg": resource_group, - "name": change_state_name, - "change_type": "ManualTouch", - "rollout_type": "Normal", - "updated_rollout": "Emergency", - "targets": f"resourceId={target_resource},operation=PATCH", - }) - - create_checks = [ - JMESPathCheck('name', change_state_name), - JMESPathCheck('properties.changeType', 'ManualTouch'), - JMESPathCheck('properties.rolloutType', 'Normal'), - JMESPathCheck('properties.changeDefinition.details.targets[0].resourceId', target_resource), - JMESPathCheck('properties.changeDefinition.details.targets[0].httpMethod', 'PATCH'), - ] - self.cmd( - 'az changesafety changestate create -g {rg} -n {name} ' - '--change-type {change_type} --rollout-type {rollout_type} ' - '--targets "{targets}" --comments "Initial deployment"', - checks=create_checks, - ) - - update_checks = [ - JMESPathCheck('properties.rolloutType', 'Emergency'), - JMESPathCheck('properties.comments', 'Escalated rollout'), - ] - self.cmd( - 'az changesafety changestate update -g {rg} -n {name} ' - '--rollout-type {updated_rollout} --comments "Escalated rollout"', - checks=update_checks, - ) - - self.cmd( - 'az changesafety changestate show -g {rg} -n {name}', - checks=[ - JMESPathCheck('properties.comments', 'Escalated rollout'), - JMESPathCheck('properties.changeDefinition.details.targets[0].resourceId', target_resource), - ], - ) - - self.cmd('az changesafety changestate delete -g {rg} -n {name} -y') - self.assertNotIn("instance", type(self)._SCENARIO_STATE) diff --git a/src/azure-changesafety/azext_changesafety/tests/latest/test_changesafety.py b/src/azure-changesafety/azext_changesafety/tests/latest/test_changesafety.py new file mode 100644 index 00000000000..5d00644a84f --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/tests/latest/test_changesafety.py @@ -0,0 +1,1136 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +""" +Unit tests for azure-changesafety CLI extension. + +Tests cover ChangeRecord, StageMap, and StageProgression commands. +""" + +import copy +import datetime +import sys +import types +from types import SimpleNamespace +from unittest import mock + +from azure.cli.testsdk import ScenarioTest, JMESPathCheck + +from azext_changesafety.custom import ( + _inject_change_definition_into_content, + _inject_targets_into_result, + _normalize_targets_arg, +) +from azure.cli.core.aaz import AAZAnyType, has_value +from azure.cli.core.aaz._arg_action import AAZArgActionOperations, _ELEMENT_APPEND_KEY + + +# ============================================================================= +# ChangeRecord Tests +# ============================================================================= + + +class ChangeRecordScenario(ScenarioTest): + """Test scenarios for ChangeRecord CRUD operations.""" + + FAKE_SUBSCRIPTION_ID = "00000000-0000-0000-0000-000000000000" + _SCENARIO_STATE = {} + + class _DummyPoller: # pylint: disable=too-few-public-methods + """Mock poller for LRO operations.""" + + def result(self, timeout=None): # pylint: disable=unused-argument + return None + + def wait(self, timeout=None): # pylint: disable=unused-argument + return None + + def done(self): + return True + + def add_done_callback(self, func): + if func: + func(self) + + @staticmethod + def _dummy_ctx_with_change_definition(payload): + """Create a dummy context with change_definition variable.""" + dummy = SimpleNamespace() + dummy.to_serialized_data = lambda: payload + return SimpleNamespace(vars=SimpleNamespace(change_definition=dummy)) + + @classmethod + def _ensure_msrestazure_stub(cls): + """Ensure msrestazure stub is available for tests.""" + if 'msrestazure' in sys.modules: + return + + msrestazure = types.ModuleType('msrestazure') + azure_operation = types.ModuleType('msrestazure.azure_operation') + + class AzureOperationPoller: # pylint: disable=too-few-public-methods + def _delay(self, *args, **kwargs): # pylint: disable=unused-argument + return + + azure_operation.AzureOperationPoller = AzureOperationPoller + arm_polling = types.ModuleType('msrestazure.polling.arm_polling') + + class ARMPolling: # pylint: disable=too-few-public-methods + def _delay(self, *args, **kwargs): # pylint: disable=unused-argument + return + + arm_polling.ARMPolling = ARMPolling + polling = types.ModuleType('msrestazure.polling') + polling.arm_polling = arm_polling + + msrestazure.azure_operation = azure_operation + msrestazure.polling = polling + + sys.modules['msrestazure'] = msrestazure + sys.modules['msrestazure.azure_operation'] = azure_operation + sys.modules['msrestazure.polling'] = polling + sys.modules['msrestazure.polling.arm_polling'] = arm_polling + + @staticmethod + def _get_arg_value(cmd, arg_name, default=None): + """Get argument value from command context.""" + arg = getattr(cmd.ctx.args, arg_name, None) + if arg is None or not has_value(arg): + return default + return arg.to_serialized_data() + + @staticmethod + def _build_mock_instance( + name, + resource_group, + subscription_id, + change_type, + rollout_type, + targets, + comments=None, + change_definition=None, + stage_map=None, + anticipated_start_time=None, + anticipated_end_time=None): + """Build a mock ChangeRecord instance.""" + return { + "id": f"/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.ChangeSafety/changeRecords/{name}", + "name": name, + "type": "Microsoft.ChangeSafety/changeRecords", + "location": "eastus", + "properties": { + "changeType": change_type, + "rolloutType": rollout_type, + "comments": comments, + "anticipatedStartTime": anticipated_start_time, + "anticipatedEndTime": anticipated_end_time, + "stageMap": stage_map, + "changeDefinition": change_definition or { + "kind": "Targets", + "name": name, + "details": { + "targets": targets or [] + } + } + } + } + + @staticmethod + def _mock_create_execute(cmd): + """Mock execute for ChangeRecordCreate.""" + cls = ChangeRecordScenario + cmd.pre_operations() + name = cls._get_arg_value(cmd, "change_record_name", "mock-change") + resource_group = cls._get_arg_value(cmd, "resource_group", "mock-rg") + subscription_id = cmd.ctx.subscription_id or cls.FAKE_SUBSCRIPTION_ID + change_type = cls._get_arg_value(cmd, "change_type", "ManualTouch") + rollout_type = cls._get_arg_value(cmd, "rollout_type", "Normal") + comments = cls._get_arg_value(cmd, "comments") + targets = copy.deepcopy(cmd._parsed_targets or []) + change_definition_var = getattr(cmd.ctx.vars, "change_definition", None) + change_definition_value = change_definition_var.to_serialized_data() if change_definition_var else None + stage_map_arg = getattr(cmd.ctx.args, "stage_map", None) + stage_map_value = None + if stage_map_arg is not None: + if hasattr(stage_map_arg, "to_serialized_data") and has_value(stage_map_arg): + stage_map_value = stage_map_arg.to_serialized_data() + elif isinstance(stage_map_arg, dict): + stage_map_value = stage_map_arg + if isinstance(stage_map_value, dict) and "resource_id" in stage_map_value and "resourceId" not in stage_map_value: + stage_map_value = {**stage_map_value} + stage_map_value["resourceId"] = stage_map_value.pop("resource_id") + start_time = cls._get_arg_value(cmd, "anticipated_start_time") + end_time = cls._get_arg_value(cmd, "anticipated_end_time") + instance = cls._build_mock_instance( + name=name, + resource_group=resource_group, + subscription_id=subscription_id, + change_type=change_type, + rollout_type=rollout_type, + targets=targets, + comments=comments, + change_definition=change_definition_value, + stage_map=stage_map_value, + anticipated_start_time=start_time, + anticipated_end_time=end_time, + ) + cls._SCENARIO_STATE["instance"] = copy.deepcopy(instance) + cmd.ctx.set_var("instance", copy.deepcopy(instance), schema_builder=lambda: AAZAnyType()) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_update_execute(cmd): + """Mock execute for ChangeRecordUpdate.""" + cls = ChangeRecordScenario + cmd._parsed_targets = getattr(cmd, '_parsed_targets', None) or [] # pylint: disable=protected-access + cmd.pre_operations() + current = copy.deepcopy(cls._SCENARIO_STATE.get("instance")) + if current is None: + name = cls._get_arg_value(cmd, "change_record_name", "mock-change") + resource_group = cls._get_arg_value(cmd, "resource_group", "mock-rg") + subscription_id = cmd.ctx.subscription_id or cls.FAKE_SUBSCRIPTION_ID + current = cls._build_mock_instance( + name=name, + resource_group=resource_group, + subscription_id=subscription_id, + change_type="ManualTouch", + rollout_type="Normal", + targets=[], + ) + new_change_type = cls._get_arg_value(cmd, "change_type") + new_rollout = cls._get_arg_value(cmd, "rollout_type") + new_comments = cls._get_arg_value(cmd, "comments") + new_start = cls._get_arg_value(cmd, "anticipated_start_time") + new_end = cls._get_arg_value(cmd, "anticipated_end_time") + stage_map_arg = getattr(cmd.ctx.args, "stage_map", None) + stage_map_value = None + if stage_map_arg is not None: + if hasattr(stage_map_arg, "to_serialized_data") and has_value(stage_map_arg): + stage_map_value = stage_map_arg.to_serialized_data() + elif isinstance(stage_map_arg, dict): + stage_map_value = stage_map_arg + if isinstance(stage_map_value, dict) and "resource_id" in stage_map_value and "resourceId" not in stage_map_value: + stage_map_value = {**stage_map_value} + stage_map_value["resourceId"] = stage_map_value.pop("resource_id") + change_definition_var = getattr(cmd.ctx.vars, "change_definition", None) + change_definition_value = change_definition_var.to_serialized_data() if change_definition_var else None + if new_change_type: + current["properties"]["changeType"] = new_change_type + if new_rollout: + current["properties"]["rolloutType"] = new_rollout + if new_comments is not None: + current["properties"]["comments"] = new_comments + if new_start is not None: + current["properties"]["anticipatedStartTime"] = new_start + if new_end is not None: + current["properties"]["anticipatedEndTime"] = new_end + if stage_map_value is not None: + current["properties"]["stageMap"] = stage_map_value + if change_definition_value is not None: + current["properties"]["changeDefinition"] = change_definition_value + elif cmd._parsed_targets: # pylint: disable=protected-access + current["properties"]["changeDefinition"]["details"]["targets"] = copy.deepcopy(cmd._parsed_targets) # pylint: disable=protected-access + cls._SCENARIO_STATE["instance"] = copy.deepcopy(current) + cmd.ctx.set_var("instance", copy.deepcopy(current), schema_builder=lambda: AAZAnyType()) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_show_execute(cmd): + """Mock execute for ChangeRecordShow.""" + cls = ChangeRecordScenario + cmd.pre_operations() + instance = copy.deepcopy(cls._SCENARIO_STATE.get("instance")) + cmd.ctx.set_var("instance", instance, schema_builder=lambda: AAZAnyType()) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_delete_execute(cmd): + """Mock execute for ChangeRecordDelete.""" + cls = ChangeRecordScenario + cmd.pre_operations() + cls._SCENARIO_STATE.pop("instance", None) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_build_lro_poller(cmd, executor, extract_result): # pylint: disable=unused-argument + """Mock LRO poller builder.""" + executor() + return ChangeRecordScenario._DummyPoller() + + def setUp(self): + type(self)._ensure_msrestazure_stub() + super().setUp() + type(self)._SCENARIO_STATE.clear() + self._patchers = [ + mock.patch('azext_changesafety.custom.ChangeRecordCreate._execute_operations', new=type(self)._mock_create_execute), + mock.patch('azext_changesafety.custom.ChangeRecordUpdate._execute_operations', new=type(self)._mock_update_execute), + mock.patch('azext_changesafety.custom.ChangeRecordShow._execute_operations', new=type(self)._mock_show_execute), + mock.patch('azext_changesafety.custom.ChangeRecordDelete._execute_operations', new=type(self)._mock_delete_execute), + mock.patch('azext_changesafety.custom.ChangeRecordDelete.build_lro_poller', new=type(self)._mock_build_lro_poller), + ] + for patcher in self._patchers: + patcher.start() + self.addCleanup(patcher.stop) + + # ------------------------------------------------------------------------- + # Unit Tests for Helper Functions + # ------------------------------------------------------------------------- + + def test_normalize_targets_from_operations(self): + """Test _normalize_targets_arg with AAZArgActionOperations.""" + operations = AAZArgActionOperations.__new__(AAZArgActionOperations) + operations._ops = [ # pylint: disable=protected-access + ((_ELEMENT_APPEND_KEY,), "env=prod"), + ((0, "resourceId"), "/subscriptions/000/resourceGroups/rg/providers/Microsoft.Web/sites/app"), + ((0, "operation"), "delete"), + ((1,), "subscriptionId=00000000-0000-0000-0000-000000000000"), + ] + + normalized = _normalize_targets_arg(operations) + + assert normalized == [ + "env=prod,resourceId=/subscriptions/000/resourceGroups/rg/providers/Microsoft.Web/sites/app,operation=delete", + "subscriptionId=00000000-0000-0000-0000-000000000000", + ] + + def test_normalize_targets_from_serializable_value(self): + """Test _normalize_targets_arg with serializable value.""" + class DummySerializable: + def to_serialized_data(self): + return ["rg=my-rg", None, "", "operation=show"] + + normalized = _normalize_targets_arg(DummySerializable()) + + assert normalized == ["rg=my-rg", "operation=show"] + + def test_normalize_targets_from_list_of_strings(self): + """Test _normalize_targets_arg with list of strings.""" + raw_targets = [" resourceId=/foo ", "", "operation=PUT", None] + + normalized = _normalize_targets_arg(raw_targets) + + assert normalized == ["resourceId=/foo", "operation=PUT"] + + def test_normalize_targets_with_none_returns_empty(self): + """Test _normalize_targets_arg with None returns empty list.""" + assert _normalize_targets_arg(None) == [] + + def test_inject_change_definition_into_content_adds_properties(self): + """Test _inject_change_definition_into_content adds to properties.""" + ctx = self._dummy_ctx_with_change_definition({"details": {"targets": []}}) + content = {"properties": {"existing": "value"}} + + result = _inject_change_definition_into_content(content, ctx) + + assert result["properties"]["existing"] == "value" + assert result["properties"]["changeDefinition"] == {"details": {"targets": []}} + + def test_inject_change_definition_with_empty_payload_noop(self): + """Test _inject_change_definition_into_content with empty payload is no-op.""" + ctx = self._dummy_ctx_with_change_definition({}) + original = {"properties": {"foo": "bar"}} + + result = _inject_change_definition_into_content(original.copy(), ctx) + + assert result == original + + def test_inject_targets_into_result_updates_nested_properties(self): + """Test _inject_targets_into_result updates nested properties.""" + data = {"properties": {"changeDefinition": {"details": {}}}} + targets = [{"resourceId": "/foo"}] + + _inject_targets_into_result(data, targets) + + assert data["properties"]["changeDefinition"]["details"]["targets"] == targets + + def test_inject_targets_does_not_override_existing(self): + """Test _inject_targets_into_result does not override existing targets.""" + existing = [{"resourceId": "/existing"}] + data = {"changeDefinition": {"details": {"targets": existing.copy()}}} + new_targets = [{"resourceId": "/new"}] + + _inject_targets_into_result(data, new_targets) + + assert data["changeDefinition"]["details"]["targets"] == existing + + # ------------------------------------------------------------------------- + # Integration Tests for ChangeRecord Commands + # ------------------------------------------------------------------------- + + def test_default_schedule_times_applied_on_create(self): + """Test that default schedule times are applied on create.""" + resource_group = "rgScheduleDefaults" + change_record_name = self.create_random_name('chg', 12) + target_resource = ( + f"/subscriptions/{self.FAKE_SUBSCRIPTION_ID}/resourceGroups/{resource_group}/" + "providers/Microsoft.Compute/virtualMachines/myVm" + ) + self.kwargs.update({ + "rg": resource_group, + "name": change_record_name, + "change_type": "ManualTouch", + "rollout_type": "Normal", + "targets": f"resourceId={target_resource},operation=PATCH", + }) + + result = self.cmd( + 'az changesafety changerecord create -g {rg} -n {name} ' + '--change-type {change_type} --rollout-type {rollout_type} ' + '--targets "{targets}"', + ).get_output_in_json() + + start = datetime.datetime.fromisoformat(result['properties']['anticipatedStartTime'].replace('Z', '+00:00')) + end = datetime.datetime.fromisoformat(result['properties']['anticipatedEndTime'].replace('Z', '+00:00')) + delta_seconds = abs((end - start).total_seconds() - 8 * 3600) + self.assertLessEqual(delta_seconds, 5) + + def test_stage_map_name_shortcut(self): + """Test --stagemap-name shortcut is expanded to full resource ID.""" + resource_group = "rgStageMapShortcut" + change_record_name = self.create_random_name('chg', 12) + stage_map_name = "rollout-plan" + target_resource = ( + f"/subscriptions/{self.FAKE_SUBSCRIPTION_ID}/resourceGroups/{resource_group}/" + "providers/Microsoft.Storage/storageAccounts/demo" + ) + expected_stage_map = f"/subscriptions/{self.FAKE_SUBSCRIPTION_ID}/providers/Microsoft.ChangeSafety/stageMaps/{stage_map_name}" + self.kwargs.update({ + "rg": resource_group, + "name": change_record_name, + "change_type": "ManualTouch", + "rollout_type": "Normal", + "targets": f"resourceId={target_resource},operation=PATCH", + "stage_map_name": stage_map_name, + "subscription": self.FAKE_SUBSCRIPTION_ID, + }) + + result = self.cmd( + 'az changesafety changerecord create -g {rg} -n {name} ' + '--change-type {change_type} --rollout-type {rollout_type} ' + '--targets "{targets}" --stagemap-name {stage_map_name} --subscription {subscription}', + ).get_output_in_json() + + self.assertEqual(result["properties"]["stageMap"]["resourceId"], expected_stage_map) + + def test_change_record_crud_scenario(self): + """Test full CRUD scenario for ChangeRecord.""" + resource_group = "rgChangeSafetyScenario" + change_record_name = self.create_random_name('chg', 12) + target_resource = ( + f"/subscriptions/{self.FAKE_SUBSCRIPTION_ID}/resourceGroups/{resource_group}/" + "providers/Microsoft.Compute/virtualMachines/myVm" + ) + self.kwargs.update({ + "rg": resource_group, + "name": change_record_name, + "change_type": "ManualTouch", + "rollout_type": "Normal", + "updated_rollout": "Emergency", + "targets": f"resourceId={target_resource},operation=PATCH", + }) + + # Create + create_checks = [ + JMESPathCheck('name', change_record_name), + JMESPathCheck('properties.changeType', 'ManualTouch'), + JMESPathCheck('properties.rolloutType', 'Normal'), + JMESPathCheck('properties.changeDefinition.details.targets[0].resourceId', target_resource), + JMESPathCheck('properties.changeDefinition.details.targets[0].httpMethod', 'PATCH'), + ] + self.cmd( + 'az changesafety changerecord create -g {rg} -n {name} ' + '--change-type {change_type} --rollout-type {rollout_type} ' + '--targets "{targets}" --comments "Initial deployment"', + checks=create_checks, + ) + + # Update + update_checks = [ + JMESPathCheck('properties.rolloutType', 'Emergency'), + JMESPathCheck('properties.comments', 'Escalated rollout'), + ] + self.cmd( + 'az changesafety changerecord update -g {rg} -n {name} ' + '--rollout-type {updated_rollout} --comments "Escalated rollout"', + checks=update_checks, + ) + + # Show + self.cmd( + 'az changesafety changerecord show -g {rg} -n {name}', + checks=[ + JMESPathCheck('properties.comments', 'Escalated rollout'), + JMESPathCheck('properties.changeDefinition.details.targets[0].resourceId', target_resource), + ], + ) + + # Delete + self.cmd('az changesafety changerecord delete -g {rg} -n {name} -y') + self.assertNotIn("instance", type(self)._SCENARIO_STATE) + + +# ============================================================================= +# StageMap Tests +# ============================================================================= + + +class StageMapScenario(ScenarioTest): + """Test scenarios for StageMap CRUD operations.""" + + FAKE_SUBSCRIPTION_ID = "00000000-0000-0000-0000-000000000000" + _SCENARIO_STATE = {} + + class _DummyPoller: # pylint: disable=too-few-public-methods + """Mock poller for LRO operations.""" + + def result(self, timeout=None): # pylint: disable=unused-argument + return None + + def wait(self, timeout=None): # pylint: disable=unused-argument + return None + + def done(self): + return True + + def add_done_callback(self, func): + if func: + func(self) + + @staticmethod + def _build_mock_stagemap(name, subscription_id, stages=None): + """Build a mock StageMap instance.""" + return { + "id": f"/subscriptions/{subscription_id}/providers/Microsoft.ChangeSafety/stageMaps/{name}", + "name": name, + "type": "Microsoft.ChangeSafety/stageMaps", + "properties": { + "stages": stages or [ + {"name": "Stage1", "sequence": 1}, + {"name": "Stage2", "sequence": 2}, + ] + } + } + + @staticmethod + def _mock_create_execute(cmd): + """Mock execute for StageMap Create.""" + cls = StageMapScenario + cmd.pre_operations() + name_arg = getattr(cmd.ctx.args, "stage_map_name", None) + name = name_arg.to_serialized_data() if name_arg and has_value(name_arg) else "mock-stagemap" + subscription_id = cmd.ctx.subscription_id or cls.FAKE_SUBSCRIPTION_ID + stages_arg = getattr(cmd.ctx.args, "stages", None) + stages = stages_arg.to_serialized_data() if stages_arg and has_value(stages_arg) else None + instance = cls._build_mock_stagemap(name, subscription_id, stages) + cls._SCENARIO_STATE["stagemap"] = copy.deepcopy(instance) + cmd.ctx.set_var("instance", copy.deepcopy(instance), schema_builder=lambda: AAZAnyType()) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_show_execute(cmd): + """Mock execute for StageMap Show.""" + cls = StageMapScenario + cmd.pre_operations() + instance = copy.deepcopy(cls._SCENARIO_STATE.get("stagemap")) + cmd.ctx.set_var("instance", instance, schema_builder=lambda: AAZAnyType()) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_update_execute(cmd): + """Mock execute for StageMap Update.""" + cls = StageMapScenario + cmd.pre_operations() + current = copy.deepcopy(cls._SCENARIO_STATE.get("stagemap")) + if current is None: + name_arg = getattr(cmd.ctx.args, "stage_map_name", None) + name = name_arg.to_serialized_data() if name_arg and has_value(name_arg) else "mock-stagemap" + subscription_id = cmd.ctx.subscription_id or cls.FAKE_SUBSCRIPTION_ID + current = cls._build_mock_stagemap(name, subscription_id) + stages_arg = getattr(cmd.ctx.args, "stages", None) + if stages_arg and has_value(stages_arg): + current["properties"]["stages"] = stages_arg.to_serialized_data() + cls._SCENARIO_STATE["stagemap"] = copy.deepcopy(current) + cmd.ctx.set_var("instance", copy.deepcopy(current), schema_builder=lambda: AAZAnyType()) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_delete_execute(cmd): + """Mock execute for StageMap Delete.""" + cls = StageMapScenario + cmd.pre_operations() + cls._SCENARIO_STATE.pop("stagemap", None) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_list_execute(cmd): + """Mock execute for StageMap List.""" + cls = StageMapScenario + cmd.pre_operations() + stagemap = cls._SCENARIO_STATE.get("stagemap") + result = [stagemap] if stagemap else [] + cmd.ctx.set_var("instance", {"value": result}, schema_builder=lambda: AAZAnyType()) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_build_lro_poller(cmd, executor, extract_result): # pylint: disable=unused-argument + """Mock LRO poller builder.""" + executor() + return StageMapScenario._DummyPoller() + + def setUp(self): + super().setUp() + type(self)._SCENARIO_STATE.clear() + self._patchers = [ + mock.patch('azext_changesafety.aaz.latest.changesafety.stagemap._create.Create._execute_operations', new=type(self)._mock_create_execute), + mock.patch('azext_changesafety.aaz.latest.changesafety.stagemap._show.Show._execute_operations', new=type(self)._mock_show_execute), + mock.patch('azext_changesafety.aaz.latest.changesafety.stagemap._update.Update._execute_operations', new=type(self)._mock_update_execute), + mock.patch('azext_changesafety.aaz.latest.changesafety.stagemap._delete.Delete._execute_operations', new=type(self)._mock_delete_execute), + mock.patch('azext_changesafety.aaz.latest.changesafety.stagemap._list.List._execute_operations', new=type(self)._mock_list_execute), + mock.patch('azext_changesafety.aaz.latest.changesafety.stagemap._delete.Delete.build_lro_poller', new=type(self)._mock_build_lro_poller), + ] + for patcher in self._patchers: + patcher.start() + self.addCleanup(patcher.stop) + + def test_stagemap_create(self): + """Test StageMap create command.""" + stagemap_name = self.create_random_name('stgmap', 12) + self.kwargs.update({ + "name": stagemap_name, + "subscription": self.FAKE_SUBSCRIPTION_ID, + }) + + result = self.cmd( + 'az changesafety stagemap create --stage-map-name {name} ' + '--stages "[{{name:Stage1,sequence:1}},{{name:Stage2,sequence:2}}]" ' + '--subscription {subscription}', + ).get_output_in_json() + + self.assertEqual(result["name"], stagemap_name) + self.assertIn("stages", result["properties"]) + + def test_stagemap_show(self): + """Test StageMap show command.""" + stagemap_name = self.create_random_name('stgmap', 12) + self.kwargs.update({ + "name": stagemap_name, + "subscription": self.FAKE_SUBSCRIPTION_ID, + }) + + # Create first + self.cmd( + 'az changesafety stagemap create --stage-map-name {name} ' + '--stages "[{{name:Stage1,sequence:1}}]" ' + '--subscription {subscription}', + ) + + # Show + result = self.cmd( + 'az changesafety stagemap show --stage-map-name {name} ' + '--subscription {subscription}', + ).get_output_in_json() + + self.assertEqual(result["name"], stagemap_name) + + def test_stagemap_delete(self): + """Test StageMap delete command.""" + stagemap_name = self.create_random_name('stgmap', 12) + self.kwargs.update({ + "name": stagemap_name, + "subscription": self.FAKE_SUBSCRIPTION_ID, + }) + + # Create first + self.cmd( + 'az changesafety stagemap create --stage-map-name {name} ' + '--stages "[{{name:Stage1,sequence:1}}]" ' + '--subscription {subscription}', + ) + + # Delete + self.cmd( + 'az changesafety stagemap delete --stage-map-name {name} ' + '--subscription {subscription} --yes', + ) + + self.assertNotIn("stagemap", type(self)._SCENARIO_STATE) + + +# ============================================================================= +# StageProgression Tests +# ============================================================================= + + +class StageProgressionScenario(ScenarioTest): + """Test scenarios for StageProgression CRUD operations.""" + + FAKE_SUBSCRIPTION_ID = "00000000-0000-0000-0000-000000000000" + _SCENARIO_STATE = {} + + class _DummyPoller: # pylint: disable=too-few-public-methods + """Mock poller for LRO operations.""" + + def result(self, timeout=None): # pylint: disable=unused-argument + return None + + def wait(self, timeout=None): # pylint: disable=unused-argument + return None + + def done(self): + return True + + def add_done_callback(self, func): + if func: + func(self) + + @staticmethod + def _build_mock_stageprogression(name, change_record_name, subscription_id, stage_reference, status="InProgress", comments=None): + """Build a mock StageProgression instance.""" + return { + "id": f"/subscriptions/{subscription_id}/providers/Microsoft.ChangeSafety/changeRecords/{change_record_name}/stageProgressions/{name}", + "name": name, + "type": "Microsoft.ChangeSafety/changeRecords/stageProgressions", + "properties": { + "stageReference": stage_reference, + "status": status, + "comments": comments, + } + } + + @staticmethod + def _mock_create_execute(cmd): + """Mock execute for StageProgression Create.""" + cls = StageProgressionScenario + cmd.pre_operations() + name_arg = getattr(cmd.ctx.args, "stage_progression_name", None) + name = name_arg.to_serialized_data() if name_arg and has_value(name_arg) else "mock-progression" + cr_arg = getattr(cmd.ctx.args, "change_record_name", None) + change_record_name = cr_arg.to_serialized_data() if cr_arg and has_value(cr_arg) else "mock-changerecord" + subscription_id = cmd.ctx.subscription_id or cls.FAKE_SUBSCRIPTION_ID + stage_ref_arg = getattr(cmd.ctx.args, "stage_reference", None) + stage_reference = stage_ref_arg.to_serialized_data() if stage_ref_arg and has_value(stage_ref_arg) else "Stage1" + status_arg = getattr(cmd.ctx.args, "status", None) + status = status_arg.to_serialized_data() if status_arg and has_value(status_arg) else "InProgress" + comments_arg = getattr(cmd.ctx.args, "comments", None) + comments = comments_arg.to_serialized_data() if comments_arg and has_value(comments_arg) else None + instance = cls._build_mock_stageprogression(name, change_record_name, subscription_id, stage_reference, status, comments) + cls._SCENARIO_STATE["stageprogression"] = copy.deepcopy(instance) + cmd.ctx.set_var("instance", copy.deepcopy(instance), schema_builder=lambda: AAZAnyType()) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_show_execute(cmd): + """Mock execute for StageProgression Show.""" + cls = StageProgressionScenario + cmd.pre_operations() + instance = copy.deepcopy(cls._SCENARIO_STATE.get("stageprogression")) + cmd.ctx.set_var("instance", instance, schema_builder=lambda: AAZAnyType()) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_update_execute(cmd): + """Mock execute for StageProgression Update.""" + cls = StageProgressionScenario + cmd.pre_operations() + current = copy.deepcopy(cls._SCENARIO_STATE.get("stageprogression")) + if current is None: + name_arg = getattr(cmd.ctx.args, "stage_progression_name", None) + name = name_arg.to_serialized_data() if name_arg and has_value(name_arg) else "mock-progression" + cr_arg = getattr(cmd.ctx.args, "change_record_name", None) + change_record_name = cr_arg.to_serialized_data() if cr_arg and has_value(cr_arg) else "mock-changerecord" + subscription_id = cmd.ctx.subscription_id or cls.FAKE_SUBSCRIPTION_ID + current = cls._build_mock_stageprogression(name, change_record_name, subscription_id, "Stage1") + status_arg = getattr(cmd.ctx.args, "status", None) + if status_arg and has_value(status_arg): + current["properties"]["status"] = status_arg.to_serialized_data() + comments_arg = getattr(cmd.ctx.args, "comments", None) + if comments_arg and has_value(comments_arg): + current["properties"]["comments"] = comments_arg.to_serialized_data() + cls._SCENARIO_STATE["stageprogression"] = copy.deepcopy(current) + cmd.ctx.set_var("instance", copy.deepcopy(current), schema_builder=lambda: AAZAnyType()) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_delete_execute(cmd): + """Mock execute for StageProgression Delete.""" + cls = StageProgressionScenario + cmd.pre_operations() + cls._SCENARIO_STATE.pop("stageprogression", None) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_list_execute(cmd): + """Mock execute for StageProgression List.""" + cls = StageProgressionScenario + cmd.pre_operations() + progression = cls._SCENARIO_STATE.get("stageprogression") + result = [progression] if progression else [] + cmd.ctx.set_var("instance", {"value": result}, schema_builder=lambda: AAZAnyType()) + cmd.post_operations() + return iter(()) + + @staticmethod + def _mock_build_lro_poller(cmd, executor, extract_result): # pylint: disable=unused-argument + """Mock LRO poller builder.""" + executor() + return StageProgressionScenario._DummyPoller() + + def setUp(self): + super().setUp() + type(self)._SCENARIO_STATE.clear() + self._patchers = [ + mock.patch('azext_changesafety.aaz.latest.changesafety.stageprogression._create.Create._execute_operations', new=type(self)._mock_create_execute), + mock.patch('azext_changesafety.aaz.latest.changesafety.stageprogression._show.Show._execute_operations', new=type(self)._mock_show_execute), + mock.patch('azext_changesafety.aaz.latest.changesafety.stageprogression._update.Update._execute_operations', new=type(self)._mock_update_execute), + mock.patch('azext_changesafety.aaz.latest.changesafety.stageprogression._delete.Delete._execute_operations', new=type(self)._mock_delete_execute), + mock.patch('azext_changesafety.aaz.latest.changesafety.stageprogression._list.List._execute_operations', new=type(self)._mock_list_execute), + mock.patch('azext_changesafety.aaz.latest.changesafety.stageprogression._delete.Delete.build_lro_poller', new=type(self)._mock_build_lro_poller), + ] + for patcher in self._patchers: + patcher.start() + self.addCleanup(patcher.stop) + + def test_stageprogression_create(self): + """Test StageProgression create command.""" + progression_name = self.create_random_name('prog', 12) + change_record_name = "test-changerecord" + self.kwargs.update({ + "name": progression_name, + "change_record": change_record_name, + "subscription": self.FAKE_SUBSCRIPTION_ID, + }) + + result = self.cmd( + 'az changesafety stageprogression create -n {name} ' + '--change-record-name {change_record} ' + '--stage-reference Stage1 --status InProgress ' + '--subscription {subscription}', + ).get_output_in_json() + + self.assertEqual(result["name"], progression_name) + self.assertEqual(result["properties"]["stageReference"], "Stage1") + self.assertEqual(result["properties"]["status"], "InProgress") + + def test_stageprogression_show(self): + """Test StageProgression show command.""" + progression_name = self.create_random_name('prog', 12) + change_record_name = "test-changerecord" + self.kwargs.update({ + "name": progression_name, + "change_record": change_record_name, + "subscription": self.FAKE_SUBSCRIPTION_ID, + }) + + # Create first + self.cmd( + 'az changesafety stageprogression create -n {name} ' + '--change-record-name {change_record} ' + '--stage-reference Stage1 --status InProgress ' + '--subscription {subscription}', + ) + + # Show + result = self.cmd( + 'az changesafety stageprogression show -n {name} ' + '--change-record-name {change_record} ' + '--subscription {subscription}', + ).get_output_in_json() + + self.assertEqual(result["name"], progression_name) + + def test_stageprogression_update(self): + """Test StageProgression update command.""" + progression_name = self.create_random_name('prog', 12) + change_record_name = "test-changerecord" + self.kwargs.update({ + "name": progression_name, + "change_record": change_record_name, + "subscription": self.FAKE_SUBSCRIPTION_ID, + }) + + # Create first + self.cmd( + 'az changesafety stageprogression create -n {name} ' + '--change-record-name {change_record} ' + '--stage-reference Stage1 --status InProgress ' + '--subscription {subscription}', + ) + + # Update + result = self.cmd( + 'az changesafety stageprogression update -n {name} ' + '--change-record-name {change_record} ' + '--status Completed --comments "Stage completed successfully" ' + '--subscription {subscription}', + ).get_output_in_json() + + self.assertEqual(result["properties"]["status"], "Completed") + self.assertEqual(result["properties"]["comments"], "Stage completed successfully") + + def test_stageprogression_delete(self): + """Test StageProgression delete command.""" + progression_name = self.create_random_name('prog', 12) + change_record_name = "test-changerecord" + self.kwargs.update({ + "name": progression_name, + "change_record": change_record_name, + "subscription": self.FAKE_SUBSCRIPTION_ID, + }) + + # Create first + self.cmd( + 'az changesafety stageprogression create -n {name} ' + '--change-record-name {change_record} ' + '--stage-reference Stage1 --status InProgress ' + '--subscription {subscription}', + ) + + # Delete + self.cmd( + 'az changesafety stageprogression delete -n {name} ' + '--change-record-name {change_record} ' + '--subscription {subscription} --yes', + ) + + self.assertNotIn("stageprogression", type(self)._SCENARIO_STATE) + + def test_stageprogression_crud_scenario(self): + """Test full CRUD scenario for StageProgression.""" + progression_name = self.create_random_name('prog', 12) + change_record_name = "deployment-cr" + self.kwargs.update({ + "name": progression_name, + "change_record": change_record_name, + "subscription": self.FAKE_SUBSCRIPTION_ID, + }) + + # Create + self.cmd( + 'az changesafety stageprogression create -n {name} ' + '--change-record-name {change_record} ' + '--stage-reference Stage1 --status InProgress ' + '--subscription {subscription}', + checks=[ + JMESPathCheck('name', progression_name), + JMESPathCheck('properties.stageReference', 'Stage1'), + JMESPathCheck('properties.status', 'InProgress'), + ], + ) + + # Update + self.cmd( + 'az changesafety stageprogression update -n {name} ' + '--change-record-name {change_record} ' + '--status Completed --comments "Finished stage 1" ' + '--subscription {subscription}', + checks=[ + JMESPathCheck('properties.status', 'Completed'), + JMESPathCheck('properties.comments', 'Finished stage 1'), + ], + ) + + # Show + self.cmd( + 'az changesafety stageprogression show -n {name} ' + '--change-record-name {change_record} ' + '--subscription {subscription}', + checks=[ + JMESPathCheck('properties.status', 'Completed'), + ], + ) + + # Delete + self.cmd( + 'az changesafety stageprogression delete -n {name} ' + '--change-record-name {change_record} ' + '--subscription {subscription} --yes', + ) + self.assertNotIn("stageprogression", type(self)._SCENARIO_STATE) + + +# ============================================================================= +# Live Tests for Recordings (Run with: azdev test azure-changesafety --live) +# ============================================================================= + + +def _ensure_msrestazure_stub(): + """Ensure msrestazure stub is available for tests.""" + if 'msrestazure' in sys.modules: + return + msrestazure = types.ModuleType('msrestazure') + azure_operation = types.ModuleType('msrestazure.azure_operation') + + class AzureOperationPoller: # pylint: disable=too-few-public-methods + def _delay(self, *args, **kwargs): # pylint: disable=unused-argument + return + + azure_operation.AzureOperationPoller = AzureOperationPoller + arm_polling = types.ModuleType('msrestazure.polling.arm_polling') + + class ARMPolling: # pylint: disable=too-few-public-methods + def _delay(self, *args, **kwargs): # pylint: disable=unused-argument + return + + arm_polling.ARMPolling = ARMPolling + polling = types.ModuleType('msrestazure.polling') + polling.arm_polling = arm_polling + msrestazure.azure_operation = azure_operation + msrestazure.polling = polling + sys.modules['msrestazure'] = msrestazure + sys.modules['msrestazure.azure_operation'] = azure_operation + sys.modules['msrestazure.polling'] = polling + sys.modules['msrestazure.polling.arm_polling'] = arm_polling + + +# Ensure stub is loaded before test collection +_ensure_msrestazure_stub() + + +class ChangeSafetyLiveScenario(ScenarioTest): + """Live test scenarios for generating recordings. + + Run with: azdev test azure-changesafety --live + Or: pytest test_changesafety.py::ChangeSafetyLiveScenario --live + + These tests make actual API calls and generate YAML recordings. + """ + + def test_changesafety_full_scenario(self): + """Full CRUD scenario for ChangeRecord, StageMap, and StageProgression.""" + # Use random names to avoid conflicts + stagemap_name = self.create_random_name('stgmap', 15) + change_record_name = self.create_random_name('cr', 15) + progression_name = self.create_random_name('prog', 15) + + self.kwargs.update({ + 'stagemap_name': stagemap_name, + 'cr_name': change_record_name, + 'prog_name': progression_name, + }) + + # ================================================================= + # StageMap CRUD + # ================================================================= + + # Create StageMap + self.cmd( + 'az changesafety stagemap create ' + '--stage-map-name {stagemap_name} ' + '--stages "[{{name:Canary,sequence:1}},{{name:Production,sequence:2}}]"', + checks=[ + JMESPathCheck('name', stagemap_name), + JMESPathCheck('properties.stages[0].name', 'Canary'), + JMESPathCheck('properties.stages[1].name', 'Production'), + ] + ) + + # Show StageMap + self.cmd( + 'az changesafety stagemap show --stage-map-name {stagemap_name}', + checks=[ + JMESPathCheck('name', stagemap_name), + ] + ) + + # Note: List commands skipped - response too large for recording + + # ================================================================= + # ChangeRecord CRUD + # ================================================================= + + # Create ChangeRecord with StageMap reference + self.cmd( + 'az changesafety changerecord create ' + '-n {cr_name} ' + '--change-type AppDeployment ' + '--rollout-type Normal ' + '--stagemap-name {stagemap_name} ' + '--targets "subscriptionId=$(az account show --query id -o tsv)"', + checks=[ + JMESPathCheck('name', change_record_name), + JMESPathCheck('properties.changeType', 'AppDeployment'), + ] + ) + + # Show ChangeRecord + self.cmd( + 'az changesafety changerecord show -n {cr_name}', + checks=[ + JMESPathCheck('name', change_record_name), + ] + ) + + # Update ChangeRecord + self.cmd( + 'az changesafety changerecord update -n {cr_name} ' + '--rollout-type Emergency --comments "Escalated"', + checks=[ + JMESPathCheck('properties.rolloutType', 'Emergency'), + JMESPathCheck('properties.comments', 'Escalated'), + ] + ) + + # ================================================================= + # StageProgression CRUD + # ================================================================= + + # Create StageProgression + self.cmd( + 'az changesafety stageprogression create ' + '--change-record-name {cr_name} ' + '-n {prog_name} ' + '--stage-reference Canary ' + '--status InProgress', + checks=[ + JMESPathCheck('name', progression_name), + JMESPathCheck('properties.stageReference', 'Canary'), + JMESPathCheck('properties.status', 'InProgress'), + ] + ) + + # Show StageProgression + self.cmd( + 'az changesafety stageprogression show ' + '--change-record-name {cr_name} -n {prog_name}', + checks=[ + JMESPathCheck('name', progression_name), + ] + ) + + # Update StageProgression + self.cmd( + 'az changesafety stageprogression update ' + '--change-record-name {cr_name} -n {prog_name} ' + '--status Completed --comments "Canary passed"', + checks=[ + JMESPathCheck('properties.status', 'Completed'), + ] + ) + + # Delete StageProgression + self.cmd('az changesafety stageprogression delete ' + '--change-record-name {cr_name} -n {prog_name} --yes') + + # ================================================================= + # Cleanup + # ================================================================= + + # Note: ChangeRecord may need to be in terminal state to delete + # Delete StageMap (if not referenced) + self.cmd('az changesafety stagemap delete --stage-map-name {stagemap_name} --yes') + From 6dc6162c9e2027b28d5bcf8462016de20d39df8e Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Fri, 30 Jan 2026 20:02:13 -0800 Subject: [PATCH 24/25] Fix issues --- src/azure-changesafety/README.md | 5 +- .../azext_changesafety/custom.py | 78 ++++++++++++------- src/azure-changesafety/setup.py | 1 - 3 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/azure-changesafety/README.md b/src/azure-changesafety/README.md index 3da614ce5cd..f78753041ae 100644 --- a/src/azure-changesafety/README.md +++ b/src/azure-changesafety/README.md @@ -73,13 +73,12 @@ az changesafety changerecord create \ --links name=Runbook uri=https://contoso.com/runbook ``` -Update the rollout type and add a comment: +Update the ChangeRecord and add a comment: ```bash az changesafety changerecord update \ -g MyResourceGroup \ -n changerecord-webapp-rollout \ - --rollout-type Emergency \ - --comments "Escalated due to customer impact" + --comments "Deployment validated in canary region" ``` ### StageProgression Examples diff --git a/src/azure-changesafety/azext_changesafety/custom.py b/src/azure-changesafety/azext_changesafety/custom.py index 16dddcd83db..c13f4d0791d 100644 --- a/src/azure-changesafety/azext_changesafety/custom.py +++ b/src/azure-changesafety/azext_changesafety/custom.py @@ -5,6 +5,8 @@ # Code generated by aaz-dev-tools # -------------------------------------------------------------------------------------------- +# pylint: disable=line-too-long + """Custom command overrides for azure-changesafety CLI extension. This module provides customizations for ChangeRecord CRUD operations: @@ -424,41 +426,49 @@ def _build_change_definition(self): } } + @staticmethod + def _parse_key_value_segment(segment): + """Parse a single key=value segment and return (mapped_key, value).""" + if '=' not in segment: + raise InvalidArgumentValueError('Each --targets entry must be in key=value format.') + key, value = segment.split('=', 1) + key = key.strip() + value = value.strip() + if not key or not value: + raise InvalidArgumentValueError('Each --targets entry must include a non-empty key and value.') + normalized_key = key.lower() + if normalized_key in TARGET_KEY_MAPPING: + mapped_key = TARGET_KEY_MAPPING[normalized_key] + if mapped_key == 'httpMethod' and value: + value = value.upper() + return mapped_key, value + return key, value + + @staticmethod + def _tokenize_target(token): + """Split a target token into segments, handling both ; and , delimiters.""" + if token is None: + return [] + segments = [] + for part in str(token).split(';'): + segments.extend(segment.strip() for segment in part.split(',') if segment.strip()) + return segments + @staticmethod def _parse_targets(raw_targets): - if raw_targets is None: - raise InvalidArgumentValueError('--targets is required and must include key=value pairs.') if not raw_targets: raise InvalidArgumentValueError('--targets is required and must include key=value pairs.') parsed_targets = [] for token in raw_targets: - if token is None: - continue - segments = [] - for part in str(token).split(';'): - segments.extend(segment.strip() for segment in part.split(',') if segment.strip()) + segments = ChangeRecordCreate._tokenize_target(token) if not segments: continue target_entry = {} for segment in segments: - if '=' not in segment: - raise InvalidArgumentValueError('Each --targets entry must be in key=value format.') - key, value = segment.split('=', 1) - key = key.strip() - value = value.strip() - if not key or not value: - raise InvalidArgumentValueError('Each --targets entry must include a non-empty key and value.') - normalized_key = key.lower() - if normalized_key in TARGET_KEY_MAPPING: - mapped_key = TARGET_KEY_MAPPING[normalized_key] - if mapped_key == 'httpMethod' and value: - value = value.upper() - target_entry[mapped_key] = value - else: - target_entry[key] = value - if not target_entry: - continue - parsed_targets.append(target_entry) + mapped_key, value = ChangeRecordCreate._parse_key_value_segment(segment) + target_entry[mapped_key] = value + if target_entry: + parsed_targets.append(target_entry) if not parsed_targets: raise InvalidArgumentValueError('--targets must include at least one key=value pair.') return parsed_targets @@ -470,6 +480,8 @@ def _output(self, *args, **kwargs): class ChangeRecordsCreateOrUpdateAtSubscriptionLevel( _ChangeRecordCreate.ChangeRecordsCreateOrUpdateAtSubscriptionLevel): + """Override PUT at subscription level to inject custom changeDefinition.""" + @property def content(self): content = super().content @@ -477,6 +489,8 @@ def content(self): class ChangeRecordsCreateOrUpdate( _ChangeRecordCreate.ChangeRecordsCreateOrUpdate): + """Override PUT at resource group level to inject custom changeDefinition.""" + @property def content(self): content = super().content @@ -517,6 +531,8 @@ def pre_operations(self): class ChangeRecordsGetAtSubscriptionLevel( _ChangeRecordUpdate.ChangeRecordsGetAtSubscriptionLevel): + """Override GET at subscription level to capture original changeDefinition.""" + def on_200(self, session): # Capture raw changeDefinition from HTTP response before schema loses details data = self.deserialize_http_content(session) @@ -528,6 +544,8 @@ def on_200(self, session): return super().on_200(session) class ChangeRecordsGet(_ChangeRecordUpdate.ChangeRecordsGet): + """Override GET at resource group level to capture original changeDefinition.""" + def on_200(self, session): # Capture raw changeDefinition from HTTP response before schema loses details data = self.deserialize_http_content(session) @@ -540,6 +558,8 @@ def on_200(self, session): class ChangeRecordsCreateOrUpdateAtSubscriptionLevel( _ChangeRecordUpdate.ChangeRecordsCreateOrUpdateAtSubscriptionLevel): + """Override PUT at subscription level to preserve original changeDefinition.""" + @property def content(self): content = super().content @@ -548,6 +568,8 @@ def content(self): class ChangeRecordsCreateOrUpdate( _ChangeRecordUpdate.ChangeRecordsCreateOrUpdate): + """Override PUT at resource group level to preserve original changeDefinition.""" + @property def content(self): content = super().content @@ -715,7 +737,7 @@ class ChangeRecordDelete(_ChangeRecordDelete): # StageMap Help Examples # ----------------------------------------------------------------------------- # pylint: disable=wrong-import-position -from azext_changesafety.aaz.latest.changesafety.stagemap import ( +from azext_changesafety.aaz.latest.changesafety.stagemap import ( # noqa: E402 Create as _StageMapCreate, Show as _StageMapShow, Update as _StageMapUpdate, @@ -818,7 +840,7 @@ class ChangeRecordDelete(_ChangeRecordDelete): # StageProgression Help Examples # ----------------------------------------------------------------------------- # pylint: disable=wrong-import-position -from azext_changesafety.aaz.latest.changesafety.stageprogression import ( +from azext_changesafety.aaz.latest.changesafety.stageprogression import ( # noqa: E402 Create as _StageProgressionCreate, Show as _StageProgressionShow, Update as _StageProgressionUpdate, @@ -828,7 +850,7 @@ class ChangeRecordDelete(_ChangeRecordDelete): _StageProgressionCreate.AZ_HELP = { **_StageProgressionCreate.AZ_HELP, - "short-summary": "Create a StageProgression to track the progress of a stage in a ChangeRecord.", + "short-summary": "Create a StageProgression to track stage progress.", "long-summary": ( "A StageProgression records the execution status of a specific stage defined " "in a StageMap. Use this to track which stages have started, completed, or failed " diff --git a/src/azure-changesafety/setup.py b/src/azure-changesafety/setup.py index d687b198f02..0e68f9c4516 100644 --- a/src/azure-changesafety/setup.py +++ b/src/azure-changesafety/setup.py @@ -47,4 +47,3 @@ package_data={'azext_changesafety': ['azext_metadata.json']}, install_requires=DEPENDENCIES ) - From 2d96cae2017dee567392663e20fc9948cc9c057c Mon Sep 17 00:00:00 2001 From: Henry Dai Date: Fri, 30 Jan 2026 20:15:17 -0800 Subject: [PATCH 25/25] fix stageMap parameters --- src/azure-changesafety/README.md | 10 ++++++++++ src/azure-changesafety/azext_changesafety/_help.py | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/src/azure-changesafety/README.md b/src/azure-changesafety/README.md index f78753041ae..0e3a83a8371 100644 --- a/src/azure-changesafety/README.md +++ b/src/azure-changesafety/README.md @@ -49,6 +49,16 @@ az changesafety stagemap create \ --stages "[{name:Canary,sequence:1},{name:Production,sequence:2}]" ``` +Create a StageMap with configurable parameters: +```bash +# Parameters use AAZ shorthand: paramName.{string|number|array|object}.property=value +# Use --parameters paramName.string="??" to explore available properties +az changesafety stagemap create \ + --stage-map-name parameterized-rollout \ + --stages "[{name:Canary,sequence:1},{name:Production,sequence:2}]" \ + --parameters region.string.default-value=westus batchSize.number.default-value=10 +``` + ### ChangeRecord Examples Create a ChangeRecord for a manual touch operation (e.g., delete a Traffic Manager profile): ```bash diff --git a/src/azure-changesafety/azext_changesafety/_help.py b/src/azure-changesafety/azext_changesafety/_help.py index 416bfb8d5b0..11d109de84a 100644 --- a/src/azure-changesafety/azext_changesafety/_help.py +++ b/src/azure-changesafety/azext_changesafety/_help.py @@ -138,6 +138,12 @@ short-summary: > Array of stage definitions. Each stage requires a name and sequence number. Use shorthand syntax or JSON format. + - name: --parameters + short-summary: > + Optional parameters schema for the StageMap. Define parameters that can be + passed when referencing this StageMap from a ChangeRecord. Use AAZ shorthand + syntax: paramName.{string|number|array|object}.default-value=value. + Run with --parameters paramName.string="??" to see available properties. examples: - name: Create a simple two-stage StageMap text: |- @@ -145,6 +151,9 @@ - name: Create a StageMap with three stages text: |- az changesafety stagemap create --subscription 00000000-0000-0000-0000-000000000000 --stage-map-name regional-rollout --stages "[{name:WestUS,sequence:1},{name:EastUS,sequence:2},{name:Global,sequence:3}]" + - name: Create a StageMap with parameters + text: |- + az changesafety stagemap create --subscription 00000000-0000-0000-0000-000000000000 --stage-map-name parameterized-rollout --stages "[{name:Canary,sequence:1},{name:Production,sequence:2}]" --parameters region.string.default-value=westus batchSize.number.default-value=10 """ helps['changesafety stagemap update'] = """