diff --git a/src/azure-changesafety/HISTORY.rst b/src/azure-changesafety/HISTORY.rst new file mode 100644 index 00000000000..abbff5a61a7 --- /dev/null +++ b/src/azure-changesafety/HISTORY.rst @@ -0,0 +1,8 @@ +.. :changelog: + +Release History +=============== + +1.0.0b1 +++++++ +* Initial release. \ No newline at end of file diff --git a/src/azure-changesafety/README.md b/src/azure-changesafety/README.md new file mode 100644 index 00000000000..89e7d4aae7c --- /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 webAppRollout01 \ + --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 webAppRollout01 \ + --rollout-type Emergency \ + --comments "Escalated due to customer impact" +``` + +Delete a ChangeState: +```bash +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` diff --git a/src/azure-changesafety/azext_changesafety/__init__.py b/src/azure-changesafety/azext_changesafety/__init__.py new file mode 100644 index 00000000000..05751262e4f --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/__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_changesafety._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_changesafety.custom#{}') + super().__init__(cli_ctx=cli_ctx, + custom_command_type=custom_command_type) + + def load_command_table(self, args): + from azext_changesafety.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_changesafety._params import load_arguments + load_arguments(self, command) + + +COMMAND_LOADER_CLS = ChangeStateCommandsLoader diff --git a/src/azure-changesafety/azext_changesafety/_help.py b/src/azure-changesafety/azext_changesafety/_help.py new file mode 100644 index 00000000000..c6174626b2d --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/_help.py @@ -0,0 +1,103 @@ +# -------------------------------------------------------------------------------------------- +# 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 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`. + 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 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 + - 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" +""" + +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 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 + 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 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" +""" + +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 changestate001 --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 changestate001 +""" diff --git a/src/azure-changesafety/azext_changesafety/_params.py b/src/azure-changesafety/azext_changesafety/_params.py new file mode 100644 index 00000000000..cfcec717c9c --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/_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/azure-changesafety/azext_changesafety/aaz/__init__.py b/src/azure-changesafety/azext_changesafety/aaz/__init__.py new file mode 100644 index 00000000000..5757aea3175 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/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/azure-changesafety/azext_changesafety/aaz/latest/__init__.py b/src/azure-changesafety/azext_changesafety/aaz/latest/__init__.py new file mode 100644 index 00000000000..f6acc11aa4e --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/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/azure-changesafety/azext_changesafety/aaz/latest/change_safety/__cmd_group.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/__cmd_group.py new file mode 100644 index 00000000000..1734459314b --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/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( + "changesafety", +) +class __CMDGroup(AAZCommandGroup): + """Manage Change Safety + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/__init__.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/__init__.py new file mode 100644 index 00000000000..5a9d61963d6 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/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/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/__cmd_group.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/__cmd_group.py new file mode 100644 index 00000000000..401be3b73af --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/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( + "changesafety changestate", +) +class __CMDGroup(AAZCommandGroup): + """Manage Change State + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/__init__.py b/src/azure-changesafety/azext_changesafety/aaz/latest/change_safety/change_state/__init__.py new file mode 100644 index 00000000000..a3db3e36481 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/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/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 new file mode 100644 index 00000000000..0fe7e63f36f --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/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( + "changesafety changestate 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/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 new file mode 100644 index 00000000000..96893b74400 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/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( + "changesafety changestate 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/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 new file mode 100644 index 00000000000..aeb51bc3422 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/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( + "changesafety changestate 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/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 new file mode 100644 index 00000000000..3de21715670 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/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( + "changesafety changestate 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/azure-changesafety/azext_changesafety/azext_metadata.json b/src/azure-changesafety/azext_changesafety/azext_metadata.json new file mode 100644 index 00000000000..71889bb136b --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/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/azure-changesafety/azext_changesafety/commands.py b/src/azure-changesafety/azext_changesafety/commands.py new file mode 100644 index 00000000000..e0bfc0702af --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/commands.py @@ -0,0 +1,22 @@ +# -------------------------------------------------------------------------------------------- +# 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_command_table(self, _): # pylint: disable=unused-argument + 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['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/azure-changesafety/azext_changesafety/custom.py b/src/azure-changesafety/azext_changesafety/custom.py new file mode 100644 index 00000000000..320f61fe9a6 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/custom.py @@ -0,0 +1,573 @@ +# -------------------------------------------------------------------------------------------- +# 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 +# pylint: disable=protected-access + +from knack.log import get_logger +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_changesafety.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) + 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 + + +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] + + 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): + """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) + + +def _custom_show_schema_builder(): + # Import the generated Show class + 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() + + # 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): + super().__init__(*args, **kwargs) + self._raw_targets = [] + self._parsed_targets = None + + @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=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. + 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() + + 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() + logger.debug("Final changeDefinition for create: %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_state_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 + } + } + + @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()) + 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.') + 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) + if not parsed_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) + 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 = [] + self._parsed_targets = None + + @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=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. + 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() + + # 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=_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_state_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 _output(self, *args, **kwargs): + result = super()._output(*args, **kwargs) + _inject_targets_into_result(result, self._parsed_targets) + return result + + 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): + @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) + + +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 + + +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", + }, + ], +} diff --git a/src/azure-changesafety/azext_changesafety/tests/__init__.py b/src/azure-changesafety/azext_changesafety/tests/__init__.py new file mode 100644 index 00000000000..5757aea3175 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/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/azure-changesafety/azext_changesafety/tests/latest/__init__.py b/src/azure-changesafety/azext_changesafety/tests/latest/__init__.py new file mode 100644 index 00000000000..5757aea3175 --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/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/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 new file mode 100644 index 00000000000..0fbb2d5d35c --- /dev/null +++ b/src/azure-changesafety/azext_changesafety/tests/latest/test_change_state.py @@ -0,0 +1,326 @@ +# -------------------------------------------------------------------------------------------- +# 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 sys +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): + 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 = [ # 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_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/setup.cfg b/src/azure-changesafety/setup.cfg new file mode 100644 index 00000000000..2fdd96e5d39 --- /dev/null +++ b/src/azure-changesafety/setup.cfg @@ -0,0 +1 @@ +#setup.cfg \ No newline at end of file diff --git a/src/azure-changesafety/setup.py b/src/azure-changesafety/setup.py new file mode 100644 index 00000000000..79dda33fcd9 --- /dev/null +++ b/src/azure-changesafety/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_changesafety': ['azext_metadata.json']}, + install_requires=DEPENDENCIES +) 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": "" } ]