diff --git a/.gitignore b/.gitignore index 329fdbc1087..4ab1dcda4f3 100644 --- a/.gitignore +++ b/.gitignore @@ -96,6 +96,7 @@ artifacts/ # local Azure configuration .azure-config/ +.x/active-backup/ # Python version .python-version diff --git a/doc/active_backup_preview.md b/doc/active_backup_preview.md new file mode 100644 index 00000000000..2e54a6d1d63 --- /dev/null +++ b/doc/active_backup_preview.md @@ -0,0 +1,205 @@ +# App Service Active Backup preview + +The preview manages plan and site **silos** through public ARM APIs. It does not +configure storage-backed backups, deployment slots, or slot traffic routing. + +## Specification and scope + +Commands are generated with AAZDev from the `2026-08-01` App Service contract in +[azure-rest-api-specs PR 44330](https://github.com/Azure/azure-rest-api-specs/pull/44330), +using source commit `33ee07b2b3d377b58c4d1a1c5be7e2339f43b879`. +That specification is still under review. Existing ordinary app/plan commands +retain their API versions and support levels. + +The first implementation exposes 24 preview commands covering the contract's +20 HTTP operations plus CLI update/wait behavior: + +| Group | Commands | +|---|---| +| `appservice plan silo` | create, update, show, list, delete, wait | +| `appservice plan silo vnet-integration` | list | +| `webapp silo` | create, update, show, list, delete, wait, refresh | +| `webapp silo vnet-integration` | show, add, update, remove | +| `webapp managed-traffic` | list, show, update | +| `webapp last-known-good` | list, show, mark | + +Configuration, deployments, processes, containers, logs, WebJobs, diagnostics, +metrics, antivirus, extension invocation and other silo pass-through APIs are +deferred. Standalone operation-status commands and managed-traffic metrics are +also deferred because this specification does not define them. + +AAZ supplies authentication, cloud selection, error handling, pagination, +serialization and long-running-operation handling. The CLI does not downgrade +the API version or call internal GeoMaster endpoints. Thin subclasses of the +generated silo commands supply a missing top-level location field in the draft +contract; they do not replace its HTTP client or poller. A small identity validator +rejects plan, slot and extension-resource IDs that core ID expansion could +otherwise reinterpret as a different silo. + +## Availability + +The intended creation preview is Windows/Linux dedicated multitenant plans in +PremiumV3, PremiumMV3, PremiumV4 and PremiumMV4 tier families. P0v3 is excluded. +Subscription, region, flavor, SKU, capacity, placement and origin restrictions +remain service-enforced. Installing the CLI does not enable the feature. + +Create a plan silo before a site silo in the same target region. Creating the +first site silo can restart the primary app. Silos allocate billable resources; +there is no automatic plan-plus-app setup, cascading teardown or safe failover +workflow. + +## Create and update + +Assume an existing eligible primary plan and app in `eastus`: + +```azurecli +az appservice plan silo create --resource-group MyGroup --name MyPlan --silo WestPlan --location westus2 --primary-location eastus --number-of-workers 2 +az webapp silo create --resource-group MyGroup --name MyApp --silo WestApp --location westus2 --primary-location eastus --traffic-mode Active +az webapp silo show --resource-group MyGroup --name MyApp --silo WestApp +az webapp silo list --resource-group MyGroup --name MyApp +az appservice plan silo update --resource-group MyGroup --name MyPlan --silo WestPlan --zone-redundant false +az webapp silo update --resource-group MyGroup --name MyApp --silo WestApp --follow-last-known-good false +``` + +`--name` identifies the primary; `--silo` identifies the child. Both create +commands require explicit target `--location` and `--primary-location`. +A configured default location does not select a replica region. The draft +specification maps these values to `properties.siloLocation` and +`properties.primaryLocation`, but the deployed API also requires top-level +`location`. The compatibility layer supplies that root field, retaining the +generated property mappings until the service specification is corrected. + +For example, a site create request contains: + +```json +{ + "location": "westus2", + "properties": { + "siloLocation": "westus2", + "primaryLocation": "eastus", + "trafficMode": "Active" + } +} +``` + +Resource commands return the modeled ARM resource; collections return item +arrays and follow `nextLink`. Generic GET+PUT updates preserve existing modeled +writable fields, exclude read-only fields, and support `--set`, `--add` and +`--remove`. Generic property paths include the envelope, for example +`--set properties.instanceNumberOverride=2`. Explicit `false` is not omission. +Updates preserve the region returned by the service, even if it is only present +at the root of the response. Missing or conflicting region metadata is an error; +the CLI does not guess from the primary region or a configured default. +An existing silo's location cannot be changed or removed. Service validation +remains authoritative for worker counts and other resource restrictions. + +Silo show/update/delete/wait and site networking commands support a complete silo +resource ID through `--ids`. Plan VNet listing uses the named parent/silo arguments. +App-level traffic/LKG/refresh use the primary app's +identity, not an internal replica name. As with standard CLI ID expansion, use +either `--ids` or named identity arguments. + +## Async behavior and waits + +Create/update/delete use standard AAZ long-running-operation handling. +`--no-wait` submits the initial request without subsequent polling and returns +no custom operation receipt. It does not make the initial HTTP request instant. + +```azurecli +az webapp silo create --resource-group MyGroup --name MyApp --silo WestApp --location westus2 --primary-location eastus --traffic-mode Active --no-wait +az webapp silo wait --resource-group MyGroup --name MyApp --silo WestApp --created --interval 10 --timeout 1800 +``` + +The separate `wait` commands poll the **resource**, using standard conditions +such as `--exists`, `--created`, `--updated`, `--deleted` and `--custom`. +They do not accept an operation ID. Provisioning completion does not establish +content/configuration revision, endpoint health or DNS convergence. Observe those +separately. A CLI timeout does not cancel the service operation. + +Until the service fixes empty pending Azure async-status responses, silo +create/update/delete use a per-operation response hook to prefer the provided +`Location` monitor. The SDK's standard Location poller keeps waiting on empty +202s; it does not treat acceptance as completion or hide failures. Azure-only +responses without a Location header retain normal Azure async polling. + +Plan-silo deletion also supplies AAZ's existing case-insensitive error formatter +for HTTP 409, preserving legacy App Service `Code`/`Message` details that core's +default conflict mapping otherwise loses. The exception remains a conflict and +the command still fails. Empty or unrecognized bodies remain generic failures; +the CLI never invents a dependency-specific reason. + +Until the service fixes origin point reads, an origin-only compatibility fallback +handles HTTP 404 by using the generated silo list command and its pagination. +Show, update pre-reads and resource waits accept only a unique matching ARM ID +with the expected resource type and properties, and warn that the collection +response may omit stamp-derived IP fields. Missing origins and unrelated errors +remain errors; no resource metadata is fabricated. This does not change delete +operation polling or treat a failed mutation as success. + +## Traffic, Last Known Good and refresh + +```azurecli +az webapp managed-traffic show --resource-group MyGroup --name MyApp +az webapp managed-traffic list --resource-group MyGroup --name MyApp --expand endpoints +az webapp managed-traffic update --resource-group MyGroup --name MyApp --traffic-mode Passive +az webapp managed-traffic update --resource-group MyGroup --name MyApp --traffic-mode Active --endpoints "[{region:eastus,weight:700},{region:westus2,weight:300}]" +az webapp last-known-good mark --resource-group MyGroup --name MyApp +az webapp last-known-good show --resource-group MyGroup --name MyApp +az webapp silo refresh --resource-group MyGroup --name MyApp +``` + +Endpoint collections use typed AAZ shorthand or `@file` input. Weights/priorities +are integers from 1 to 1000, as defined by the specification. `--expand endpoints` +is available on traffic list, not point show. + +Active uses weights; Passive uses priorities. Mark selects the latest ready +revision, not an arbitrary revision or a primary rollback. Refresh returns +success with no output on HTTP 204 **acceptance**; revision propagation continues +asynchronously. It does not poll revisions or invent a target revision. + +## Networking and teardown + +```azurecli +az webapp silo vnet-integration add --resource-group MyGroup --name MyApp --silo WestApp --subnet /subscriptions/SUB/resourceGroups/NetworkGroup/providers/Microsoft.Network/virtualNetworks/WestNet/subnets/integration +az webapp silo vnet-integration show --resource-group MyGroup --name MyApp --silo WestApp +az appservice plan silo vnet-integration list --resource-group MyGroup --name MyPlan --silo WestPlan +az webapp silo vnet-integration update --resource-group MyGroup --name MyApp --silo WestApp --swift-supported false +az webapp silo vnet-integration remove --resource-group MyGroup --name MyApp --silo WestApp --yes +az webapp silo delete --resource-group MyGroup --name MyApp --silo WestApp --yes +az appservice plan silo delete --resource-group MyGroup --name MyPlan --silo WestPlan --yes +``` + +Site networking uses `virtualNetworkConnections/virtualNetwork`; update sends +PATCH. Plan networking inspection is read-only. Delete site silos before backing +plan silos. + +The draft plan VNet-list contract declares an array, but the deployed API returns +an ARM envelope whose `properties` is an array of subnet entries. A handwritten +subclass accepts both shapes and maps the wrapped entries into the generated +model, retaining normal list output without inventing resource IDs. Unexpected +wrapped shapes are errors. Remove this adapter after correcting and regenerating +the service contract; generated Python remains unchanged. + +## Validation and regeneration + +The independent test-owned command catalog is +`tests\latest\active_backup_command_catalog.json` in the App Service module. +Offline tests exercise actual CLI parsing and generated requests, outputs, +pagination, async failures, resource waits and examples. They do not prove +deployed availability or close service defects. + +The opt-in live scenarios cover only this limited surface. Live runs require +explicit authorization, an enabled subscription and the expected regional +capacity. These scenarios are currently live-only; replay recordings and full +command/argument coverage checks remain publication work. Developer-local run +reports, credentials and validation tooling are not committed test assets. + +AAZ command models must accompany eventual CLI publication in `Azure/aaz`. +The local generation experiment retains those models under the ignored +`.x\active-backup\aaz-spike\limited-models-v2` directory. Regenerate from command +models; do not hand-edit the generated `aaz\latest` Python files. The generator's +shared `SwiftVirtualNetwork` resource-ID inference warning and the unmerged +service specification remain review dependencies. The location subclasses live +in `operations\active_backup.py` and directly inherit the matching generated +commands/operations. Remove this compatibility code after the service contract +has been corrected and regenerated, with create/update/read regressions passing. diff --git a/src/azure-cli/azure/cli/command_modules/appservice/_validators.py b/src/azure-cli/azure/cli/command_modules/appservice/_validators.py index ba623cbb0c0..2b8ffb29050 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/_validators.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/_validators.py @@ -10,7 +10,7 @@ ResourceNotFoundError, ValidationError, MutuallyExclusiveArgumentError) from azure.cli.core.commands.client_factory import get_mgmt_service_client, get_subscription_id from azure.cli.core.commands.validators import validate_tags -from azure.mgmt.core.tools import is_valid_resource_id, parse_resource_id +from azure.mgmt.core.tools import is_valid_resource_id, parse_resource_id, resource_id from knack.log import get_logger @@ -27,6 +27,34 @@ logger = get_logger(__name__) +def validate_active_backup_ids(cmd, namespace): + """Keep core ID expansion from retargeting another resource type.""" + parent_type = 'serverfarms' if cmd.name.startswith('appservice plan silo ') else 'sites' + singleton = ('managedTrafficConfig' if cmd.name.startswith('webapp managed-traffic ') else + 'lastKnownGood' if cmd.name.startswith('webapp last-known-good ') else None) + parent_only = bool(singleton) or cmd.name == 'webapp silo refresh' + resource_type = parent_type if parent_only else parent_type + '/silos' + ids = getattr(namespace, '_ids', None) or getattr(namespace, 'ids', None) or [] + for value in ids: + parts = parse_resource_id(value) + required = ('subscription', 'resource_group', 'name') + (() if parent_only else ('child_name_1',)) + if not is_valid_resource_id(value) or not all(parts.get(key) for key in required): + raise InvalidArgumentValueError( + "Provide a Microsoft.Web/{} resource ID with --ids.".format(resource_type)) + parameters = dict( + subscription=parts['subscription'], resource_group=parts['resource_group'], + namespace='Microsoft.Web', type=parent_type, name=parts['name'], + ) + if not parent_only: + parameters.update(child_type_1='silos', child_name_1=parts['child_name_1']) + expected = [resource_id(**parameters).lower()] + if singleton: + expected.append(resource_id(**parameters, child_type_1=singleton, child_name_1='default').lower()) + if value.rstrip('/').lower() not in expected: + raise InvalidArgumentValueError( + "Provide a Microsoft.Web/{} resource ID with --ids.".format(resource_type)) + + def validate_and_convert_to_int(flag, val): try: return int(val) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/__cmd_group.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/__cmd_group.py new file mode 100644 index 00000000000..0d253f4f93f --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "appservice plan silo", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Manage appservice plan silo resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/__init__.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/__init__.py new file mode 100644 index 00000000000..db73033039b --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/__init__.py @@ -0,0 +1,17 @@ +# -------------------------------------------------------------------------------------------- +# 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 ._list import * +from ._show import * +from ._update import * +from ._wait import * diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_create.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_create.py new file mode 100644 index 00000000000..9742c7a4ba4 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_create.py @@ -0,0 +1,321 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "appservice plan silo create", + is_preview=True, +) +class Create(AAZCommand): + """Create a silo for an App Service plan. + + :example: Create a secondary plan silo. + az appservice plan silo create --resource-group MyGroup --name MyPlan --silo WestPlan --location westus2 --primary-location eastus --number-of-workers 1 + + :example: Submit a plan silo create without waiting. + az appservice plan silo create --resource-group MyGroup --name MyPlan --silo WestPlan --location westus2 --primary-location eastus --primary-server-farm-name MyPlan --number-of-workers 2 --zone-redundant false --no-wait + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/serverfarms/{}/silos/{}", "2026-08-01"], + ] + } + + AZ_SUPPORT_NO_WAIT = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_lro_poller(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the App Service plan.", + required=True, + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="The name of the ServerFarmSilo", + required=True, + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.number_of_workers = AAZIntArg( + options=["--number-of-workers"], + arg_group="Properties", + help="Instance number override for the silo.", + ) + _args_schema.primary_location = AAZStrArg( + options=["--primary-location"], + arg_group="Properties", + help="Region of the primary App Service plan.", + required=True, + ) + _args_schema.primary_server_farm_name = AAZStrArg( + options=["--primary-server-farm-name"], + arg_group="Properties", + help="Name of the primary App Service plan.", + ) + _args_schema.location = AAZStrArg( + options=["-l", "--location"], + arg_group="Properties", + help="ARM location of the App Service plan silo.", + required=True, + ) + _args_schema.zone_redundant = AAZBoolArg( + options=["--zone-redundant"], + arg_group="Properties", + help="Zone redundancy override for the silo.", + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + yield self.ServerFarmSiloResourcesCreateOrUpdateServerFarmSilo(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 ServerFarmSiloResourcesCreateOrUpdateServerFarmSilo(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( + self.ctx.args.no_wait, + session, + self.on_200_201, + self.on_error, + lro_options={"final-state-via": "azure-async-operation"}, + path_format_arguments=self.url_parameters, + ) + if session.http_response.status_code in [200, 201]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200_201, + self.on_error, + lro_options={"final-state-via": "azure-async-operation"}, + 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.Web/serverfarms/{name}/silos/{siloName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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("instanceNumberOverride", AAZIntType, ".number_of_workers") + properties.set_prop("primaryLocation", AAZStrType, ".primary_location", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("primaryServerFarmName", AAZStrType, ".primary_server_farm_name") + properties.set_prop("siloLocation", AAZStrType, ".location") + properties.set_prop("zoneRedundantOverride", AAZBoolType, ".zone_redundant") + + 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.created_time = AAZStrType( + serialized_name="createdTime", + flags={"read_only": True}, + ) + properties.error_code = AAZStrType( + serialized_name="errorCode", + flags={"read_only": True}, + ) + properties.instance_number_override = AAZIntType( + serialized_name="instanceNumberOverride", + ) + properties.last_modified_time = AAZStrType( + serialized_name="lastModifiedTime", + flags={"read_only": True}, + ) + properties.primary_location = AAZStrType( + serialized_name="primaryLocation", + flags={"required": True}, + ) + properties.primary_server_farm_name = AAZStrType( + serialized_name="primaryServerFarmName", + ) + properties.provisioning_details = AAZStrType( + serialized_name="provisioningDetails", + flags={"read_only": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.silo_location = AAZStrType( + serialized_name="siloLocation", + ) + properties.zone_redundant_override = AAZBoolType( + serialized_name="zoneRedundantOverride", + ) + + 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-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_delete.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_delete.py new file mode 100644 index 00000000000..2fd7bf030bb --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_delete.py @@ -0,0 +1,180 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "appservice plan silo delete", + is_preview=True, + confirmation="Are you sure you want to perform this operation?", +) +class Delete(AAZCommand): + """Delete a silo for an App Service plan. + + :example: Delete the silo after removing its dependent resources. + az appservice plan silo delete --resource-group MyGroup --name MyPlan --silo WestPlan --yes + + :example: Submit deletion without waiting. + az appservice plan silo delete --resource-group MyGroup --name MyPlan --silo WestPlan --yes --no-wait + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/serverfarms/{}/silos/{}", "2026-08-01"], + ] + } + + AZ_SUPPORT_NO_WAIT = True + + 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.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the App Service plan.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="The name of the ServerFarmSilo", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + yield self.ServerFarmSiloResourcesDeleteServerFarmSilo(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + class ServerFarmSiloResourcesDeleteServerFarmSilo(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( + self.ctx.args.no_wait, + 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( + self.ctx.args.no_wait, + 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( + self.ctx.args.no_wait, + 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.Web/serverfarms/{name}/silos/{siloName}", + **self.url_parameters + ) + + @property + def method(self): + return "DELETE" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_list.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_list.py new file mode 100644 index 00000000000..c9f409a55a1 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_list.py @@ -0,0 +1,252 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "appservice plan silo list", + is_preview=True, +) +class List(AAZCommand): + """List all silos for an App Service plan. + + :example: List the resources for the primary app or plan. + az appservice plan silo list --resource-group MyGroup --name MyPlan + + :example: List resource names. + az appservice plan silo list --resource-group MyGroup --name MyPlan --query "[].name" --output tsv + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/serverfarms/{}/silos", "2026-08-01"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the App Service plan.", + required=True, + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ServerFarmSiloResourcesListServerFarmSilos(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance.value, client_flatten=True) + next_link = self.deserialize_output(self.ctx.vars.instance.next_link) + return result, next_link + + class ServerFarmSiloResourcesListServerFarmSilos(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.Web/serverfarms/{name}/silos", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + ) + _schema_on_200.value = AAZListType( + flags={"required": True}, + ) + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.created_time = AAZStrType( + serialized_name="createdTime", + flags={"read_only": True}, + ) + properties.error_code = AAZStrType( + serialized_name="errorCode", + flags={"read_only": True}, + ) + properties.instance_number_override = AAZIntType( + serialized_name="instanceNumberOverride", + ) + properties.last_modified_time = AAZStrType( + serialized_name="lastModifiedTime", + flags={"read_only": True}, + ) + properties.primary_location = AAZStrType( + serialized_name="primaryLocation", + flags={"required": True}, + ) + properties.primary_server_farm_name = AAZStrType( + serialized_name="primaryServerFarmName", + ) + properties.provisioning_details = AAZStrType( + serialized_name="provisioningDetails", + flags={"read_only": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.silo_location = AAZStrType( + serialized_name="siloLocation", + ) + properties.zone_redundant_override = AAZBoolType( + serialized_name="zoneRedundantOverride", + ) + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + +__all__ = ["List"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_show.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_show.py new file mode 100644 index 00000000000..ba64ff6c9e3 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_show.py @@ -0,0 +1,253 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "appservice plan silo show", + is_preview=True, +) +class Show(AAZCommand): + """Get a specific silo for an App Service plan. + + :example: Show the resource for the primary app or plan. + az appservice plan silo show --resource-group MyGroup --name MyPlan --silo WestPlan + + :example: Show the resource identity. + az appservice plan silo show --resource-group MyGroup --name MyPlan --silo WestPlan --query id --output tsv + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/serverfarms/{}/silos/{}", "2026-08-01"], + ] + } + + 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.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the App Service plan.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="The name of the ServerFarmSilo", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ServerFarmSiloResourcesGetServerFarmSilo(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 ServerFarmSiloResourcesGetServerFarmSilo(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.Web/serverfarms/{name}/silos/{siloName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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.created_time = AAZStrType( + serialized_name="createdTime", + flags={"read_only": True}, + ) + properties.error_code = AAZStrType( + serialized_name="errorCode", + flags={"read_only": True}, + ) + properties.instance_number_override = AAZIntType( + serialized_name="instanceNumberOverride", + ) + properties.last_modified_time = AAZStrType( + serialized_name="lastModifiedTime", + flags={"read_only": True}, + ) + properties.primary_location = AAZStrType( + serialized_name="primaryLocation", + flags={"required": True}, + ) + properties.primary_server_farm_name = AAZStrType( + serialized_name="primaryServerFarmName", + ) + properties.provisioning_details = AAZStrType( + serialized_name="provisioningDetails", + flags={"read_only": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.silo_location = AAZStrType( + serialized_name="siloLocation", + ) + properties.zone_redundant_override = AAZBoolType( + serialized_name="zoneRedundantOverride", + ) + + 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-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_update.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_update.py new file mode 100644 index 00000000000..c333f112ded --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_update.py @@ -0,0 +1,469 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "appservice plan silo update", + is_preview=True, +) +class Update(AAZCommand): + """Update a silo for an App Service plan. + + :example: Change the secondary worker override and retain explicit routing fields. + az appservice plan silo update --resource-group MyGroup --name MyPlan --silo WestPlan --number-of-workers 2 --zone-redundant false --location westus2 --primary-location eastus --primary-server-farm-name MyPlan + + :example: Update a plan silo by resource ID. + az appservice plan silo update --ids /subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/MyGroup/providers/Microsoft.Web/serverfarms/MyPlan/silos/WestPlan --set properties.instanceNumberOverride=1 --no-wait + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/serverfarms/{}/silos/{}", "2026-08-01"], + ] + } + + AZ_SUPPORT_NO_WAIT = True + + AZ_SUPPORT_GENERIC_UPDATE = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_lro_poller(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the App Service plan.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="The name of the ServerFarmSilo", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.number_of_workers = AAZIntArg( + options=["--number-of-workers"], + arg_group="Properties", + help="Instance number override for the silo.", + nullable=True, + ) + _args_schema.primary_location = AAZStrArg( + options=["--primary-location"], + arg_group="Properties", + help="Region of the primary App Service plan.", + ) + _args_schema.primary_server_farm_name = AAZStrArg( + options=["--primary-server-farm-name"], + arg_group="Properties", + help="Name of the primary App Service plan.", + nullable=True, + ) + _args_schema.location = AAZStrArg( + options=["-l", "--location"], + arg_group="Properties", + help="ARM location of the App Service plan silo.", + nullable=True, + ) + _args_schema.zone_redundant = AAZBoolArg( + options=["--zone-redundant"], + arg_group="Properties", + help="Zone redundancy override for the silo.", + nullable=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ServerFarmSiloResourcesGetServerFarmSilo(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) + yield self.ServerFarmSiloResourcesCreateOrUpdateServerFarmSilo(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 ServerFarmSiloResourcesGetServerFarmSilo(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.Web/serverfarms/{name}/silos/{siloName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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_server_farm_silo_read(cls._schema_on_200) + + return cls._schema_on_200 + + class ServerFarmSiloResourcesCreateOrUpdateServerFarmSilo(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( + self.ctx.args.no_wait, + session, + self.on_200_201, + self.on_error, + lro_options={"final-state-via": "azure-async-operation"}, + path_format_arguments=self.url_parameters, + ) + if session.http_response.status_code in [200, 201]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200_201, + self.on_error, + lro_options={"final-state-via": "azure-async-operation"}, + 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.Web/serverfarms/{name}/silos/{siloName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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_server_farm_silo_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("instanceNumberOverride", AAZIntType, ".number_of_workers") + properties.set_prop("primaryLocation", AAZStrType, ".primary_location", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("primaryServerFarmName", AAZStrType, ".primary_server_farm_name") + properties.set_prop("siloLocation", AAZStrType, ".location") + properties.set_prop("zoneRedundantOverride", AAZBoolType, ".zone_redundant") + + 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_server_farm_silo_read = None + + @classmethod + def _build_schema_server_farm_silo_read(cls, _schema): + if cls._schema_server_farm_silo_read is not None: + _schema.id = cls._schema_server_farm_silo_read.id + _schema.name = cls._schema_server_farm_silo_read.name + _schema.properties = cls._schema_server_farm_silo_read.properties + _schema.system_data = cls._schema_server_farm_silo_read.system_data + _schema.type = cls._schema_server_farm_silo_read.type + return + + cls._schema_server_farm_silo_read = _schema_server_farm_silo_read = AAZObjectType() + + server_farm_silo_read = _schema_server_farm_silo_read + server_farm_silo_read.id = AAZStrType( + flags={"read_only": True}, + ) + server_farm_silo_read.name = AAZStrType( + flags={"read_only": True}, + ) + server_farm_silo_read.properties = AAZObjectType() + server_farm_silo_read.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + server_farm_silo_read.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = _schema_server_farm_silo_read.properties + properties.created_time = AAZStrType( + serialized_name="createdTime", + flags={"read_only": True}, + ) + properties.error_code = AAZStrType( + serialized_name="errorCode", + flags={"read_only": True}, + ) + properties.instance_number_override = AAZIntType( + serialized_name="instanceNumberOverride", + ) + properties.last_modified_time = AAZStrType( + serialized_name="lastModifiedTime", + flags={"read_only": True}, + ) + properties.primary_location = AAZStrType( + serialized_name="primaryLocation", + flags={"required": True}, + ) + properties.primary_server_farm_name = AAZStrType( + serialized_name="primaryServerFarmName", + ) + properties.provisioning_details = AAZStrType( + serialized_name="provisioningDetails", + flags={"read_only": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.silo_location = AAZStrType( + serialized_name="siloLocation", + ) + properties.zone_redundant_override = AAZBoolType( + serialized_name="zoneRedundantOverride", + ) + + system_data = _schema_server_farm_silo_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_server_farm_silo_read.id + _schema.name = cls._schema_server_farm_silo_read.name + _schema.properties = cls._schema_server_farm_silo_read.properties + _schema.system_data = cls._schema_server_farm_silo_read.system_data + _schema.type = cls._schema_server_farm_silo_read.type + + +__all__ = ["Update"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_wait.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_wait.py new file mode 100644 index 00000000000..e4561bfcfe4 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/_wait.py @@ -0,0 +1,254 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "appservice plan silo wait", + is_preview=True, +) +class Wait(AAZWaitCommand): + """Place the CLI in a waiting state until a condition is met. + + Poll the silo resource, not a saved operation ID. Content replication, endpoint health and DNS convergence require separate observation. + + :example: Wait for Succeeded provisioning state. + az appservice plan silo wait --resource-group MyGroup --name MyPlan --silo WestPlan --created --interval 10 --timeout 1800 + + :example: Wait until a deleted silo no longer exists. + az appservice plan silo wait --resource-group MyGroup --name MyPlan --silo WestPlan --deleted --interval 10 --timeout 1800 + """ + + _aaz_info = { + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/serverfarms/{}/silos/{}", "2026-08-01"], + ] + } + + 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.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the App Service plan.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="The name of the ServerFarmSilo", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ServerFarmSiloResourcesGetServerFarmSilo(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=False) + return result + + class ServerFarmSiloResourcesGetServerFarmSilo(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.Web/serverfarms/{name}/silos/{siloName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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.created_time = AAZStrType( + serialized_name="createdTime", + flags={"read_only": True}, + ) + properties.error_code = AAZStrType( + serialized_name="errorCode", + flags={"read_only": True}, + ) + properties.instance_number_override = AAZIntType( + serialized_name="instanceNumberOverride", + ) + properties.last_modified_time = AAZStrType( + serialized_name="lastModifiedTime", + flags={"read_only": True}, + ) + properties.primary_location = AAZStrType( + serialized_name="primaryLocation", + flags={"required": True}, + ) + properties.primary_server_farm_name = AAZStrType( + serialized_name="primaryServerFarmName", + ) + properties.provisioning_details = AAZStrType( + serialized_name="provisioningDetails", + flags={"read_only": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.silo_location = AAZStrType( + serialized_name="siloLocation", + ) + properties.zone_redundant_override = AAZBoolType( + serialized_name="zoneRedundantOverride", + ) + + 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 _WaitHelper: + """Helper class for Wait""" + + +__all__ = ["Wait"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/vnet_integration/__cmd_group.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/vnet_integration/__cmd_group.py new file mode 100644 index 00000000000..c575149527b --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/vnet_integration/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "appservice plan silo vnet-integration", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Manage appservice plan silo vnet-integration resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/vnet_integration/__init__.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/vnet_integration/__init__.py new file mode 100644 index 00000000000..d63ae5a6fc9 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/vnet_integration/__init__.py @@ -0,0 +1,12 @@ +# -------------------------------------------------------------------------------------------- +# 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 ._list import * diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/vnet_integration/_list.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/vnet_integration/_list.py new file mode 100644 index 00000000000..c71dce04d0b --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/appservice/plan/silo/vnet_integration/_list.py @@ -0,0 +1,227 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "appservice plan silo vnet-integration list", + is_preview=True, +) +class List(AAZCommand): + """Get the virtual network integrations associated with an App Service plan silo. + + :example: List virtual network integrations on the plan silo. + az appservice plan silo vnet-integration list --resource-group MyGroup --name MyPlan --silo WestPlan + + :example: List subnet resource IDs. + az appservice plan silo vnet-integration list --resource-group MyGroup --name MyPlan --silo WestPlan --query "[].subnetResourceId" + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/serverfarms/{}/silos/{}/virtualnetworkintegrations", "2026-08-01"], + ] + } + + 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.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the App Service plan.", + required=True, + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="The name of the ServerFarmSilo", + required=True, + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ServerFarmSiloResourcesGetServerFarmSiloVnetIntegrations(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 ServerFarmSiloResourcesGetServerFarmSiloVnetIntegrations(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.Web/serverfarms/{name}/silos/{siloName}/virtualNetworkIntegrations", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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 = AAZListType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.Element = AAZObjectType() + + _element = cls._schema_on_200.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.kind = AAZStrType() + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType( + flags={"client_flatten": True}, + ) + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.Element.properties + properties.subnet_resource_id = AAZStrType( + serialized_name="subnetResourceId", + ) + properties.swift_supported = AAZBoolType( + serialized_name="swiftSupported", + ) + + system_data = cls._schema_on_200.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + +__all__ = ["List"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/__cmd_group.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/__cmd_group.py new file mode 100644 index 00000000000..ed56574f04e --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/__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( + "webapp", +) +class __CMDGroup(AAZCommandGroup): + """Manage webapp resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/__init__.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/__init__.py new file mode 100644 index 00000000000..5a9d61963d6 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/__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-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/__cmd_group.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/__cmd_group.py new file mode 100644 index 00000000000..473d0012942 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp last-known-good", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Manage webapp last-known-good resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/__init__.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/__init__.py new file mode 100644 index 00000000000..8989cb677c6 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/__init__.py @@ -0,0 +1,14 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * +from ._list import * +from ._mark import * +from ._show import * diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/_list.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/_list.py new file mode 100644 index 00000000000..d183411167e --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/_list.py @@ -0,0 +1,228 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp last-known-good list", + is_preview=True, +) +class List(AAZCommand): + """List the last known good revision state for a Web App. + + :example: List the resources for the primary app or plan. + az webapp last-known-good list --resource-group MyGroup --name MyApp + + :example: List resource names. + az webapp last-known-good list --resource-group MyGroup --name MyApp --query "[].name" --output tsv + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/lastknowngood", "2026-08-01"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.LastKnownGoodResourcesListLastKnownGoods(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance.value, client_flatten=True) + next_link = self.deserialize_output(self.ctx.vars.instance.next_link) + return result, next_link + + class LastKnownGoodResourcesListLastKnownGoods(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.Web/sites/{name}/lastKnownGood", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + ) + _schema_on_200.value = AAZListType( + flags={"required": True}, + ) + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.created_time = AAZStrType( + serialized_name="createdTime", + flags={"read_only": True}, + ) + properties.revision_number = AAZIntType( + serialized_name="revisionNumber", + flags={"read_only": True}, + ) + properties.site_name = AAZStrType( + serialized_name="siteName", + flags={"read_only": True}, + ) + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + +__all__ = ["List"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/_mark.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/_mark.py new file mode 100644 index 00000000000..76b9f050086 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/_mark.py @@ -0,0 +1,216 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp last-known-good mark", + is_preview=True, +) +class Mark(AAZCommand): + """Marks the current Web App state as last known good. + + :example: Mark the latest ready revision as last known good. + az webapp last-known-good mark --resource-group MyGroup --name MyApp + + :example: Return the revision number selected by the service. + az webapp last-known-good mark --resource-group MyGroup --name MyApp --query properties.revisionNumber + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/lastknowngood/default/mark", "2026-08-01"], + ] + } + + 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.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.LastKnownGoodResourcesMarkLastKnownGood(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 LastKnownGoodResourcesMarkLastKnownGood(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.Web/sites/{name}/lastKnownGood/default/mark", + **self.url_parameters + ) + + @property + def method(self): + return "POST" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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.created_time = AAZStrType( + serialized_name="createdTime", + flags={"read_only": True}, + ) + properties.revision_number = AAZIntType( + serialized_name="revisionNumber", + flags={"read_only": True}, + ) + properties.site_name = AAZStrType( + serialized_name="siteName", + flags={"read_only": True}, + ) + + 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 _MarkHelper: + """Helper class for Mark""" + + +__all__ = ["Mark"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/_show.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/_show.py new file mode 100644 index 00000000000..2c117435de6 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/last_known_good/_show.py @@ -0,0 +1,216 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp last-known-good show", + is_preview=True, +) +class Show(AAZCommand): + """Get the last known good revision state for a Web App. + + :example: Show the resource for the primary app or plan. + az webapp last-known-good show --resource-group MyGroup --name MyApp + + :example: Show the resource identity. + az webapp last-known-good show --resource-group MyGroup --name MyApp --query id --output tsv + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/lastknowngood/default", "2026-08-01"], + ] + } + + 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.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.LastKnownGoodResourcesGetLastKnownGood(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 LastKnownGoodResourcesGetLastKnownGood(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.Web/sites/{name}/lastKnownGood/default", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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.created_time = AAZStrType( + serialized_name="createdTime", + flags={"read_only": True}, + ) + properties.revision_number = AAZIntType( + serialized_name="revisionNumber", + flags={"read_only": True}, + ) + properties.site_name = AAZStrType( + serialized_name="siteName", + flags={"read_only": True}, + ) + + 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-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/__cmd_group.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/__cmd_group.py new file mode 100644 index 00000000000..0be42b7c1ca --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp managed-traffic", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Manage webapp managed-traffic resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/__init__.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/__init__.py new file mode 100644 index 00000000000..bee768f7653 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/__init__.py @@ -0,0 +1,14 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +from .__cmd_group import * +from ._list import * +from ._show import * +from ._update import * diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/_list.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/_list.py new file mode 100644 index 00000000000..64a110a5103 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/_list.py @@ -0,0 +1,240 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp managed-traffic list", + is_preview=True, +) +class List(AAZCommand): + """List managed traffic configuration resources for a Web App. + + :example: List the app's traffic configuration. + az webapp managed-traffic list --resource-group MyGroup --name MyApp + + :example: Include persisted endpoint weights and priorities. + az webapp managed-traffic list --resource-group MyGroup --name MyApp --expand endpoints --query "[].properties.endpoints" + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/managedtrafficconfig", "2026-08-01"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.expand = AAZStrArg( + options=["--expand"], + help="Set to `endpoints` to return the endpoint collection without projection, including both weight and priority for every endpoint.", + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ManagedTrafficConfigResourcesListManagedTrafficConfigs(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance.value, client_flatten=True) + next_link = self.deserialize_output(self.ctx.vars.instance.next_link) + return result, next_link + + class ManagedTrafficConfigResourcesListManagedTrafficConfigs(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.Web/sites/{name}/managedTrafficConfig", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.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( + "$expand", self.ctx.args.expand, + ), + **self.serialize_query_param( + "api-version", "2026-08-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + ) + _schema_on_200.value = AAZListType( + flags={"required": True}, + ) + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.endpoints = AAZListType() + properties.traffic_mode = AAZStrType( + serialized_name="trafficMode", + ) + + endpoints = cls._schema_on_200.value.Element.properties.endpoints + endpoints.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.endpoints.Element + _element.is_weight_auto_computed = AAZBoolType( + serialized_name="isWeightAutoComputed", + ) + _element.priority = AAZIntType() + _element.region = AAZStrType( + flags={"required": True}, + ) + _element.weight = AAZIntType() + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + +__all__ = ["List"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/_show.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/_show.py new file mode 100644 index 00000000000..5236d84982b --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/_show.py @@ -0,0 +1,221 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp managed-traffic show", + is_preview=True, +) +class Show(AAZCommand): + """Get managed traffic configuration for a Web App. + + :example: Show the resource for the primary app or plan. + az webapp managed-traffic show --resource-group MyGroup --name MyApp + + :example: Show the resource identity. + az webapp managed-traffic show --resource-group MyGroup --name MyApp --query id --output tsv + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/managedtrafficconfig/default", "2026-08-01"], + ] + } + + 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.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ManagedTrafficConfigResourcesGetManagedTrafficConfig(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 ManagedTrafficConfigResourcesGetManagedTrafficConfig(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.Web/sites/{name}/managedTrafficConfig/default", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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.endpoints = AAZListType() + properties.traffic_mode = AAZStrType( + serialized_name="trafficMode", + ) + + endpoints = cls._schema_on_200.properties.endpoints + endpoints.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.endpoints.Element + _element.is_weight_auto_computed = AAZBoolType( + serialized_name="isWeightAutoComputed", + ) + _element.priority = AAZIntType() + _element.region = AAZStrType( + flags={"required": True}, + ) + _element.weight = AAZIntType() + + 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-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/_update.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/_update.py new file mode 100644 index 00000000000..332e7c6a764 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/managed_traffic/_update.py @@ -0,0 +1,442 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp managed-traffic update", + is_preview=True, +) +class Update(AAZCommand): + """Update managed traffic configuration for a Web App. + + :example: Use priority-based traffic routing. + az webapp managed-traffic update --resource-group MyGroup --name MyApp --traffic-mode Passive --endpoints "[{region:eastus,priority:1},{region:westus2,priority:2}]" + + :example: Set explicit endpoint weights with structured arguments. + az webapp managed-traffic update --resource-group MyGroup --name MyApp --traffic-mode Active --endpoints "[{region:eastus,weight:700,is-weight-auto-computed:false},{region:westus2,weight:300}]" + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/managedtrafficconfig/default", "2026-08-01"], + ] + } + + 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.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.endpoints = AAZListArg( + options=["--endpoints"], + arg_group="Properties", + help="Per-region endpoint configurations controlling weight and priority.", + nullable=True, + ) + _args_schema.traffic_mode = AAZStrArg( + options=["--traffic-mode"], + arg_group="Properties", + help="Traffic routing mode: Active (weighted) or Passive (priority-based failover).", + nullable=True, + enum={"Active": "Active", "Passive": "Passive", "Unknown": "Unknown"}, + ) + + endpoints = cls._args_schema.endpoints + endpoints.Element = AAZObjectArg( + nullable=True, + ) + + _element = cls._args_schema.endpoints.Element + _element.is_weight_auto_computed = AAZBoolArg( + options=["is-weight-auto-computed"], + help="Whether the weight was auto-computed from the silo's worker count.", + nullable=True, + ) + _element.priority = AAZIntArg( + options=["priority"], + help="Priority of the endpoint (1-1000). Lower values receive traffic first in priority routing.", + nullable=True, + fmt=AAZIntArgFormat( + maximum=1000, + minimum=1, + ), + ) + _element.region = AAZStrArg( + options=["region"], + help="Normalized Azure region name (for example, eastus, westus).", + ) + _element.weight = AAZIntArg( + options=["weight"], + help="Weight of the endpoint (1-1000). Higher values receive proportionally more traffic in weighted routing.", + nullable=True, + fmt=AAZIntArgFormat( + maximum=1000, + minimum=1, + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ManagedTrafficConfigResourcesGetManagedTrafficConfig(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) + self.ManagedTrafficConfigResourcesUpdateManagedTrafficConfig(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 ManagedTrafficConfigResourcesGetManagedTrafficConfig(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.Web/sites/{name}/managedTrafficConfig/default", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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_managed_traffic_config_resource_read(cls._schema_on_200) + + return cls._schema_on_200 + + class ManagedTrafficConfigResourcesUpdateManagedTrafficConfig(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.Web/sites/{name}/managedTrafficConfig/default", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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_managed_traffic_config_resource_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("endpoints", AAZListType, ".endpoints") + properties.set_prop("trafficMode", AAZStrType, ".traffic_mode") + + endpoints = _builder.get(".properties.endpoints") + if endpoints is not None: + endpoints.set_elements(AAZObjectType, ".") + + _elements = _builder.get(".properties.endpoints[]") + if _elements is not None: + _elements.set_prop("isWeightAutoComputed", AAZBoolType, ".is_weight_auto_computed") + _elements.set_prop("priority", AAZIntType, ".priority") + _elements.set_prop("region", AAZStrType, ".region", typ_kwargs={"flags": {"required": True}}) + _elements.set_prop("weight", AAZIntType, ".weight") + + 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_managed_traffic_config_resource_read = None + + @classmethod + def _build_schema_managed_traffic_config_resource_read(cls, _schema): + if cls._schema_managed_traffic_config_resource_read is not None: + _schema.id = cls._schema_managed_traffic_config_resource_read.id + _schema.name = cls._schema_managed_traffic_config_resource_read.name + _schema.properties = cls._schema_managed_traffic_config_resource_read.properties + _schema.system_data = cls._schema_managed_traffic_config_resource_read.system_data + _schema.type = cls._schema_managed_traffic_config_resource_read.type + return + + cls._schema_managed_traffic_config_resource_read = _schema_managed_traffic_config_resource_read = AAZObjectType() + + managed_traffic_config_resource_read = _schema_managed_traffic_config_resource_read + managed_traffic_config_resource_read.id = AAZStrType( + flags={"read_only": True}, + ) + managed_traffic_config_resource_read.name = AAZStrType( + flags={"read_only": True}, + ) + managed_traffic_config_resource_read.properties = AAZObjectType() + managed_traffic_config_resource_read.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + managed_traffic_config_resource_read.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = _schema_managed_traffic_config_resource_read.properties + properties.endpoints = AAZListType() + properties.traffic_mode = AAZStrType( + serialized_name="trafficMode", + ) + + endpoints = _schema_managed_traffic_config_resource_read.properties.endpoints + endpoints.Element = AAZObjectType() + + _element = _schema_managed_traffic_config_resource_read.properties.endpoints.Element + _element.is_weight_auto_computed = AAZBoolType( + serialized_name="isWeightAutoComputed", + ) + _element.priority = AAZIntType() + _element.region = AAZStrType( + flags={"required": True}, + ) + _element.weight = AAZIntType() + + system_data = _schema_managed_traffic_config_resource_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_managed_traffic_config_resource_read.id + _schema.name = cls._schema_managed_traffic_config_resource_read.name + _schema.properties = cls._schema_managed_traffic_config_resource_read.properties + _schema.system_data = cls._schema_managed_traffic_config_resource_read.system_data + _schema.type = cls._schema_managed_traffic_config_resource_read.type + + +__all__ = ["Update"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/__cmd_group.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/__cmd_group.py new file mode 100644 index 00000000000..3b7e1e4616e --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp silo", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Manage webapp silo resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/__init__.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/__init__.py new file mode 100644 index 00000000000..06de5aa8813 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/__init__.py @@ -0,0 +1,18 @@ +# -------------------------------------------------------------------------------------------- +# 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 ._list import * +from ._refresh import * +from ._show import * +from ._update import * +from ._wait import * diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_create.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_create.py new file mode 100644 index 00000000000..5bd04de87fe --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_create.py @@ -0,0 +1,400 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp silo create", + is_preview=True, +) +class Create(AAZCommand): + """Create a silo for a Web App. + + :example: Create a site silo with active traffic routing. + az webapp silo create --resource-group MyGroup --name MyApp --silo WestApp --location westus2 --primary-location eastus --traffic-mode Active --follow-last-known-good false + + :example: Submit a site silo create with a networking override. + az webapp silo create --resource-group MyGroup --name MyApp --silo WestApp --location westus2 --primary-location eastus --primary-site-name MyApp --traffic-mode Passive --endpoint-status Enabled --networking-override "{vnet-integration-override:/subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/NetworkGroup/providers/Microsoft.Network/virtualNetworks/WestNet/subnets/integration}" --no-wait + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/silos/{}", "2026-08-01"], + ] + } + + AZ_SUPPORT_NO_WAIT = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_lro_poller(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="The name of the SiteSilo", + required=True, + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.endpoint_status = AAZStrArg( + options=["--endpoint-status"], + arg_group="Properties", + help="Status of the silo traffic endpoint.", + ) + _args_schema.follow_last_known_good = AAZBoolArg( + options=["--follow-last-known-good"], + arg_group="Properties", + help="Whether the silo should follow the last known good revision.", + ) + _args_schema.networking_override = AAZObjectArg( + options=["--networking-override"], + arg_group="Properties", + help="Networking overrides for the silo.", + ) + _args_schema.primary_location = AAZStrArg( + options=["--primary-location"], + arg_group="Properties", + help="Region of the primary site.", + required=True, + ) + _args_schema.primary_site_name = AAZStrArg( + options=["--primary-site-name"], + arg_group="Properties", + help="Name of the primary site.", + ) + _args_schema.location = AAZStrArg( + options=["-l", "--location"], + arg_group="Properties", + help="ARM location of the site silo.", + required=True, + ) + _args_schema.traffic_mode = AAZStrArg( + options=["--traffic-mode"], + arg_group="Properties", + help="Traffic mode for the site silo's Traffic Manager profile.", + enum={"Active": "Active", "Passive": "Passive", "Unknown": "Unknown"}, + ) + + networking_override = cls._args_schema.networking_override + networking_override.vnet_integration_override = AAZStrArg( + options=["vnet-integration-override"], + help="Virtual network subnet resource ID override for the silo.", + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + yield self.SiteSiloResourcesCreateOrUpdateSiteSilo(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 SiteSiloResourcesCreateOrUpdateSiteSilo(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( + self.ctx.args.no_wait, + session, + self.on_200_201, + self.on_error, + lro_options={"final-state-via": "azure-async-operation"}, + path_format_arguments=self.url_parameters, + ) + if session.http_response.status_code in [200, 201]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200_201, + self.on_error, + lro_options={"final-state-via": "azure-async-operation"}, + 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.Web/sites/{name}/silos/{siloName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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("endpointStatus", AAZStrType, ".endpoint_status") + properties.set_prop("followLastKnownGood", AAZBoolType, ".follow_last_known_good") + properties.set_prop("networkingOverride", AAZObjectType, ".networking_override") + properties.set_prop("primaryLocation", AAZStrType, ".primary_location", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("primarySiteName", AAZStrType, ".primary_site_name") + properties.set_prop("siloLocation", AAZStrType, ".location") + properties.set_prop("trafficMode", AAZStrType, ".traffic_mode") + + networking_override = _builder.get(".properties.networkingOverride") + if networking_override is not None: + networking_override.set_prop("vnetIntegrationOverride", AAZStrType, ".vnet_integration_override") + + 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.created_time = AAZStrType( + serialized_name="createdTime", + flags={"read_only": True}, + ) + properties.default_host_name = AAZStrType( + serialized_name="defaultHostName", + flags={"read_only": True}, + ) + properties.endpoint_status = AAZStrType( + serialized_name="endpointStatus", + ) + properties.error_code = AAZStrType( + serialized_name="errorCode", + flags={"read_only": True}, + ) + properties.follow_last_known_good = AAZBoolType( + serialized_name="followLastKnownGood", + ) + properties.global_host_name = AAZStrType( + serialized_name="globalHostName", + flags={"read_only": True}, + ) + properties.inbound_ip_address = AAZStrType( + serialized_name="inboundIpAddress", + flags={"read_only": True}, + ) + properties.inbound_ipv6_address = AAZStrType( + serialized_name="inboundIpv6Address", + flags={"read_only": True}, + ) + properties.last_modified_time = AAZStrType( + serialized_name="lastModifiedTime", + flags={"read_only": True}, + ) + properties.monitor_status = AAZStrType( + serialized_name="monitorStatus", + flags={"read_only": True}, + ) + properties.networking_override = AAZObjectType( + serialized_name="networkingOverride", + ) + properties.outbound_ip_addresses = AAZStrType( + serialized_name="outboundIpAddresses", + flags={"read_only": True}, + ) + properties.outbound_ipv6_addresses = AAZStrType( + serialized_name="outboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.possible_inbound_ip_addresses = AAZStrType( + serialized_name="possibleInboundIpAddresses", + flags={"read_only": True}, + ) + properties.possible_inbound_ipv6_addresses = AAZStrType( + serialized_name="possibleInboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.possible_outbound_ip_addresses = AAZStrType( + serialized_name="possibleOutboundIpAddresses", + flags={"read_only": True}, + ) + properties.possible_outbound_ipv6_addresses = AAZStrType( + serialized_name="possibleOutboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.primary_location = AAZStrType( + serialized_name="primaryLocation", + flags={"required": True}, + ) + properties.primary_site_name = AAZStrType( + serialized_name="primarySiteName", + ) + properties.provisioning_details = AAZStrType( + serialized_name="provisioningDetails", + flags={"read_only": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.revision_number = AAZIntType( + serialized_name="revisionNumber", + flags={"read_only": True}, + ) + properties.silo_location = AAZStrType( + serialized_name="siloLocation", + ) + + networking_override = cls._schema_on_200_201.properties.networking_override + networking_override.vnet_integration_override = AAZStrType( + serialized_name="vnetIntegrationOverride", + ) + + 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-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_delete.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_delete.py new file mode 100644 index 00000000000..5a2f56143b1 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_delete.py @@ -0,0 +1,180 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp silo delete", + is_preview=True, + confirmation="Are you sure you want to perform this operation?", +) +class Delete(AAZCommand): + """Delete a silo for a Web App. + + :example: Delete the silo after removing its dependent resources. + az webapp silo delete --resource-group MyGroup --name MyApp --silo WestApp --yes + + :example: Submit deletion without waiting. + az webapp silo delete --resource-group MyGroup --name MyApp --silo WestApp --yes --no-wait + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/silos/{}", "2026-08-01"], + ] + } + + AZ_SUPPORT_NO_WAIT = True + + 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.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="The name of the SiteSilo", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + yield self.SiteSiloResourcesDeleteSiteSilo(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + class SiteSiloResourcesDeleteSiteSilo(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( + self.ctx.args.no_wait, + 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( + self.ctx.args.no_wait, + 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( + self.ctx.args.no_wait, + 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.Web/sites/{name}/silos/{siloName}", + **self.url_parameters + ) + + @property + def method(self): + return "DELETE" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_list.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_list.py new file mode 100644 index 00000000000..31e3dbffaa6 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_list.py @@ -0,0 +1,308 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp silo list", + is_preview=True, +) +class List(AAZCommand): + """List all silos for a Web App. + + :example: List the resources for the primary app or plan. + az webapp silo list --resource-group MyGroup --name MyApp + + :example: List resource names. + az webapp silo list --resource-group MyGroup --name MyApp --query "[].name" --output tsv + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/silos", "2026-08-01"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.SiteSiloResourcesListSiteSilos(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + def _output(self, *args, **kwargs): + result = self.deserialize_output(self.ctx.vars.instance.value, client_flatten=True) + next_link = self.deserialize_output(self.ctx.vars.instance.next_link) + return result, next_link + + class SiteSiloResourcesListSiteSilos(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.Web/sites/{name}/silos", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + ) + _schema_on_200.value = AAZListType( + flags={"required": True}, + ) + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.created_time = AAZStrType( + serialized_name="createdTime", + flags={"read_only": True}, + ) + properties.default_host_name = AAZStrType( + serialized_name="defaultHostName", + flags={"read_only": True}, + ) + properties.endpoint_status = AAZStrType( + serialized_name="endpointStatus", + ) + properties.error_code = AAZStrType( + serialized_name="errorCode", + flags={"read_only": True}, + ) + properties.follow_last_known_good = AAZBoolType( + serialized_name="followLastKnownGood", + ) + properties.global_host_name = AAZStrType( + serialized_name="globalHostName", + flags={"read_only": True}, + ) + properties.inbound_ip_address = AAZStrType( + serialized_name="inboundIpAddress", + flags={"read_only": True}, + ) + properties.inbound_ipv6_address = AAZStrType( + serialized_name="inboundIpv6Address", + flags={"read_only": True}, + ) + properties.last_modified_time = AAZStrType( + serialized_name="lastModifiedTime", + flags={"read_only": True}, + ) + properties.monitor_status = AAZStrType( + serialized_name="monitorStatus", + flags={"read_only": True}, + ) + properties.networking_override = AAZObjectType( + serialized_name="networkingOverride", + ) + properties.outbound_ip_addresses = AAZStrType( + serialized_name="outboundIpAddresses", + flags={"read_only": True}, + ) + properties.outbound_ipv6_addresses = AAZStrType( + serialized_name="outboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.possible_inbound_ip_addresses = AAZStrType( + serialized_name="possibleInboundIpAddresses", + flags={"read_only": True}, + ) + properties.possible_inbound_ipv6_addresses = AAZStrType( + serialized_name="possibleInboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.possible_outbound_ip_addresses = AAZStrType( + serialized_name="possibleOutboundIpAddresses", + flags={"read_only": True}, + ) + properties.possible_outbound_ipv6_addresses = AAZStrType( + serialized_name="possibleOutboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.primary_location = AAZStrType( + serialized_name="primaryLocation", + flags={"required": True}, + ) + properties.primary_site_name = AAZStrType( + serialized_name="primarySiteName", + ) + properties.provisioning_details = AAZStrType( + serialized_name="provisioningDetails", + flags={"read_only": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.revision_number = AAZIntType( + serialized_name="revisionNumber", + flags={"read_only": True}, + ) + properties.silo_location = AAZStrType( + serialized_name="siloLocation", + ) + + networking_override = cls._schema_on_200.value.Element.properties.networking_override + networking_override.vnet_integration_override = AAZStrType( + serialized_name="vnetIntegrationOverride", + ) + + system_data = cls._schema_on_200.value.Element.system_data + system_data.created_at = AAZStrType( + serialized_name="createdAt", + ) + system_data.created_by = AAZStrType( + serialized_name="createdBy", + ) + system_data.created_by_type = AAZStrType( + serialized_name="createdByType", + ) + system_data.last_modified_at = AAZStrType( + serialized_name="lastModifiedAt", + ) + system_data.last_modified_by = AAZStrType( + serialized_name="lastModifiedBy", + ) + system_data.last_modified_by_type = AAZStrType( + serialized_name="lastModifiedByType", + ) + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + +__all__ = ["List"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_refresh.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_refresh.py new file mode 100644 index 00000000000..8c039f3b28d --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_refresh.py @@ -0,0 +1,137 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp silo refresh", + is_preview=True, +) +class Refresh(AAZCommand): + """Forces a fresh revision for an Active Backup primary Web App and re-syncs all of its silos. The refresh request is recorded synchronously; silos then converge asynchronously — track progress through each silo's revisionNumber. + + :example: Request a fresh revision for the primary app. + az webapp silo refresh --resource-group MyGroup --name MyApp + + :example: Request refresh in an explicit subscription. + az webapp silo refresh --resource-group MyGroup --name MyApp --subscription 00000000-0000-0000-0000-000000000001 + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/refreshsilos", "2026-08-01"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return None + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.SiteSiloResourcesRefreshSiteSilos(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + class SiteSiloResourcesRefreshSiteSilos(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 [204]: + return self.on_204(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/refreshSilos", + **self.url_parameters + ) + + @property + def method(self): + return "POST" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + required=True, + ), + } + return parameters + + def on_204(self, session): + pass + + +class _RefreshHelper: + """Helper class for Refresh""" + + +__all__ = ["Refresh"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_show.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_show.py new file mode 100644 index 00000000000..70037baf7cf --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_show.py @@ -0,0 +1,309 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp silo show", + is_preview=True, +) +class Show(AAZCommand): + """Get a specific silo for a Web App. + + :example: Show the resource for the primary app or plan. + az webapp silo show --resource-group MyGroup --name MyApp --silo WestApp + + :example: Show the resource identity. + az webapp silo show --resource-group MyGroup --name MyApp --silo WestApp --query id --output tsv + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/silos/{}", "2026-08-01"], + ] + } + + 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.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="The name of the SiteSilo", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.SiteSiloResourcesGetSiteSilo(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 SiteSiloResourcesGetSiteSilo(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.Web/sites/{name}/silos/{siloName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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.created_time = AAZStrType( + serialized_name="createdTime", + flags={"read_only": True}, + ) + properties.default_host_name = AAZStrType( + serialized_name="defaultHostName", + flags={"read_only": True}, + ) + properties.endpoint_status = AAZStrType( + serialized_name="endpointStatus", + ) + properties.error_code = AAZStrType( + serialized_name="errorCode", + flags={"read_only": True}, + ) + properties.follow_last_known_good = AAZBoolType( + serialized_name="followLastKnownGood", + ) + properties.global_host_name = AAZStrType( + serialized_name="globalHostName", + flags={"read_only": True}, + ) + properties.inbound_ip_address = AAZStrType( + serialized_name="inboundIpAddress", + flags={"read_only": True}, + ) + properties.inbound_ipv6_address = AAZStrType( + serialized_name="inboundIpv6Address", + flags={"read_only": True}, + ) + properties.last_modified_time = AAZStrType( + serialized_name="lastModifiedTime", + flags={"read_only": True}, + ) + properties.monitor_status = AAZStrType( + serialized_name="monitorStatus", + flags={"read_only": True}, + ) + properties.networking_override = AAZObjectType( + serialized_name="networkingOverride", + ) + properties.outbound_ip_addresses = AAZStrType( + serialized_name="outboundIpAddresses", + flags={"read_only": True}, + ) + properties.outbound_ipv6_addresses = AAZStrType( + serialized_name="outboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.possible_inbound_ip_addresses = AAZStrType( + serialized_name="possibleInboundIpAddresses", + flags={"read_only": True}, + ) + properties.possible_inbound_ipv6_addresses = AAZStrType( + serialized_name="possibleInboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.possible_outbound_ip_addresses = AAZStrType( + serialized_name="possibleOutboundIpAddresses", + flags={"read_only": True}, + ) + properties.possible_outbound_ipv6_addresses = AAZStrType( + serialized_name="possibleOutboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.primary_location = AAZStrType( + serialized_name="primaryLocation", + flags={"required": True}, + ) + properties.primary_site_name = AAZStrType( + serialized_name="primarySiteName", + ) + properties.provisioning_details = AAZStrType( + serialized_name="provisioningDetails", + flags={"read_only": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.revision_number = AAZIntType( + serialized_name="revisionNumber", + flags={"read_only": True}, + ) + properties.silo_location = AAZStrType( + serialized_name="siloLocation", + ) + + networking_override = cls._schema_on_200.properties.networking_override + networking_override.vnet_integration_override = AAZStrType( + serialized_name="vnetIntegrationOverride", + ) + + 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-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_update.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_update.py new file mode 100644 index 00000000000..845e9c1d8aa --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_update.py @@ -0,0 +1,543 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp silo update", + is_preview=True, +) +class Update(AAZCommand): + """Update a silo for a Web App. + + :example: Enable the endpoint and follow the last known good revision. + az webapp silo update --resource-group MyGroup --name MyApp --silo WestApp --endpoint-status Enabled --follow-last-known-good true --location westus2 --primary-location eastus --primary-site-name MyApp --networking-override "{vnet-integration-override:/subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/NetworkGroup/providers/Microsoft.Network/virtualNetworks/WestNet/subnets/integration}" + + :example: Update the revision policy by resource ID. + az webapp silo update --ids /subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/MyGroup/providers/Microsoft.Web/sites/MyApp/silos/WestApp --set properties.followLastKnownGood=false --no-wait + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/silos/{}", "2026-08-01"], + ] + } + + AZ_SUPPORT_NO_WAIT = True + + AZ_SUPPORT_GENERIC_UPDATE = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_lro_poller(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="The name of the SiteSilo", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.endpoint_status = AAZStrArg( + options=["--endpoint-status"], + arg_group="Properties", + help="Status of the silo traffic endpoint.", + nullable=True, + ) + _args_schema.follow_last_known_good = AAZBoolArg( + options=["--follow-last-known-good"], + arg_group="Properties", + help="Whether the silo should follow the last known good revision.", + nullable=True, + ) + _args_schema.networking_override = AAZObjectArg( + options=["--networking-override"], + arg_group="Properties", + help="Networking overrides for the silo.", + nullable=True, + ) + _args_schema.primary_location = AAZStrArg( + options=["--primary-location"], + arg_group="Properties", + help="Region of the primary site.", + ) + _args_schema.primary_site_name = AAZStrArg( + options=["--primary-site-name"], + arg_group="Properties", + help="Name of the primary site.", + nullable=True, + ) + _args_schema.location = AAZStrArg( + options=["-l", "--location"], + arg_group="Properties", + help="ARM location of the site silo.", + nullable=True, + ) + + networking_override = cls._args_schema.networking_override + networking_override.vnet_integration_override = AAZStrArg( + options=["vnet-integration-override"], + help="Virtual network subnet resource ID override for the silo.", + nullable=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.SiteSiloResourcesGetSiteSilo(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) + yield self.SiteSiloResourcesCreateOrUpdateSiteSilo(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 SiteSiloResourcesGetSiteSilo(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.Web/sites/{name}/silos/{siloName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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_site_silo_read(cls._schema_on_200) + + return cls._schema_on_200 + + class SiteSiloResourcesCreateOrUpdateSiteSilo(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( + self.ctx.args.no_wait, + session, + self.on_200_201, + self.on_error, + lro_options={"final-state-via": "azure-async-operation"}, + path_format_arguments=self.url_parameters, + ) + if session.http_response.status_code in [200, 201]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200_201, + self.on_error, + lro_options={"final-state-via": "azure-async-operation"}, + 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.Web/sites/{name}/silos/{siloName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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_site_silo_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("endpointStatus", AAZStrType, ".endpoint_status") + properties.set_prop("followLastKnownGood", AAZBoolType, ".follow_last_known_good") + properties.set_prop("networkingOverride", AAZObjectType, ".networking_override") + properties.set_prop("primaryLocation", AAZStrType, ".primary_location", typ_kwargs={"flags": {"required": True}}) + properties.set_prop("primarySiteName", AAZStrType, ".primary_site_name") + properties.set_prop("siloLocation", AAZStrType, ".location") + + networking_override = _builder.get(".properties.networkingOverride") + if networking_override is not None: + networking_override.set_prop("vnetIntegrationOverride", AAZStrType, ".vnet_integration_override") + + 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_site_silo_read = None + + @classmethod + def _build_schema_site_silo_read(cls, _schema): + if cls._schema_site_silo_read is not None: + _schema.id = cls._schema_site_silo_read.id + _schema.name = cls._schema_site_silo_read.name + _schema.properties = cls._schema_site_silo_read.properties + _schema.system_data = cls._schema_site_silo_read.system_data + _schema.type = cls._schema_site_silo_read.type + return + + cls._schema_site_silo_read = _schema_site_silo_read = AAZObjectType() + + site_silo_read = _schema_site_silo_read + site_silo_read.id = AAZStrType( + flags={"read_only": True}, + ) + site_silo_read.name = AAZStrType( + flags={"read_only": True}, + ) + site_silo_read.properties = AAZObjectType() + site_silo_read.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + site_silo_read.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = _schema_site_silo_read.properties + properties.created_time = AAZStrType( + serialized_name="createdTime", + flags={"read_only": True}, + ) + properties.default_host_name = AAZStrType( + serialized_name="defaultHostName", + flags={"read_only": True}, + ) + properties.endpoint_status = AAZStrType( + serialized_name="endpointStatus", + ) + properties.error_code = AAZStrType( + serialized_name="errorCode", + flags={"read_only": True}, + ) + properties.follow_last_known_good = AAZBoolType( + serialized_name="followLastKnownGood", + ) + properties.global_host_name = AAZStrType( + serialized_name="globalHostName", + flags={"read_only": True}, + ) + properties.inbound_ip_address = AAZStrType( + serialized_name="inboundIpAddress", + flags={"read_only": True}, + ) + properties.inbound_ipv6_address = AAZStrType( + serialized_name="inboundIpv6Address", + flags={"read_only": True}, + ) + properties.last_modified_time = AAZStrType( + serialized_name="lastModifiedTime", + flags={"read_only": True}, + ) + properties.monitor_status = AAZStrType( + serialized_name="monitorStatus", + flags={"read_only": True}, + ) + properties.networking_override = AAZObjectType( + serialized_name="networkingOverride", + ) + properties.outbound_ip_addresses = AAZStrType( + serialized_name="outboundIpAddresses", + flags={"read_only": True}, + ) + properties.outbound_ipv6_addresses = AAZStrType( + serialized_name="outboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.possible_inbound_ip_addresses = AAZStrType( + serialized_name="possibleInboundIpAddresses", + flags={"read_only": True}, + ) + properties.possible_inbound_ipv6_addresses = AAZStrType( + serialized_name="possibleInboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.possible_outbound_ip_addresses = AAZStrType( + serialized_name="possibleOutboundIpAddresses", + flags={"read_only": True}, + ) + properties.possible_outbound_ipv6_addresses = AAZStrType( + serialized_name="possibleOutboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.primary_location = AAZStrType( + serialized_name="primaryLocation", + flags={"required": True}, + ) + properties.primary_site_name = AAZStrType( + serialized_name="primarySiteName", + ) + properties.provisioning_details = AAZStrType( + serialized_name="provisioningDetails", + flags={"read_only": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.revision_number = AAZIntType( + serialized_name="revisionNumber", + flags={"read_only": True}, + ) + properties.silo_location = AAZStrType( + serialized_name="siloLocation", + ) + + networking_override = _schema_site_silo_read.properties.networking_override + networking_override.vnet_integration_override = AAZStrType( + serialized_name="vnetIntegrationOverride", + ) + + system_data = _schema_site_silo_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_site_silo_read.id + _schema.name = cls._schema_site_silo_read.name + _schema.properties = cls._schema_site_silo_read.properties + _schema.system_data = cls._schema_site_silo_read.system_data + _schema.type = cls._schema_site_silo_read.type + + +__all__ = ["Update"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_wait.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_wait.py new file mode 100644 index 00000000000..f6c96e4ad1b --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/_wait.py @@ -0,0 +1,310 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp silo wait", + is_preview=True, +) +class Wait(AAZWaitCommand): + """Place the CLI in a waiting state until a condition is met. + + Poll the silo resource, not a saved operation ID. Content replication, endpoint health and DNS convergence require separate observation. + + :example: Wait for Succeeded provisioning state. + az webapp silo wait --resource-group MyGroup --name MyApp --silo WestApp --created --interval 10 --timeout 1800 + + :example: Wait until a deleted silo no longer exists. + az webapp silo wait --resource-group MyGroup --name MyApp --silo WestApp --deleted --interval 10 --timeout 1800 + """ + + _aaz_info = { + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/silos/{}", "2026-08-01"], + ] + } + + 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.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="The name of the SiteSilo", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.SiteSiloResourcesGetSiteSilo(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=False) + return result + + class SiteSiloResourcesGetSiteSilo(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.Web/sites/{name}/silos/{siloName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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.created_time = AAZStrType( + serialized_name="createdTime", + flags={"read_only": True}, + ) + properties.default_host_name = AAZStrType( + serialized_name="defaultHostName", + flags={"read_only": True}, + ) + properties.endpoint_status = AAZStrType( + serialized_name="endpointStatus", + ) + properties.error_code = AAZStrType( + serialized_name="errorCode", + flags={"read_only": True}, + ) + properties.follow_last_known_good = AAZBoolType( + serialized_name="followLastKnownGood", + ) + properties.global_host_name = AAZStrType( + serialized_name="globalHostName", + flags={"read_only": True}, + ) + properties.inbound_ip_address = AAZStrType( + serialized_name="inboundIpAddress", + flags={"read_only": True}, + ) + properties.inbound_ipv6_address = AAZStrType( + serialized_name="inboundIpv6Address", + flags={"read_only": True}, + ) + properties.last_modified_time = AAZStrType( + serialized_name="lastModifiedTime", + flags={"read_only": True}, + ) + properties.monitor_status = AAZStrType( + serialized_name="monitorStatus", + flags={"read_only": True}, + ) + properties.networking_override = AAZObjectType( + serialized_name="networkingOverride", + ) + properties.outbound_ip_addresses = AAZStrType( + serialized_name="outboundIpAddresses", + flags={"read_only": True}, + ) + properties.outbound_ipv6_addresses = AAZStrType( + serialized_name="outboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.possible_inbound_ip_addresses = AAZStrType( + serialized_name="possibleInboundIpAddresses", + flags={"read_only": True}, + ) + properties.possible_inbound_ipv6_addresses = AAZStrType( + serialized_name="possibleInboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.possible_outbound_ip_addresses = AAZStrType( + serialized_name="possibleOutboundIpAddresses", + flags={"read_only": True}, + ) + properties.possible_outbound_ipv6_addresses = AAZStrType( + serialized_name="possibleOutboundIpv6Addresses", + flags={"read_only": True}, + ) + properties.primary_location = AAZStrType( + serialized_name="primaryLocation", + flags={"required": True}, + ) + properties.primary_site_name = AAZStrType( + serialized_name="primarySiteName", + ) + properties.provisioning_details = AAZStrType( + serialized_name="provisioningDetails", + flags={"read_only": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.revision_number = AAZIntType( + serialized_name="revisionNumber", + flags={"read_only": True}, + ) + properties.silo_location = AAZStrType( + serialized_name="siloLocation", + ) + + networking_override = cls._schema_on_200.properties.networking_override + networking_override.vnet_integration_override = AAZStrType( + serialized_name="vnetIntegrationOverride", + ) + + 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 _WaitHelper: + """Helper class for Wait""" + + +__all__ = ["Wait"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/__cmd_group.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/__cmd_group.py new file mode 100644 index 00000000000..1d7762919d0 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/__cmd_group.py @@ -0,0 +1,24 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp silo vnet-integration", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Manage webapp silo vnet-integration resources. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/__init__.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/__init__.py new file mode 100644 index 00000000000..1f70447eedf --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/__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 ._add import * +from ._remove import * +from ._show import * +from ._update import * diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/_add.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/_add.py new file mode 100644 index 00000000000..8cfac5c62a5 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/_add.py @@ -0,0 +1,269 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp silo vnet-integration add", + is_preview=True, +) +class Add(AAZCommand): + """Create a virtual network connection to a silo, or updates the connection properties. + + :example: Connect a site silo to a delegated subnet. + az webapp silo vnet-integration add --resource-group MyGroup --name MyApp --silo WestApp --subnet /subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/NetworkGroup/providers/Microsoft.Network/virtualNetworks/WestNet/subnets/integration + + :example: Set the connection properties explicitly. + az webapp silo vnet-integration add --resource-group MyGroup --name MyApp --silo WestApp --subnet /subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/NetworkGroup/providers/Microsoft.Network/virtualNetworks/WestNet/subnets/integration --swift-supported true --kind app + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/silos/{}/virtualnetworkconnections/virtualnetwork", "2026-08-01"], + ] + } + + 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.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="Name of the silo.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.subnet = AAZStrArg( + options=["--subnet"], + arg_group="Properties", + help="The Virtual Network subnet's resource ID. This is the subnet that this Web App will join. This subnet must have a delegation to Microsoft.Web/serverFarms defined first.", + ) + _args_schema.swift_supported = AAZBoolArg( + options=["--swift-supported"], + arg_group="Properties", + help="A flag that specifies if the scale unit this Web App is on supports Swift integration.", + ) + + # define Arg Group "Resource" + + _args_schema = cls._args_schema + _args_schema.kind = AAZStrArg( + options=["--kind"], + arg_group="Resource", + help="Kind of resource.", + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.SiteSiloVirtualNetworkConnectionsCreateOrUpdateSiteSiloVnetConnection(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 SiteSiloVirtualNetworkConnectionsCreateOrUpdateSiteSiloVnetConnection(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.Web/sites/{name}/silos/{siloName}/virtualNetworkConnections/virtualNetwork", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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("kind", AAZStrType, ".kind") + _builder.set_prop("properties", AAZObjectType, typ_kwargs={"flags": {"client_flatten": True}}) + + properties = _builder.get(".properties") + if properties is not None: + properties.set_prop("subnetResourceId", AAZStrType, ".subnet") + properties.set_prop("swiftSupported", AAZBoolType, ".swift_supported") + + 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.kind = AAZStrType() + _schema_on_200_201.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200_201.properties = AAZObjectType( + flags={"client_flatten": True}, + ) + _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.subnet_resource_id = AAZStrType( + serialized_name="subnetResourceId", + ) + properties.swift_supported = AAZBoolType( + serialized_name="swiftSupported", + ) + + 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 _AddHelper: + """Helper class for Add""" + + +__all__ = ["Add"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/_remove.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/_remove.py new file mode 100644 index 00000000000..12c9b1c254c --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/_remove.py @@ -0,0 +1,156 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp silo vnet-integration remove", + is_preview=True, + confirmation="Are you sure you want to perform this operation?", +) +class Remove(AAZCommand): + """Delete the virtual network connection from a silo. + + :example: Disconnect a site silo from its virtual network. + az webapp silo vnet-integration remove --resource-group MyGroup --name MyApp --silo WestApp --yes + + :example: Disconnect using a silo resource ID. + az webapp silo vnet-integration remove --ids /subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/MyGroup/providers/Microsoft.Web/sites/MyApp/silos/WestApp --yes + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/silos/{}/virtualnetworkconnections/virtualnetwork", "2026-08-01"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return None + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="Name of the silo.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.SiteSiloVirtualNetworkConnectionsDeleteSiteSiloVnetConnection(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + class SiteSiloVirtualNetworkConnectionsDeleteSiteSiloVnetConnection(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + if session.http_response.status_code in [204]: + return self.on_204(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/silos/{siloName}/virtualNetworkConnections/virtualNetwork", + **self.url_parameters + ) + + @property + def method(self): + return "DELETE" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + required=True, + ), + } + return parameters + + def on_200(self, session): + pass + + def on_204(self, session): + pass + + +class _RemoveHelper: + """Helper class for Remove""" + + +__all__ = ["Remove"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/_show.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/_show.py new file mode 100644 index 00000000000..3c210a07c90 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/_show.py @@ -0,0 +1,226 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp silo vnet-integration show", + is_preview=True, +) +class Show(AAZCommand): + """Get the virtual network the silo is connected to. + + :example: Show the resource for the primary app or plan. + az webapp silo vnet-integration show --resource-group MyGroup --name MyApp --silo WestApp + + :example: Show the resource identity. + az webapp silo vnet-integration show --resource-group MyGroup --name MyApp --silo WestApp --query id --output tsv + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/silos/{}/virtualnetworkconnections/virtualnetwork", "2026-08-01"], + ] + } + + 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.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="Name of the silo.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.SiteSiloVirtualNetworkConnectionsGetSiteSiloVnetConnection(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 SiteSiloVirtualNetworkConnectionsGetSiteSiloVnetConnection(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.Web/sites/{name}/silos/{siloName}/virtualNetworkConnections/virtualNetwork", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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.kind = AAZStrType() + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType( + flags={"client_flatten": True}, + ) + _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.subnet_resource_id = AAZStrType( + serialized_name="subnetResourceId", + ) + properties.swift_supported = AAZBoolType( + serialized_name="swiftSupported", + ) + + 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-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/_update.py b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/_update.py new file mode 100644 index 00000000000..c392bc870a9 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/aaz/latest/webapp/silo/vnet_integration/_update.py @@ -0,0 +1,265 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "webapp silo vnet-integration update", + is_preview=True, +) +class Update(AAZCommand): + """Update the properties of a virtual network connection on a silo. + + :example: Update the silo's subnet connection using PATCH. + az webapp silo vnet-integration update --resource-group MyGroup --name MyApp --silo WestApp --subnet /subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/NetworkGroup/providers/Microsoft.Network/virtualNetworks/WestNet/subnets/integration + + :example: Update the supported connection flag. + az webapp silo vnet-integration update --resource-group MyGroup --name MyApp --silo WestApp --swift-supported false --kind app + """ + + _aaz_info = { + "version": "2026-08-01", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.web/sites/{}/silos/{}/virtualnetworkconnections/virtualnetwork", "2026-08-01"], + ] + } + + 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.name = AAZStrArg( + options=["-n", "--name"], + help="Name of the app.", + required=True, + id_part="name", + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + _args_schema.silo = AAZStrArg( + options=["--silo"], + help="Name of the silo.", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[A-Za-z0-9-]{1,24}$", + ), + ) + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.kind = AAZStrArg( + options=["--kind"], + arg_group="Properties", + help="Kind of resource.", + ) + _args_schema.subnet = AAZStrArg( + options=["--subnet"], + arg_group="Properties", + help="The Virtual Network subnet's resource ID. This is the subnet that this Web App will join. This subnet must have a delegation to Microsoft.Web/serverFarms defined first.", + ) + _args_schema.swift_supported = AAZBoolArg( + options=["--swift-supported"], + arg_group="Properties", + help="A flag that specifies if the scale unit this Web App is on supports Swift integration.", + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.SiteSiloVirtualNetworkConnectionsUpdateSiteSiloVnetConnection(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 SiteSiloVirtualNetworkConnectionsUpdateSiteSiloVnetConnection(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.Web/sites/{name}/silos/{siloName}/virtualNetworkConnections/virtualNetwork", + **self.url_parameters + ) + + @property + def method(self): + return "PATCH" + + @property + def error_format(self): + return "ODataV4Format" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "name", self.ctx.args.name, + required=True, + ), + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siloName", self.ctx.args.silo, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-08-01", + 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("kind", AAZStrType, ".kind") + _builder.set_prop("properties", AAZObjectType, typ_kwargs={"flags": {"client_flatten": True}}) + + properties = _builder.get(".properties") + if properties is not None: + properties.set_prop("subnetResourceId", AAZStrType, ".subnet") + properties.set_prop("swiftSupported", AAZBoolType, ".swift_supported") + + return self.serialize_content(_content_value) + + 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.kind = AAZStrType() + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType( + flags={"client_flatten": True}, + ) + _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.subnet_resource_id = AAZStrType( + serialized_name="subnetResourceId", + ) + properties.swift_supported = AAZBoolType( + serialized_name="swiftSupported", + ) + + 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 _UpdateHelper: + """Helper class for Update""" + + +__all__ = ["Update"] diff --git a/src/azure-cli/azure/cli/command_modules/appservice/commands.py b/src/azure-cli/azure/cli/command_modules/appservice/commands.py index cd4aac51c11..ffa659017a6 100644 --- a/src/azure-cli/azure/cli/command_modules/appservice/commands.py +++ b/src/azure-cli/azure/cli/command_modules/appservice/commands.py @@ -17,7 +17,7 @@ validate_functionapp_on_containerapp_site_config_show, validate_functionapp_on_containerapp_container_settings_delete, validate_functionapp_on_containerapp_update, validate_functionapp_on_flex_plan, - validate_functionapp, validate_is_flex_functionapp) + validate_functionapp, validate_is_flex_functionapp, validate_active_backup_ids) def output_slots_in_table(slots): @@ -284,6 +284,34 @@ def _polish_bad_errors(ex, creating_plan): # pylint: disable=too-many-statements def load_command_table(self, _): + from .operations.active_backup import ( + PlanSiloCreate, PlanSiloUpdate, PlanSiloDelete, PlanSiloShow, PlanSiloList, PlanSiloWait, PlanSiloVnetList, + SiteSiloCreate, SiteSiloUpdate, SiteSiloDelete, SiteSiloShow, SiteSiloList, SiteSiloWait, + ) + for name, command in ( + ('appservice plan silo create', PlanSiloCreate), ('appservice plan silo update', PlanSiloUpdate), + ('appservice plan silo delete', PlanSiloDelete), + ('appservice plan silo show', PlanSiloShow), ('appservice plan silo list', PlanSiloList), + ('appservice plan silo wait', PlanSiloWait), + ('appservice plan silo vnet-integration list', PlanSiloVnetList), + ('webapp silo create', SiteSiloCreate), ('webapp silo update', SiteSiloUpdate), + ('webapp silo delete', SiteSiloDelete), + ('webapp silo show', SiteSiloShow), ('webapp silo list', SiteSiloList), ('webapp silo wait', SiteSiloWait), + ): + if name in self.command_table: + self.command_table[name] = command(loader=self) + for name in ( + 'appservice plan silo show', 'appservice plan silo update', 'appservice plan silo delete', + 'appservice plan silo wait', 'appservice plan silo vnet-integration list', + 'webapp silo show', 'webapp silo update', 'webapp silo delete', 'webapp silo wait', + 'webapp silo vnet-integration show', 'webapp silo vnet-integration add', + 'webapp silo vnet-integration update', 'webapp silo vnet-integration remove', + 'webapp silo refresh', 'webapp managed-traffic list', 'webapp managed-traffic show', + 'webapp managed-traffic update', 'webapp last-known-good list', 'webapp last-known-good show', + 'webapp last-known-good mark', + ): + if name in self.command_table: + self.command_table[name].validator = validate_active_backup_ids webclient_sdk = CliCommandType( operations_tmpl='azure.mgmt.web.operations#WebSiteManagementClientOperationsMixin.{}', client_factory=cf_web_client diff --git a/src/azure-cli/azure/cli/command_modules/appservice/operations/__init__.py b/src/azure-cli/azure/cli/command_modules/appservice/operations/__init__.py new file mode 100644 index 00000000000..34913fb394d --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/operations/__init__.py @@ -0,0 +1,4 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- diff --git a/src/azure-cli/azure/cli/command_modules/appservice/operations/active_backup.py b/src/azure-cli/azure/cli/command_modules/appservice/operations/active_backup.py new file mode 100644 index 00000000000..1452e1e92d9 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/operations/active_backup.py @@ -0,0 +1,319 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +"""Compatibility for silo resource envelopes, origin reads and async responses.""" + +from functools import partial +from urllib.parse import unquote, urlsplit + +from azure.core.exceptions import ResourceExistsError +from azure.cli.core.aaz import AAZStrType +from azure.cli.core.azclierror import AzureResponseError, InvalidArgumentValueError +from knack.log import get_logger + +from ..aaz.latest.appservice.plan.silo import ( + Create as _PlanCreate, Update as _PlanUpdate, Delete as _PlanDelete, + Show as _PlanShow, List as _PlanList, Wait as _PlanWait, +) +from ..aaz.latest.appservice.plan.silo.vnet_integration import List as _PlanVnetList +from ..aaz.latest.webapp.silo import ( + Create as _SiteCreate, Update as _SiteUpdate, Delete as _SiteDelete, + Show as _SiteShow, List as _SiteList, Wait as _SiteWait, +) + + +logger = get_logger(__name__) + + +def _prefer_location_polling(response, hook=None): + if hook: + hook(response) + headers = response.http_response.headers + # Silo async-status reads return empty 202s; suppress that header so the SDK uses + # the provided Location monitor. Remove once Azure-AsyncOperation returns status JSON. + if headers.get("Location"): + headers.pop("Azure-AsyncOperation", None) + + +def _read_origin_from_collection(operation, schema_builder): + ctx = operation.ctx + if ctx.args.silo.to_serialized_data().casefold() != "origin": + return False + # Origin point GET can return 404 while the collection contains it. Remove this + # fallback once point GET is fixed; keep generated paging and response schemas. + resources = SiteSiloList(cli_ctx=ctx.cli_ctx)(command_args={ + "subscription": ctx.subscription_id, + "resource_group": ctx.args.resource_group.to_serialized_data(), + "name": ctx.args.name.to_serialized_data(), + }) + resource_id = unquote(urlsplit(operation.url).path).casefold() + matches = [ + resource for resource in resources + if isinstance(resource, dict) and isinstance(resource.get("id"), str) and + unquote(resource["id"]).casefold() == resource_id + ] + if not matches: + return False + if len(matches) != 1: + raise AzureResponseError("ARM returned duplicate origin resources in the silo collection.") + resource = matches[0] + resource_type = resource.get("type") + if (not isinstance(resource_type, str) or resource_type.casefold() != "microsoft.web/sites/silos" or + not isinstance(resource.get("properties"), dict)): + raise AzureResponseError("ARM returned an invalid origin resource in the silo collection.") + ctx.set_var("instance", resource, schema_builder=schema_builder) + logger.warning( + "Origin point GET returned HTTP 404; using the matching origin from the silo collection. " + "Stamp-derived IP fields may be absent.") + return True + + +def _include_location(schema): + if not hasattr(schema, "location"): + schema.location = AAZStrType() + return schema + + +def _same_location(left, right): + return left.replace(" ", "").casefold() == right.replace(" ", "").casefold() + + +def _silo_location(content): + locations = [ + content.get("location"), + content.get("properties", {}).get("siloLocation"), + ] + locations = [value for value in locations if value is not None] + if not locations or any(not isinstance(value, str) or not value.strip() for value in locations): + raise AzureResponseError("The silo resource does not include a usable location.") + if any(not _same_location(locations[0], value) for value in locations[1:]): + raise AzureResponseError("The silo resource contains conflicting location fields.") + return locations[0] + + +def _create_content(content): + content["location"] = _silo_location(content) + return content + + +def _prepare_update(ctx, instance): + location = _silo_location(instance.to_serialized_data()) + ctx.set_var("original_silo_location", location, schema_builder=AAZStrType) + instance.location = location + + +def _complete_update(ctx, instance): + original = ctx.vars.original_silo_location.to_serialized_data() + content = instance.to_serialized_data() + location = content.get("location") + nested = content.get("properties", {}).get("siloLocation") + if (not isinstance(location, str) or not _same_location(location, original) or + (nested is not None and (not isinstance(nested, str) or not _same_location(nested, original)))): + raise InvalidArgumentValueError("The location of an existing silo cannot be changed or removed.") + instance.location = original + + +class PlanSiloCreate(_PlanCreate): + class ServerFarmSiloResourcesCreateOrUpdateServerFarmSilo( + _PlanCreate.ServerFarmSiloResourcesCreateOrUpdateServerFarmSilo): + _schema_on_200_201 = None + + def __call__(self, *args, **kwargs): + kwargs["raw_response_hook"] = partial(_prefer_location_polling, hook=kwargs.get("raw_response_hook")) + return super().__call__(*args, **kwargs) + + @property + def content(self): + return _create_content(super().content) + + @classmethod + def _build_schema_on_200_201(cls): + return _include_location(super()._build_schema_on_200_201()) + + +class PlanSiloUpdate(_PlanUpdate): + def pre_instance_update(self, instance): + _prepare_update(self.ctx, instance) + + def post_instance_update(self, instance): + _complete_update(self.ctx, instance) + + class ServerFarmSiloResourcesGetServerFarmSilo(_PlanUpdate.ServerFarmSiloResourcesGetServerFarmSilo): + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + return _include_location(super()._build_schema_on_200()) + + class ServerFarmSiloResourcesCreateOrUpdateServerFarmSilo( + _PlanUpdate.ServerFarmSiloResourcesCreateOrUpdateServerFarmSilo): + _schema_on_200_201 = None + + def __call__(self, *args, **kwargs): + kwargs["raw_response_hook"] = partial(_prefer_location_polling, hook=kwargs.get("raw_response_hook")) + return super().__call__(*args, **kwargs) + + @classmethod + def _build_schema_on_200_201(cls): + return _include_location(super()._build_schema_on_200_201()) + + +class PlanSiloDelete(_PlanDelete): + class ServerFarmSiloResourcesDeleteServerFarmSilo(_PlanDelete.ServerFarmSiloResourcesDeleteServerFarmSilo): + def __call__(self, *args, **kwargs): + kwargs["raw_response_hook"] = partial(_prefer_location_polling, hook=kwargs.get("raw_response_hook")) + return super().__call__(*args, **kwargs) + + def on_error(self, response): + if response.status_code == 409: + # Core's 409 mapping skips AAZ's case-insensitive formatter for App Service Code/Message fields. + raise ResourceExistsError(response=response, error_format=self.ctx.get_error_format(self.error_format)) + return super().on_error(response) + + +class PlanSiloShow(_PlanShow): + class ServerFarmSiloResourcesGetServerFarmSilo(_PlanShow.ServerFarmSiloResourcesGetServerFarmSilo): + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + return _include_location(super()._build_schema_on_200()) + + +class PlanSiloList(_PlanList): + class ServerFarmSiloResourcesListServerFarmSilos(_PlanList.ServerFarmSiloResourcesListServerFarmSilos): + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + schema = super()._build_schema_on_200() + _include_location(schema.value.Element) + return schema + + +class PlanSiloWait(_PlanWait): + class ServerFarmSiloResourcesGetServerFarmSilo(_PlanWait.ServerFarmSiloResourcesGetServerFarmSilo): + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + return _include_location(super()._build_schema_on_200()) + + +class PlanSiloVnetList(_PlanVnetList): + class ServerFarmSiloResourcesGetServerFarmSiloVnetIntegrations( + _PlanVnetList.ServerFarmSiloResourcesGetServerFarmSiloVnetIntegrations): + def deserialize_http_content(self, session): + data = super().deserialize_http_content(session) + if isinstance(data, list) and all(isinstance(item, dict) for item in data): + return data + # The draft spec declares an array, but the service wraps subnet properties + # in an ARM envelope. Remove this adapter after the contract is regenerated. + resource_type = data.get("type") if isinstance(data, dict) else None + properties = data.get("properties") if isinstance(data, dict) else None + if (not isinstance(resource_type, str) or + resource_type.casefold() != "microsoft.web/serverfarms/virtualnetworkintegrations" or + not isinstance(properties, list) or any( + not isinstance(item, dict) or not isinstance(item.get("subnetResourceId"), str) or + not item["subnetResourceId"].strip() for item in properties)): + raise AzureResponseError("The plan-silo VNet integration API returned an unexpected response shape.") + logger.warning( + "Plan-silo VNet integrations returned an ARM properties array; using its subnet entries " + "until the service specification is corrected.") + return [{"properties": item} for item in properties] + + +class SiteSiloCreate(_SiteCreate): + class SiteSiloResourcesCreateOrUpdateSiteSilo(_SiteCreate.SiteSiloResourcesCreateOrUpdateSiteSilo): + _schema_on_200_201 = None + + def __call__(self, *args, **kwargs): + kwargs["raw_response_hook"] = partial(_prefer_location_polling, hook=kwargs.get("raw_response_hook")) + return super().__call__(*args, **kwargs) + + @property + def content(self): + return _create_content(super().content) + + @classmethod + def _build_schema_on_200_201(cls): + return _include_location(super()._build_schema_on_200_201()) + + +class SiteSiloUpdate(_SiteUpdate): + def pre_instance_update(self, instance): + _prepare_update(self.ctx, instance) + + def post_instance_update(self, instance): + _complete_update(self.ctx, instance) + + class SiteSiloResourcesGetSiteSilo(_SiteUpdate.SiteSiloResourcesGetSiteSilo): + _schema_on_200 = None + + def on_error(self, response): + if response.status_code == 404 and _read_origin_from_collection(self, self._build_schema_on_200): + return None + return super().on_error(response) + + @classmethod + def _build_schema_on_200(cls): + return _include_location(super()._build_schema_on_200()) + + class SiteSiloResourcesCreateOrUpdateSiteSilo(_SiteUpdate.SiteSiloResourcesCreateOrUpdateSiteSilo): + _schema_on_200_201 = None + + def __call__(self, *args, **kwargs): + kwargs["raw_response_hook"] = partial(_prefer_location_polling, hook=kwargs.get("raw_response_hook")) + return super().__call__(*args, **kwargs) + + @classmethod + def _build_schema_on_200_201(cls): + return _include_location(super()._build_schema_on_200_201()) + + +class SiteSiloDelete(_SiteDelete): + class SiteSiloResourcesDeleteSiteSilo(_SiteDelete.SiteSiloResourcesDeleteSiteSilo): + def __call__(self, *args, **kwargs): + kwargs["raw_response_hook"] = partial(_prefer_location_polling, hook=kwargs.get("raw_response_hook")) + return super().__call__(*args, **kwargs) + + +class SiteSiloShow(_SiteShow): + class SiteSiloResourcesGetSiteSilo(_SiteShow.SiteSiloResourcesGetSiteSilo): + _schema_on_200 = None + + def on_error(self, response): + if response.status_code == 404 and _read_origin_from_collection(self, self._build_schema_on_200): + return None + return super().on_error(response) + + @classmethod + def _build_schema_on_200(cls): + return _include_location(super()._build_schema_on_200()) + + +class SiteSiloList(_SiteList): + class SiteSiloResourcesListSiteSilos(_SiteList.SiteSiloResourcesListSiteSilos): + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + schema = super()._build_schema_on_200() + _include_location(schema.value.Element) + return schema + + +class SiteSiloWait(_SiteWait): + class SiteSiloResourcesGetSiteSilo(_SiteWait.SiteSiloResourcesGetSiteSilo): + _schema_on_200 = None + + def on_error(self, response): + if response.status_code == 404 and _read_origin_from_collection(self, self._build_schema_on_200): + return None + return super().on_error(response) + + @classmethod + def _build_schema_on_200(cls): + return _include_location(super()._build_schema_on_200()) diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/active_backup_command_catalog.json b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/active_backup_command_catalog.json new file mode 100644 index 00000000000..8facd80ee76 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/active_backup_command_catalog.json @@ -0,0 +1,30 @@ +{ + "apiVersion": "2026-08-01", + "specCommit": "33ee07b2b3d377b58c4d1a1c5be7e2339f43b879", + "commands": [ + {"command": "appservice plan silo list", "key": "C01", "methods": ["GET"], "path": "/serverfarms/{name}/silos"}, + {"command": "appservice plan silo show", "key": "C02", "methods": ["GET"], "path": "/serverfarms/{name}/silos/{silo}"}, + {"command": "appservice plan silo create", "key": "C03", "methods": ["PUT"], "path": "/serverfarms/{name}/silos/{silo}"}, + {"command": "appservice plan silo update", "key": "C04", "methods": ["GET", "PUT"], "path": "/serverfarms/{name}/silos/{silo}"}, + {"command": "appservice plan silo delete", "key": "C05", "methods": ["DELETE"], "path": "/serverfarms/{name}/silos/{silo}"}, + {"command": "appservice plan silo wait", "methods": ["GET"], "path": "/serverfarms/{name}/silos/{silo}"}, + {"command": "appservice plan silo vnet-integration list", "key": "C07", "methods": ["GET"], "path": "/serverfarms/{name}/silos/{silo}/virtualNetworkIntegrations"}, + {"command": "webapp silo list", "key": "C08", "methods": ["GET"], "path": "/sites/{name}/silos"}, + {"command": "webapp silo show", "key": "C09", "methods": ["GET"], "path": "/sites/{name}/silos/{silo}"}, + {"command": "webapp silo create", "key": "C10", "methods": ["PUT"], "path": "/sites/{name}/silos/{silo}"}, + {"command": "webapp silo update", "key": "C11", "methods": ["GET", "PUT"], "path": "/sites/{name}/silos/{silo}"}, + {"command": "webapp silo delete", "key": "C12", "methods": ["DELETE"], "path": "/sites/{name}/silos/{silo}"}, + {"command": "webapp silo wait", "methods": ["GET"], "path": "/sites/{name}/silos/{silo}"}, + {"command": "webapp silo refresh", "key": "C14", "methods": ["POST"], "path": "/sites/{name}/refreshSilos"}, + {"command": "webapp last-known-good list", "key": "C15", "methods": ["GET"], "path": "/sites/{name}/lastKnownGood"}, + {"command": "webapp last-known-good show", "key": "C16", "methods": ["GET"], "path": "/sites/{name}/lastKnownGood/default"}, + {"command": "webapp last-known-good mark", "key": "C17", "methods": ["POST"], "path": "/sites/{name}/lastKnownGood/default/mark"}, + {"command": "webapp managed-traffic list", "key": "C18", "methods": ["GET"], "path": "/sites/{name}/managedTrafficConfig"}, + {"command": "webapp managed-traffic show", "key": "C19", "methods": ["GET"], "path": "/sites/{name}/managedTrafficConfig/default"}, + {"command": "webapp managed-traffic update", "key": "C20", "methods": ["GET", "PUT"], "path": "/sites/{name}/managedTrafficConfig/default"}, + {"command": "webapp silo vnet-integration show", "key": "C22", "methods": ["GET"], "path": "/sites/{name}/silos/{silo}/virtualNetworkConnections/virtualNetwork"}, + {"command": "webapp silo vnet-integration add", "key": "C23", "methods": ["PUT"], "path": "/sites/{name}/silos/{silo}/virtualNetworkConnections/virtualNetwork"}, + {"command": "webapp silo vnet-integration update", "key": "C24", "methods": ["PATCH"], "path": "/sites/{name}/silos/{silo}/virtualNetworkConnections/virtualNetwork"}, + {"command": "webapp silo vnet-integration remove", "key": "C25", "methods": ["DELETE"], "path": "/sites/{name}/silos/{silo}/virtualNetworkConnections/virtualNetwork"} + ] +} diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_active_backup_aaz.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_active_backup_aaz.py new file mode 100644 index 00000000000..1551ca10960 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_active_backup_aaz.py @@ -0,0 +1,897 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from contextlib import ExitStack, redirect_stderr, redirect_stdout +import io +import json +import os +from pathlib import Path +import shlex +import tempfile +import unittest +from unittest.mock import patch +from urllib.parse import parse_qs, urlsplit + +from azure.core.credentials import AccessToken +from azure.core.pipeline.transport import HttpRequest, HttpResponse +from requests.structures import CaseInsensitiveDict + +from azure.cli.core import get_default_cli +from azure.cli.core._profile import Profile +from azure.cli.command_modules.appservice import COMMAND_LOADER_CLS + + +CATALOG = json.loads(Path(__file__).with_name("active_backup_command_catalog.json").read_text(encoding="utf-8")) +API_VERSION = CATALOG["apiVersion"] +COMMANDS = CATALOG["commands"] +SUBSCRIPTION = "00000000-0000-0000-0000-000000000001" +RESOURCE_ROOT = "/subscriptions/{}/resourceGroups/group/providers/Microsoft.Web".format(SUBSCRIPTION) +SUBNET = RESOURCE_ROOT.replace("Microsoft.Web", "Microsoft.Network") + "/virtualNetworks/net/subnets/integration" +ORIGIN = RESOURCE_ROOT + "/sites/primary/silos/origin" + + +class _Credential: + def get_token(self, *_args, **_kwargs): + return AccessToken("offline-active-backup-test", 9999999999) + + +class _Response(HttpResponse): + def __init__(self, request, status, payload=None, headers=None): + super().__init__(request, None) + self.status_code = status + self.reason = "Conflict" if status == 409 else "Offline test response" + self.headers = CaseInsensitiveDict({"content-type": "application/json", **(headers or {})}) + self.content_type = "application/json" + self._body = b"" if payload is None else json.dumps(payload).encode("utf-8") + + def body(self): + return self._body + + +def _resource(path): + result = {"id": path, "name": path.rsplit("/", 1)[-1], "properties": {}} + if "/managedTrafficConfig/" in path: + result["type"] = "Microsoft.Web/sites/managedTrafficConfig" + result["properties"] = { + "trafficMode": "Active", + "endpoints": [{"region": "eastus", "weight": 700}, {"region": "westus2", "weight": 300}], + } + elif "/lastKnownGood/" in path: + result["type"] = "Microsoft.Web/sites/lastKnownGood" + result["properties"] = {"revisionNumber": 7, "siteName": "primary"} + elif "/virtualNetworkConnections/" in path: + result["type"] = "Microsoft.Web/sites/silos/virtualNetworkConnections" + result["properties"] = {"subnetResourceId": SUBNET, "swiftSupported": True} + else: + plan = "/serverfarms/" in path + result["type"] = "Microsoft.Web/{}/silos".format("serverfarms" if plan else "sites") + result["location"] = "eastus" if path.casefold().endswith("/silos/origin") else "westus2" + result["properties"] = { + "primaryLocation": "eastus", "provisioningState": "Succeeded", + "primaryServerFarmName" if plan else "primarySiteName": "primary", + } + if plan: + result["properties"].update(instanceNumberOverride=1, zoneRedundantOverride=True) + else: + result["properties"].update( + trafficMode="Active", endpointStatus="Enabled", followLastKnownGood=False, revisionNumber=7, + defaultHostName="secondary.example.test", + ) + return result + + +class ActiveBackupAAZTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + root = Path(__file__).resolve().parents[8] + cache = root / ".x" / "active-backup" / "offline-cli" + cache.mkdir(parents=True, exist_ok=True) + cls.directory = tempfile.TemporaryDirectory(dir=cache) + cls.isolation = ExitStack() + cls.isolation.enter_context(patch.dict(os.environ, { + "AZURE_CONFIG_DIR": cls.directory.name, + "AZURE_EXTENSION_DIR": str(Path(cls.directory.name) / "extensions"), + "AZURE_EXTENSION_USE_DYNAMIC_INSTALL": "no", + "AZURE_CORE_COLLECT_TELEMETRY": "false", + })) + cls.isolation.enter_context(patch("azure.cli.core._config.GLOBAL_CONFIG_DIR", cls.directory.name)) + cls.isolation.enter_context(patch( + "azure.cli.core.extension.EXTENSIONS_DIR", str(Path(cls.directory.name) / "extensions"))) + cls.isolation.enter_context(patch( + "azure.cli.core.extension.EXTENSIONS_SYS_DIR", str(Path(cls.directory.name) / "system-extensions"))) + cls.isolation.enter_context(patch("azure.cli.core.extension.DEV_EXTENSION_SOURCES", [])) + + @classmethod + def tearDownClass(cls): + cls.isolation.close() + cls.directory.cleanup() + + def setUp(self): + self.calls = [] + self.async_operation = False + self.operation_status = "Succeeded" + self.location_operation = False + self.location_responses = [] + self.operation_method = None + self.http_status = None + self.paginate = False + self.reads = [] + self.responses = [] + self.cloud = None + self.stack = ExitStack() + self.addCleanup(self.stack.close) + self.stack.enter_context(patch("socket.socket.connect", side_effect=AssertionError("Unexpected network"))) + self.stack.enter_context(patch("socket.create_connection", side_effect=AssertionError("Unexpected network"))) + self.stack.enter_context(patch("azure.cli.core.util.handle_version_update")) + self.stack.enter_context(patch("azure.cli.core.util.check_connectivity", return_value=False)) + self.stack.enter_context(patch.object( + Profile, "get_subscription", + return_value={"id": SUBSCRIPTION, "tenantId": SUBSCRIPTION, "name": "Offline"}, + )) + self.stack.enter_context(patch.object( + Profile, "get_login_credentials", return_value=(_Credential(), SUBSCRIPTION, SUBSCRIPTION), + )) + self.stack.enter_context(patch("azure.core.pipeline.transport.RequestsTransport.send", + autospec=True, side_effect=self.send)) + self.stack.enter_context(patch("time.sleep")) + + def send(self, _transport, request, **_kwargs): + url = urlsplit(request.url) + query = parse_qs(url.query) + self.assertEqual(query.get("api-version"), [API_VERSION]) + body = request.body + if isinstance(body, bytes): + body = body.decode("utf-8") + body = json.loads(body) if body else None + self.calls.append({"method": request.method, "url": request.url, "path": url.path, "body": body}) + if self.http_status: + return _Response(request, self.http_status, { + "error": {"code": "FixtureError", "message": "The service rejected this operation."}, + }) + if self.responses: + status, payload = self.responses.pop(0) + return _Response(request, status, payload) + if url.path.endswith("/operationresults/test-operation"): + if self.location_operation: + self.assertNotIn("azureAsyncOperation", query) + status = self.location_responses.pop(0) + if status == 202: + return _Response(request, 202, headers={"Retry-After": "0"}) + if status != 200: + return _Response(request, status, { + "error": {"code": "FixtureFailure", "message": "The operation failed."}, + }) + payload = (None if self.operation_method == "DELETE" else + _resource(url.path.rsplit("/operationresults/", 1)[0])) + return _Response(request, 200, payload) + return _Response(request, 200, { + "status": self.operation_status, + **({"error": {"code": "FixtureFailure", "message": "The operation failed."}} + if self.operation_status == "Failed" else {}), + }, {"Retry-After": "0"}) + if self.async_operation and request.method in ("PUT", "DELETE"): + self.operation_method = request.method + operation_url = (request.url.split("?", 1)[0] + + "/operationresults/test-operation?api-version=" + API_VERSION) + headers = {"Azure-AsyncOperation": operation_url + "&azureAsyncOperation=1", "Retry-After": "0"} + if self.location_operation: + headers["Location"] = operation_url + return _Response(request, 202, {"status": "InProgress"}, headers) + if request.method == "POST" and url.path.endswith("/refreshSilos"): + return _Response(request, 204) + if request.method == "DELETE": + return _Response(request, 204) + if request.method == "GET" and url.path.endswith(("/silos", "/managedTrafficConfig", "/lastKnownGood")): + payload = {"value": [_resource(url.path + ("/backup" if url.path.endswith("/silos") else "/default"))]} + if self.paginate and "page" not in query: + payload["nextLink"] = request.url + "&page=2" + return _Response(request, 200, payload) + if request.method == "GET" and url.path.endswith("/virtualNetworkIntegrations"): + return _Response(request, 200, [{"properties": {"subnetResourceId": SUBNET}}]) + payload = self.reads.pop(0) if self.reads and request.method == "GET" else _resource(url.path) + if body: + payload["properties"].update(body.get("properties", {})) + if "location" in body: + payload["location"] = body["location"] + return _Response(request, 200, payload) + + def invoke(self, arguments, expected=0, location_default=None): + stdout, stderr = io.StringIO(), io.StringIO() + cli = get_default_cli() + if self.cloud is not None: + cli.cloud = self.cloud + if location_default: + cli.config.set_value("defaults", "location", location_default) + with redirect_stdout(stdout), redirect_stderr(stderr): + try: + code = cli.invoke(arguments, out_file=stdout) + except SystemExit as ex: + code = ex.code + self.assertEqual(code, expected, "{}\n{}\n{}".format( + arguments, stderr.getvalue(), getattr(cli.result, "error", None))) + output_format = "json" + for flag in ("--output", "-o"): + if flag in arguments: + output_format = arguments[arguments.index(flag) + 1] + if stdout.getvalue().strip() and "--help" not in arguments and output_format == "json": + return json.loads(stdout.getvalue()) + return stdout.getvalue() + + @staticmethod + def arguments(entry): + command = entry["command"] + args = command.split() + ["-g", "group", "-n", "primary", "--output", "json"] + if "{silo}" in entry["path"]: + args += ["--silo", "backup"] + if command.endswith((" delete", " remove")): + args += ["--yes"] + if command.endswith("silo create"): + args += ["--location", "westus2", "--primary-location", "eastus"] + args += (["--number-of-workers", "2", "--zone-redundant", "false"] + if command.startswith("appservice") else + ["--traffic-mode", "Active", "--follow-last-known-good", "false"]) + if command.endswith("silo update"): + args += (["--number-of-workers", "2", "--zone-redundant", "false"] + if command.startswith("appservice") else + ["--endpoint-status", "Disabled", "--follow-last-known-good", "false"]) + if command == "webapp managed-traffic update": + args += ["--traffic-mode", "Passive"] + if command.endswith(("vnet-integration add", "vnet-integration update")): + args += ["--subnet", SUBNET] + if command.endswith(" wait"): + args += ["--exists", "--interval", "1", "--timeout", "2"] + return args + + def test_appservice_active_backup_inventory_is_spec_limited_and_preview(self): + table = COMMAND_LOADER_CLS(cli_ctx=get_default_cli()).load_command_table(None) + prefixes = ("appservice plan silo ", "webapp silo ", "webapp managed-traffic ", "webapp last-known-good ") + actual = {name for name in table if name.startswith(prefixes)} + self.assertEqual(actual, {entry["command"] for entry in COMMANDS}) + self.assertEqual(len(actual), 24) + for command in actual: + self.assertIsNotNone(table[command].preview_info, command) + + def test_appservice_active_backup_all_help_loads(self): + for entry in COMMANDS: + with self.subTest(command=entry["command"]): + output = self.invoke(entry["command"].split() + ["--help"]) + self.assertIn("Examples", output) + self.assertEqual(self.calls, []) + + def test_appservice_active_backup_examples_parse_and_execute(self): + table = COMMAND_LOADER_CLS(cli_ctx=get_default_cli()).load_command_table(None) + for entry in COMMANDS: + command = table[entry["command"]] + examples = command.help.get("examples", []) + self.assertGreaterEqual(len(examples), 2, entry["command"]) + for example in examples: + with self.subTest(command=entry["command"], example=example["name"]): + arguments = shlex.split(example["text"])[1:] + arguments = [arg.replace("MyGroup", "group").replace("MyPlan", "primary") + .replace("MyApp", "primary").replace("WestPlan", "backup") + .replace("WestApp", "backup") for arg in arguments] + if "--deleted" in arguments: + self.http_status = 404 + result = self.invoke(arguments) + if entry["command"] == "appservice plan silo vnet-integration list" and "--query" in arguments: + self.assertEqual(result, [SUBNET]) + self.http_status = None + + def test_appservice_active_backup_exact_spec_routes(self): + expected = set() + actual = set() + for entry in COMMANDS: + with self.subTest(command=entry["command"]): + self.calls.clear() + self.invoke(self.arguments(entry)) + path = RESOURCE_ROOT + entry["path"].format(name="primary", silo="backup") + self.assertEqual([(call["method"], call["path"]) for call in self.calls], + [(method, path) for method in entry["methods"]]) + expected.update((method, path) for method in entry["methods"]) + actual.update((call["method"], call["path"]) for call in self.calls) + self.assertEqual(actual, expected) + self.assertEqual(len(actual), 20) + + def test_appservice_active_backup_create_adds_arm_location_and_keeps_generated_fields(self): + for entry in COMMANDS: + if not entry["command"].endswith("silo create"): + continue + self.calls.clear() + output = self.invoke(self.arguments(entry)) + body = self.calls[0]["body"] + self.assertEqual(body["location"], "westus2") + self.assertEqual(output["location"], "westus2") + self.assertEqual(body["properties"]["siloLocation"], "westus2") + self.assertEqual(body["properties"]["primaryLocation"], "eastus") + field = "zoneRedundantOverride" if entry["command"].startswith("appservice") else "followLastKnownGood" + self.assertIs(body["properties"][field], False) + + def test_appservice_active_backup_updates_preserve_fields_without_readonly_roundtrip(self): + for family, flag, value in ( + ("appservice plan", "--number-of-workers", "2"), ("webapp", "--endpoint-status", "Disabled"), + ): + self.calls.clear() + self.invoke(family.split() + [ + "silo", "update", "-g", "group", "-n", "primary", "--silo", "backup", flag, value, + ]) + props = self.calls[-1]["body"]["properties"] + self.assertEqual(self.calls[-1]["body"]["location"], "westus2") + self.assertEqual(props["primaryLocation"], "eastus") + self.assertNotIn("provisioningState", props) + self.assertNotIn("revisionNumber", props) + if family == "webapp": + self.assertIs(props["followLastKnownGood"], False) + + def test_appservice_active_backup_async_and_no_wait(self): + self.async_operation = True + for entry in COMMANDS: + if not entry["command"].endswith("silo create"): + continue + for no_wait in (False, True): + self.calls.clear() + args = self.arguments(entry) + (["--no-wait"] if no_wait else []) + output = self.invoke(args) + self.assertEqual(len(self.calls), 1 if no_wait else 3) + if no_wait: + self.assertEqual(output, "") + else: + self.assertEqual(output["id"], self.calls[0]["path"]) + self.assertIn("/operationresults/test-operation", self.calls[1]["path"]) + self.assertEqual(self.calls[2]["path"], self.calls[0]["path"]) + + def test_appservice_active_backup_async_failure_is_not_success(self): + self.async_operation = True + self.operation_status = "Failed" + self.invoke([ + "webapp", "silo", "create", "-g", "group", "-n", "primary", "--silo", "backup", + "--location", "westus2", "--primary-location", "eastus", + ], expected=1) + self.assertEqual(len(self.calls), 2) + + def test_appservice_active_backup_synchronous_no_wait_has_no_receipt(self): + for entry in COMMANDS: + if entry["command"].endswith("silo create"): + self.calls.clear() + self.assertEqual(self.invoke(self.arguments(entry) + ["--no-wait"]), "") + self.assertEqual(len(self.calls), 1) + + def test_appservice_active_backup_async_delete_has_no_resource_readback(self): + self.async_operation = True + for entry in COMMANDS: + if not entry["command"].endswith("silo delete"): + continue + for no_wait in (False, True): + self.calls.clear() + result = self.invoke(self.arguments(entry) + (["--no-wait"] if no_wait else [])) + self.assertEqual(result, "") + self.assertEqual(len(self.calls), 1 if no_wait else 2) + self.assertEqual(self.calls[0]["method"], "DELETE") + if not no_wait: + self.assertTrue(self.calls[1]["path"].endswith("/operationresults/test-operation")) + + def test_appservice_active_backup_plan_delete_preserves_standard_and_legacy_conflict_details(self): + message = "Cannot delete this plan silo because it has site silos associated with it. Delete site silos first." + for payload in ( + {"error": {"code": "Conflict", "message": message}}, + {"Code": "Conflict", "Message": message}, + {"Error": {"Code": "Conflict", "Message": message}}, + ): + with self.subTest(payload=payload): + self.calls.clear() + self.responses = [(409, payload)] + with self.assertLogs("cli.azure.cli.core.azclierror", level="ERROR") as logged: + self.invoke([ + "appservice", "plan", "silo", "delete", "-g", "group", "-n", "primary", + "--silo", "backup", "--yes", + ], expected=1) + output = "\n".join(logged.output) + self.assertIn(message, output) + self.assertNotIn("invalid status", output) + self.assertEqual([call["method"] for call in self.calls], ["DELETE"]) + + def test_appservice_active_backup_plan_delete_does_not_invent_dependency_errors(self): + message = "Another operation is in progress." + self.responses = [(409, {"Code": "Conflict", "Message": message})] + with self.assertLogs("cli.azure.cli.core.azclierror", level="ERROR") as logged: + self.invoke([ + "appservice", "plan", "silo", "delete", "-g", "group", "-n", "primary", + "--silo", "backup", "--yes", + ], expected=1) + output = "\n".join(logged.output) + self.assertIn(message, output) + self.assertNotIn("site silo", output) + self.assertEqual(len(self.calls), 1) + + def test_appservice_active_backup_plan_delete_unstructured_conflicts_still_fail(self): + for payload in (None, {"unexpected": "value"}, "not an error object"): + with self.subTest(payload=payload): + self.calls.clear() + self.responses = [(409, payload)] + with self.assertLogs("cli.azure.cli.core.azclierror", level="ERROR") as logged: + self.invoke([ + "appservice", "plan", "silo", "delete", "-g", "group", "-n", "primary", + "--silo", "backup", "--yes", + ], expected=1) + output = "\n".join(logged.output) + self.assertIn("Operation returned an invalid status 'Conflict'", output) + self.assertNotIn("site silo", output) + self.assertEqual(len(self.calls), 1) + + def test_appservice_active_backup_plan_delete_keeps_conflict_exception_type_and_status(self): + from types import SimpleNamespace + from azure.core.exceptions import ResourceExistsError + from azure.cli.core.aaz._command_ctx import AAZCommandCtx + from azure.cli.command_modules.appservice.operations.active_backup import PlanSiloDelete + operation = PlanSiloDelete.ServerFarmSiloResourcesDeleteServerFarmSilo(ctx=SimpleNamespace( + get_http_client=lambda _kind: None, get_error_format=AAZCommandCtx.get_error_format, + )) + response = _Response(HttpRequest("DELETE", "https://management.azure.com/example"), 409, { + "Code": "Conflict", "Message": "Service-provided failure.", + }) + with self.assertRaises(ResourceExistsError) as caught: + operation.on_error(response) + self.assertEqual(caught.exception.status_code, 409) + self.assertIs(caught.exception.response, response) + self.assertEqual(caught.exception.error.message, "Service-provided failure.") + + def test_appservice_active_backup_resource_waits(self): + path = RESOURCE_ROOT + "/sites/primary/silos/backup" + pending = _resource(path) + pending["properties"]["provisioningState"] = "Creating" + self.reads = [pending, _resource(path)] + result = self.invoke([ + "webapp", "silo", "wait", "--ids", path, "--created", "--interval", "1", "--timeout", "2", + ]) + self.assertEqual(result, "") + self.assertEqual(len(self.calls), 2) + self.calls.clear() + self.http_status = 404 + result = self.invoke([ + "webapp", "silo", "wait", "--ids", path, "--deleted", "--interval", "1", "--timeout", "2", + ]) + self.assertEqual(result, "") + self.assertEqual(len(self.calls), 1) + + def test_appservice_active_backup_refresh_acceptance_and_conflict(self): + args = ["webapp", "silo", "refresh", "-g", "group", "-n", "primary"] + self.assertEqual(self.invoke(args), "") + self.assertEqual(len(self.calls), 1) + self.calls.clear() + self.http_status = 409 + self.invoke(args, expected=1) + self.assertEqual(len(self.calls), 1) + + def test_appservice_active_backup_origin_read_uses_matching_collection_resource_and_warns(self): + for identity in (["--ids", ORIGIN], ["-g", "group", "-n", "primary", "--silo", "ORIGIN"]): + with self.subTest(identity=identity): + self.calls.clear() + origin = _resource(ORIGIN) + self.responses = [(404, None), (200, {"value": [_resource(ORIGIN + "-other"), origin]})] + with self.assertLogs( + "cli.azure.cli.command_modules.appservice.operations.active_backup", level="WARNING") as logged: + output = self.invoke(["webapp", "silo", "show"] + identity) + self.assertEqual(output["id"], ORIGIN) + self.assertEqual(output["location"], "eastus") + self.assertEqual(output["properties"]["endpointStatus"], "Enabled") + self.assertNotIn("inboundIpAddress", output["properties"]) + self.assertIn("Origin point GET returned HTTP 404", logged.output[0]) + self.assertEqual([call["method"] for call in self.calls], ["GET", "GET"]) + self.assertEqual(self.calls[1]["path"], ORIGIN.rsplit("/", 1)[0]) + + def test_appservice_active_backup_successful_origin_read_does_not_use_collection(self): + output = self.invoke(["webapp", "silo", "show", "--ids", ORIGIN]) + self.assertEqual(output["id"], ORIGIN) + self.assertEqual([(call["method"], call["path"]) for call in self.calls], [("GET", ORIGIN)]) + + def test_appservice_active_backup_origin_read_searches_generated_pagination(self): + next_link = "https://management.azure.com" + ORIGIN.rsplit("/", 1)[0] + "?api-version=" + API_VERSION + "&page=2" + self.responses = [ + (404, None), (200, {"value": [], "nextLink": next_link}), + (200, {"value": [_resource(ORIGIN)]}), + ] + output = self.invoke(["webapp", "silo", "show", "--ids", ORIGIN]) + self.assertEqual(output["id"], ORIGIN) + self.assertEqual(len(self.calls), 3) + self.assertEqual(parse_qs(urlsplit(self.calls[-1]["url"]).query)["page"], ["2"]) + self.assertEqual(self.responses, []) + + def test_appservice_active_backup_origin_read_preserves_missing_and_unrelated_errors(self): + for command, status, expected in ( + (["webapp", "silo", "show", "--ids", ORIGIN + "-missing"], 404, 3), + (["appservice", "plan", "silo", "show", "--ids", ORIGIN.replace("/sites/", "/serverfarms/")], 404, 3), + *[(["webapp", "silo", "show", "--ids", ORIGIN], status, 1) for status in (400, 401, 403, 409)], + ): + with self.subTest(command=command, status=status): + self.calls.clear() + self.responses = [(status, {"error": {"code": "FixtureError", "message": "Original failure."}})] + self.invoke(command, expected=expected) + self.assertEqual(len(self.calls), 1) + self.calls.clear() + self.responses = [(404, None), (403, {"error": {"code": "Forbidden", "message": "Collection denied."}})] + self.invoke(["webapp", "silo", "show", "--ids", ORIGIN], expected=1) + self.assertEqual(len(self.calls), 2) + + def test_appservice_active_backup_origin_read_requires_exact_id_and_unique_valid_resource(self): + for resource_id in ( + ORIGIN + "-other", ORIGIN.replace("/primary/", "/other/"), + ORIGIN.replace(SUBSCRIPTION, "00000000-0000-0000-0000-000000000002"), + ORIGIN.replace("/silos/", "/providers/Other.Provider/silos/"), + ): + with self.subTest(resource_id=resource_id): + self.calls.clear() + self.responses = [(404, None), (200, {"value": [_resource(resource_id)]})] + self.invoke(["webapp", "silo", "show", "--ids", ORIGIN], expected=3) + self.assertEqual(len(self.calls), 2) + for field, value in (("type", "Microsoft.Web/serverfarms/silos"), ("properties", None)): + with self.subTest(field=field): + origin = _resource(ORIGIN) + origin[field] = value + self.responses = [(404, None), (200, {"value": [origin]})] + self.invoke(["webapp", "silo", "show", "--ids", ORIGIN], expected=1) + next_link = "https://management.azure.com" + ORIGIN.rsplit("/", 1)[0] + "?api-version=" + API_VERSION + "&page=2" + self.responses = [ + (404, None), (200, {"value": [_resource(ORIGIN)], "nextLink": next_link}), + (200, {"value": [_resource(ORIGIN.upper())]}), + ] + self.invoke(["webapp", "silo", "show", "--ids", ORIGIN], expected=1) + self.assertEqual(self.responses, []) + + def test_appservice_active_backup_origin_update_reuses_generated_get_and_put(self): + origin = _resource(ORIGIN) + origin["properties"]["followLastKnownGood"] = True + self.responses = [(404, None), (200, {"value": [origin]})] + output = self.invoke(["webapp", "silo", "update", "--ids", ORIGIN, "--endpoint-status", "Disabled"]) + self.assertEqual([call["method"] for call in self.calls], ["GET", "GET", "PUT"]) + self.assertEqual(self.calls[-1]["path"], ORIGIN) + body = self.calls[-1]["body"] + self.assertEqual(body["location"], "eastus") + self.assertEqual(body["properties"]["endpointStatus"], "Disabled") + self.assertIs(body["properties"]["followLastKnownGood"], True) + self.assertNotIn("revisionNumber", body["properties"]) + self.assertEqual(output["properties"]["endpointStatus"], "Disabled") + + def test_appservice_active_backup_origin_fallback_does_not_mask_update_failure(self): + self.responses = [ + (404, None), (200, {"value": [_resource(ORIGIN)]}), + (409, {"error": {"code": "Conflict", "message": "Update rejected."}}), + ] + self.invoke(["webapp", "silo", "update", "--ids", ORIGIN, "--endpoint-status", "Disabled"], expected=1) + self.assertEqual([call["method"] for call in self.calls], ["GET", "GET", "PUT"]) + self.assertEqual(self.responses, []) + + def test_appservice_active_backup_origin_wait_checks_collection_state_and_actual_absence(self): + pending = _resource(ORIGIN) + pending["properties"]["provisioningState"] = "Creating" + for condition, pages in ( + ("--created", [{"value": [pending]}, {"value": [_resource(ORIGIN)]}]), + ("--deleted", [{"value": [_resource(ORIGIN)]}, {"value": []}]), + ): + with self.subTest(condition=condition): + self.calls.clear() + self.responses = [(404, None), (200, pages[0]), (404, None), (200, pages[1])] + self.invoke(["webapp", "silo", "wait", "--ids", ORIGIN, condition, "--interval", "1", "--timeout", "2"]) + self.assertEqual([call["method"] for call in self.calls], ["GET"] * 4) + self.assertEqual(self.responses, []) + + def test_appservice_active_backup_origin_read_preserves_cloud_subscription_and_encoded_identity(self): + from azure.cli.core.cloud import get_cloud + self.cloud = get_cloud(get_default_cli(), "AzureUSGovernment") + target = ORIGIN.replace(SUBSCRIPTION, "00000000-0000-0000-0000-000000000002").replace("/group/", "/group space/") + self.responses = [(404, None), (200, {"value": [_resource(target.upper())]})] + output = self.invoke(["webapp", "silo", "show", "--ids", target]) + self.assertEqual(output["id"], target.upper()) + self.assertEqual(len(self.calls), 2) + for call in self.calls: + self.assertEqual(urlsplit(call["url"]).hostname, "management.usgovcloudapi.net") + self.assertIn("/subscriptions/00000000-0000-0000-0000-000000000002/", call["path"]) + self.assertIn("/resourceGroups/group%20space/", call["path"]) + + def test_appservice_active_backup_lists_follow_next_link(self): + self.paginate = True + result = self.invoke(["webapp", "silo", "list", "-g", "group", "-n", "primary"]) + self.assertEqual(len(result), 2) + self.assertEqual(len(self.calls), 2) + self.assertEqual(parse_qs(urlsplit(self.calls[1]["url"]).query)["page"], ["2"]) + + def test_appservice_active_backup_traffic_uses_structured_arguments(self): + self.invoke([ + "webapp", "managed-traffic", "update", "-g", "group", "-n", "primary", + "--traffic-mode", "Active", + "--endpoints", "[{region:eastus,weight:1},{region:westus2,weight:1000,is-weight-auto-computed:false}]", + ]) + endpoints = self.calls[-1]["body"]["properties"]["endpoints"] + self.assertEqual([item["weight"] for item in endpoints], [1, 1000]) + self.assertIs(endpoints[1]["isWeightAutoComputed"], False) + self.calls.clear() + self.invoke([ + "webapp", "managed-traffic", "list", "-g", "group", "-n", "primary", "--expand", "endpoints", + ]) + self.assertEqual(parse_qs(urlsplit(self.calls[0]["url"]).query)["$expand"], ["endpoints"]) + + def test_appservice_active_backup_invalid_traffic_bounds_send_no_request(self): + for value in ("0", "1001"): + self.invoke([ + "webapp", "managed-traffic", "update", "-g", "group", "-n", "primary", + "--endpoints", "[{region:eastus,weight:" + value + "}]", + ], expected=1) + self.assertEqual(self.calls, []) + + def test_appservice_active_backup_vnet_update_is_patch(self): + self.invoke([ + "webapp", "silo", "vnet-integration", "update", "-g", "group", "-n", "primary", "--silo", "backup", + "--subnet", SUBNET, "--swift-supported", "false", + ]) + self.assertEqual(len(self.calls), 1) + self.assertEqual(self.calls[0]["method"], "PATCH") + self.assertEqual(self.calls[0]["body"]["properties"], {"subnetResourceId": SUBNET, "swiftSupported": False}) + + def test_appservice_active_backup_plan_vnet_list_accepts_array_and_arm_envelope(self): + properties = {"subnetResourceId": SUBNET, "swiftSupported": False} + for payload in ( + [{"properties": properties}], + {"id": RESOURCE_ROOT + "/serverfarms/physical/virtualNetworkIntegrations", + "type": "Microsoft.Web/serverfarms/virtualNetworkIntegrations", "location": "westus2", + "properties": [{**properties, "subnetIpAllocation": {"subnetIpAddressesAvailable": 59}}]}, + ): + with self.subTest(payload=payload): + self.calls.clear() + self.responses = [(200, payload)] + output = self.invoke([ + "appservice", "plan", "silo", "vnet-integration", "list", + "-g", "group", "-n", "primary", "--silo", "backup", + ]) + self.assertEqual(output, [properties]) + self.assertNotIn("id", output[0]) + self.assertEqual(len(self.calls), 1) + self.assertEqual(self.calls[0]["method"], "GET") + + def test_appservice_active_backup_plan_vnet_list_empty_results(self): + for payload in ([], {"type": "Microsoft.Web/serverfarms/virtualNetworkIntegrations", "properties": []}): + self.responses = [(200, payload)] + self.assertEqual(self.invoke([ + "appservice", "plan", "silo", "vnet-integration", "list", + "-g", "group", "-n", "primary", "--silo", "backup", + ]), []) + + def test_appservice_active_backup_plan_vnet_list_rejects_unexpected_shapes(self): + envelope = {"type": "Microsoft.Web/serverfarms/virtualNetworkIntegrations", "properties": []} + for payload in ( + None, {}, {"value": []}, [None], {**envelope, "type": "Microsoft.Web/sites/silos"}, + {**envelope, "properties": {}}, {**envelope, "properties": [None]}, + {**envelope, "properties": [{}]}, {**envelope, "properties": [{"subnetResourceId": ""}]}, + ): + with self.subTest(payload=payload): + self.calls.clear() + self.responses = [(200, payload)] + self.invoke([ + "appservice", "plan", "silo", "vnet-integration", "list", + "-g", "group", "-n", "primary", "--silo", "backup", + ], expected=1) + self.assertEqual(len(self.calls), 1) + + def test_appservice_active_backup_plan_vnet_list_preserves_service_errors(self): + for status, expected in ((403, 1), (404, 3)): + self.calls.clear() + self.responses = [(status, {"error": {"code": "FixtureError", "message": "List rejected."}})] + self.invoke([ + "appservice", "plan", "silo", "vnet-integration", "list", + "-g", "group", "-n", "primary", "--silo", "backup", + ], expected=expected) + self.assertEqual(len(self.calls), 1) + + def test_appservice_active_backup_create_requires_explicit_locations(self): + self.invoke([ + "webapp", "silo", "create", "-g", "group", "-n", "primary", "--silo", "backup", + "--primary-location", "eastus", + ], expected=2, location_default="centralus") + self.assertEqual(self.calls, []) + + def test_appservice_active_backup_update_does_not_inherit_location_default(self): + self.invoke([ + "webapp", "silo", "update", "-g", "group", "-n", "primary", "--silo", "backup", + "--endpoint-status", "Disabled", + ], location_default="centralus") + self.assertEqual(self.calls[-1]["body"]["location"], "westus2") + + def test_appservice_active_backup_generic_set_is_supported(self): + self.invoke([ + "appservice", "plan", "silo", "update", "-g", "group", "-n", "primary", "--silo", "backup", + "--set", "properties.instanceNumberOverride=2", + ]) + self.assertEqual(self.calls[-1]["body"]["properties"]["instanceNumberOverride"], 2) + + def test_appservice_active_backup_nested_ids(self): + for family, segment in (("appservice plan", "serverfarms"), ("webapp", "sites")): + self.calls.clear() + target = RESOURCE_ROOT + "/{}/primary/silos/backup".format(segment) + self.invoke(family.split() + ["silo", "show", "--ids", target]) + self.assertEqual(self.calls[0]["path"], target) + + def test_appservice_active_backup_ids_do_not_retarget_other_resource_types(self): + invalid = [ + RESOURCE_ROOT + "/serverfarms/primary/silos/backup", + RESOURCE_ROOT + "/sites/primary/slots/staging/silos/backup", + RESOURCE_ROOT + "/sites/primary/providers/Other.Provider/silos/backup", + ] + for target in invalid: + with self.subTest(resource_id=target): + self.calls.clear() + self.invoke(["webapp", "silo", "show", "--ids", target], expected=1) + self.assertEqual(self.calls, []) + + def test_appservice_active_backup_parent_ids(self): + for command in ("webapp managed-traffic show", "webapp last-known-good show", "webapp silo refresh"): + self.calls.clear() + self.invoke(command.split() + ["--ids", RESOURCE_ROOT + "/sites/primary"]) + self.assertEqual(len(self.calls), 1) + self.calls.clear() + self.invoke(command.split() + ["--ids", RESOURCE_ROOT + "/serverfarms/primary"], expected=1) + self.assertEqual(self.calls, []) + + def test_appservice_active_backup_invalid_ids_cannot_mutate_or_wait_on_other_silos(self): + for entry in COMMANDS: + if "{silo}" not in entry["path"] or entry["command"].endswith((" create", " show")): + continue + command = entry["command"] + wrong_parent = "sites" if command.startswith("appservice") else "serverfarms" + target = RESOURCE_ROOT + "/{}/primary/silos/backup".format(wrong_parent) + args = command.split() + ["--ids", target] + if command.endswith((" delete", " remove")): + args += ["--yes"] + if command.endswith(" wait"): + args += ["--exists"] + with self.subTest(command=command): + self.invoke(args, expected=2 if entry.get("key") == "C07" else 1) + self.assertEqual(self.calls, []) + + def test_appservice_active_backup_selected_cloud(self): + from azure.cli.core.cloud import get_cloud + self.cloud = get_cloud(get_default_cli(), "AzureUSGovernment") + self.invoke(["webapp", "silo", "show", "-g", "group", "-n", "primary", "--silo", "backup"]) + self.assertEqual(urlsplit(self.calls[0]["url"]).hostname, "management.usgovcloudapi.net") + + def test_appservice_active_backup_does_not_mark_existing_groups_preview(self): + loader = COMMAND_LOADER_CLS(cli_ctx=get_default_cli()) + table = loader.load_command_table(None) + self.assertIsNone(table["webapp show"].preview_info) + self.assertIsNone(table["appservice plan show"].preview_info) + self.assertNotIn("preview_info", loader.command_group_table["webapp"].group_kwargs) + + def test_appservice_active_backup_location_customizations_inherit_generated_commands(self): + from azure.cli.command_modules.appservice.operations import active_backup as custom + from azure.cli.command_modules.appservice.aaz.latest.appservice.plan import silo as plan + from azure.cli.command_modules.appservice.aaz.latest.webapp import silo as site + from azure.cli.command_modules.appservice.aaz.latest.appservice.plan.silo.vnet_integration import List + self.assertEqual(custom.PlanSiloVnetList.__bases__, (List,)) + self.assertEqual( + custom.PlanSiloVnetList.ServerFarmSiloResourcesGetServerFarmSiloVnetIntegrations.__bases__, + (List.ServerFarmSiloResourcesGetServerFarmSiloVnetIntegrations,), + ) + for prefix, generated in (("PlanSilo", plan), ("SiteSilo", site)): + for verb in ("Create", "Update", "Delete", "Show", "List", "Wait"): + cls = getattr(custom, prefix + verb) + self.assertEqual(cls.__bases__, (getattr(generated, verb),)) + self.assertEqual( + custom.PlanSiloCreate.ServerFarmSiloResourcesCreateOrUpdateServerFarmSilo.__bases__, + (plan.Create.ServerFarmSiloResourcesCreateOrUpdateServerFarmSilo,), + ) + self.assertEqual( + custom.SiteSiloCreate.SiteSiloResourcesCreateOrUpdateSiteSilo.__bases__, + (site.Create.SiteSiloResourcesCreateOrUpdateSiteSilo,), + ) + + def test_appservice_active_backup_read_and_list_preserve_service_location(self): + for family in ("appservice plan", "webapp"): + shown = self.invoke(family.split() + ["silo", "show", "-g", "group", "-n", "primary", "--silo", "backup"]) + self.assertEqual(shown["location"], "westus2") + listed = self.invoke(family.split() + ["silo", "list", "-g", "group", "-n", "primary"]) + self.assertEqual(listed[0]["location"], "westus2") + + def test_appservice_active_backup_update_accepts_nested_location_metadata(self): + for family, segment in (("appservice plan", "serverfarms"), ("webapp", "sites")): + self.calls.clear() + data = _resource(RESOURCE_ROOT + "/{}/primary/silos/backup".format(segment)) + del data["location"] + data["properties"]["siloLocation"] = "westus2" + self.reads = [data] + self.invoke(family.split() + ["silo", "update", "-g", "group", "-n", "primary", "--silo", "backup"]) + self.assertEqual(self.calls[-1]["body"]["location"], "westus2") + + def test_appservice_active_backup_update_requires_real_location_metadata(self): + for missing in (True, False): + self.calls.clear() + data = _resource(RESOURCE_ROOT + "/sites/primary/silos/backup") + if missing: + del data["location"] + else: + data["properties"]["siloLocation"] = "eastus" + self.reads = [data] + self.invoke([ + "webapp", "silo", "update", "-g", "group", "-n", "primary", "--silo", "backup", + "--endpoint-status", "Enabled", + ], expected=1, location_default="centralus") + self.assertEqual([call["method"] for call in self.calls], ["GET"]) + + def test_appservice_active_backup_update_cannot_change_or_remove_location(self): + for flags in ( + ["--location", "eastus"], + ["--set", "location=eastus"], + ["--set", "properties.siloLocation=eastus"], + ["--remove", "location"], + ): + self.calls.clear() + self.invoke([ + "webapp", "silo", "update", "-g", "group", "-n", "primary", "--silo", "backup", + ] + flags, expected=1) + self.assertEqual([call["method"] for call in self.calls], ["GET"]) + + def test_appservice_active_backup_update_retains_service_location_spelling(self): + data = _resource(RESOURCE_ROOT + "/sites/primary/silos/backup") + data["location"] = "West US 2" + self.reads = [data] + self.invoke([ + "webapp", "silo", "update", "-g", "group", "-n", "primary", "--silo", "backup", "--location", "westus2", + ]) + self.assertEqual(self.calls[-1]["body"]["location"], "West US 2") + + def test_appservice_active_backup_location_polling_accepts_empty_pending_responses(self): + self.async_operation = True + self.location_operation = True + for entry in COMMANDS: + if not entry["command"].endswith(("silo create", "silo update", "silo delete")): + continue + with self.subTest(command=entry["command"]): + self.calls.clear() + self.location_responses = [202, 200] + result = self.invoke(self.arguments(entry)) + self.assertEqual([call["method"] for call in self.calls], + entry["methods"] + ["GET", "GET"]) + self.assertEqual(self.location_responses, []) + if entry["command"].endswith("delete"): + self.assertEqual(result, "") + else: + self.assertEqual(result["id"], self.calls[-1]["path"].rsplit("/operationresults/", 1)[0]) + + def test_appservice_active_backup_location_polling_preserves_no_wait(self): + self.async_operation = True + self.location_operation = True + for entry in COMMANDS: + if not entry["command"].endswith(("silo create", "silo update", "silo delete")): + continue + self.calls.clear() + self.assertEqual(self.invoke(self.arguments(entry) + ["--no-wait"]), "") + self.assertEqual([call["method"] for call in self.calls], entry["methods"]) + + def test_appservice_active_backup_location_polling_preserves_core_error_handling(self): + self.async_operation = True + self.location_operation = True + for entry in COMMANDS: + if not entry["command"].endswith(("silo create", "silo update", "silo delete")): + continue + for status, exit_code in ((409, 1), (404, 3)): + # CLI core already treats a polling 404 as completion for long-running deletes, not PUTs. + expected = 0 if status == 404 and entry["command"].endswith("delete") else exit_code + with self.subTest(command=entry["command"], status=status): + self.calls.clear() + self.location_responses = [202, status] + self.invoke(self.arguments(entry), expected=expected) + self.assertEqual(self.location_responses, []) + self.assertEqual([call["method"] for call in self.calls], + entry["methods"] + ["GET", "GET"]) + + def test_appservice_active_backup_polling_hook_preserves_other_hooks_and_async_only_headers(self): + from types import SimpleNamespace + from azure.cli.command_modules.appservice.operations.active_backup import _prefer_location_polling + original = CaseInsensitiveDict({"Location": "location-monitor", "Azure-AsyncOperation": "async-monitor"}) + response = SimpleNamespace(http_response=SimpleNamespace(headers=original)) + observed = [] + _prefer_location_polling(response, hook=lambda value: observed.append( + value.http_response.headers["Azure-AsyncOperation"])) + self.assertEqual(observed, ["async-monitor"]) + self.assertNotIn("Azure-AsyncOperation", original) + original.pop("Location") + original["Azure-AsyncOperation"] = "async-only" + _prefer_location_polling(response) + self.assertEqual(original["Azure-AsyncOperation"], "async-only") diff --git a/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_active_backup_commands.py b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_active_backup_commands.py new file mode 100644 index 00000000000..0e00964ab0d --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/appservice/tests/latest/test_active_backup_commands.py @@ -0,0 +1,77 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import unittest + +from azure.cli.testsdk import LiveScenarioTest, ResourceGroupPreparer + + +PRIMARY_LOCATION = os.environ.get('AZURE_ACTIVE_BACKUP_PRIMARY_LOCATION', 'eastus') +SILO_LOCATION = os.environ.get('AZURE_ACTIVE_BACKUP_SILO_LOCATION', 'westus2') + + +@unittest.skipUnless(os.environ.get('AZURE_ACTIVE_BACKUP_LIVE') == '1', + 'Requires explicit live-resource authorization and an enabled Active Backup subscription.') +class ActiveBackupLiveTests(LiveScenarioTest): + + @ResourceGroupPreparer(location=PRIMARY_LOCATION) + def test_appservice_active_backup_windows(self, resource_group): + self._lifecycle(resource_group, linux=False) + + @ResourceGroupPreparer(location=PRIMARY_LOCATION) + def test_appservice_active_backup_linux(self, resource_group): + self._lifecycle(resource_group, linux=True) + + def _lifecycle(self, resource_group, linux): + plan = self.create_random_name('abplan', 20) + app = self.create_random_name('abapp', 24) + self.kwargs.update(rg=resource_group, plan=plan, app=app, + primary=PRIMARY_LOCATION, secondary=SILO_LOCATION, + linux='true' if linux else 'false', vnet=self.create_random_name('abnet', 20)) + self.cmd('appservice plan create -g {rg} -n {plan} --location {primary} ' + '--sku P1V3 --number-of-workers 2 --is-linux {linux}') + self.cmd('webapp create -g {rg} -n {app} --plan {plan}' + + (' --runtime "PYTHON:3.12"' if linux else '')) + try: + self.cmd('appservice plan silo create -g {rg} -n {plan} --silo backup-plan ' + '--location {secondary} --primary-location {primary} --number-of-workers 2 --no-wait') + self.cmd('appservice plan silo wait -g {rg} -n {plan} --silo backup-plan --created') + self.cmd('appservice plan silo show -g {rg} -n {plan} --silo backup-plan') + self.cmd('webapp silo create -g {rg} -n {app} --silo backup-app --location {secondary} ' + '--primary-location {primary} --traffic-mode Active --no-wait') + self.cmd('webapp silo wait -g {rg} -n {app} --silo backup-app --created') + self.cmd('webapp silo show -g {rg} -n {app} --silo backup-app', + checks=[self.check('properties.provisioningState', 'Succeeded')]) + self.cmd('webapp silo list -g {rg} -n {app}') + self.cmd('appservice plan silo list -g {rg} -n {plan}') + self.cmd('appservice plan silo update -g {rg} -n {plan} --silo backup-plan --number-of-workers 2') + self.cmd('webapp silo update -g {rg} -n {app} --silo backup-app --follow-last-known-good false') + self.cmd('webapp managed-traffic update -g {rg} -n {app} --traffic-mode Passive') + self.cmd('webapp managed-traffic list -g {rg} -n {app} --expand endpoints') + self.cmd('webapp managed-traffic show -g {rg} -n {app}') + self.cmd('webapp last-known-good mark -g {rg} -n {app}') + self.cmd('webapp last-known-good show -g {rg} -n {app}') + self.cmd('webapp last-known-good list -g {rg} -n {app}') + self.cmd('webapp silo refresh -g {rg} -n {app}') + vnet_result = self.cmd( + 'network vnet create -g {rg} -n {vnet} --location {secondary} ' + '--address-prefixes 10.237.16.0/24 --subnet-name integration --subnet-prefixes 10.237.16.0/26' + ).get_output_in_json() + self.kwargs['subnet'] = vnet_result['newVNet']['subnets'][0]['id'] + self.cmd('network vnet subnet update -g {rg} --vnet-name {vnet} -n integration ' + '--delegations Microsoft.Web/serverFarms') + self.cmd('webapp silo vnet-integration add -g {rg} -n {app} --silo backup-app --subnet {subnet}') + try: + self.cmd('webapp silo vnet-integration show -g {rg} -n {app} --silo backup-app') + self.cmd('webapp silo vnet-integration update -g {rg} -n {app} --silo backup-app --subnet {subnet}') + self.cmd('appservice plan silo vnet-integration list -g {rg} -n {plan} --silo backup-plan') + finally: + self.cmd('webapp silo vnet-integration remove -g {rg} -n {app} --silo backup-app --yes') + finally: + try: + self.cmd('webapp silo delete -g {rg} -n {app} --silo backup-app --yes') + finally: + self.cmd('appservice plan silo delete -g {rg} -n {plan} --silo backup-plan --yes')