From 24eb1fccfff38e45f254badf9800e9353ed3a73a Mon Sep 17 00:00:00 2001 From: Ayushi Upmanyu Date: Tue, 14 Apr 2026 16:26:35 +0530 Subject: [PATCH 1/3] fix(site key create): fix set_const arg order and add SystemAssigned identity - set_const signature is (name, value, type) not (name, type, value) - API requires identity.type=SystemAssigned on siteKey creation - Use hidden arg for siteResourceId populated in pre_operations Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/site/azext_site/_help.py | 50 ++++ .../aaz/latest/site/key/__cmd_group.py | 23 ++ .../aaz/latest/site/key/__init__.py | 16 ++ .../azext_site/aaz/latest/site/key/_create.py | 272 ++++++++++++++++++ .../azext_site/aaz/latest/site/key/_delete.py | 140 +++++++++ .../aaz/latest/site/key/_download.py | 205 +++++++++++++ .../azext_site/aaz/latest/site/key/_list.py | 216 ++++++++++++++ .../azext_site/aaz/latest/site/key/_show.py | 215 ++++++++++++++ src/site/azext_site/tests/latest/test_site.py | 56 ++++ 9 files changed, 1193 insertions(+) create mode 100644 src/site/azext_site/aaz/latest/site/key/__cmd_group.py create mode 100644 src/site/azext_site/aaz/latest/site/key/__init__.py create mode 100644 src/site/azext_site/aaz/latest/site/key/_create.py create mode 100644 src/site/azext_site/aaz/latest/site/key/_delete.py create mode 100644 src/site/azext_site/aaz/latest/site/key/_download.py create mode 100644 src/site/azext_site/aaz/latest/site/key/_list.py create mode 100644 src/site/azext_site/aaz/latest/site/key/_show.py diff --git a/src/site/azext_site/_help.py b/src/site/azext_site/_help.py index 126d5d00714..abbb4a7578c 100644 --- a/src/site/azext_site/_help.py +++ b/src/site/azext_site/_help.py @@ -9,3 +9,53 @@ # pylint: disable=too-many-lines from knack.help_files import helps # pylint: disable=unused-import + +helps['site key'] = """ +type: group +short-summary: Manage Site Keys for Azure Edge sites. +""" + +helps['site key create'] = """ +type: command +short-summary: Create a site key linked to a site. +examples: + - name: Create a site key + text: az site key create --name TestSiteKeyName --resource-group TestRGName --site-name TestSiteName + - name: Create a site key with custom token expiry + text: az site key create --name TestSiteKeyName --resource-group TestRGName --site-name TestSiteName --token-expiry-date "2026-05-01T00:00:00Z" +""" + +helps['site key delete'] = """ +type: command +short-summary: Delete a site key. +examples: + - name: Delete a site key + text: az site key delete --name TestSiteKeyName --resource-group TestRGName +""" + +helps['site key list'] = """ +type: command +short-summary: List all site keys in a resource group. +examples: + - name: List site keys + text: az site key list --resource-group TestRGName +""" + +helps['site key show'] = """ +type: command +short-summary: Get details of a specific site key. +examples: + - name: Show a site key + text: az site key show --name TestSiteKeyName --resource-group TestRGName +""" + +helps['site key download'] = """ +type: command +short-summary: Download the token for a site key. +long-summary: Downloads the site key token and saves it to a file. If --file is not specified, the token is saved to .txt in the current directory. +examples: + - name: Download a site key token + text: az site key download --name TestSiteKeyName --resource-group TestRGName + - name: Download to a specific file + text: az site key download --name TestSiteKeyName --resource-group TestRGName --file ./my-token.txt +""" diff --git a/src/site/azext_site/aaz/latest/site/key/__cmd_group.py b/src/site/azext_site/aaz/latest/site/key/__cmd_group.py new file mode 100644 index 00000000000..1f89adb145b --- /dev/null +++ b/src/site/azext_site/aaz/latest/site/key/__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( + "site key", +) +class __CMDGroup(AAZCommandGroup): + """Manage Site Keys for Azure Edge sites. + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/site/azext_site/aaz/latest/site/key/__init__.py b/src/site/azext_site/aaz/latest/site/key/__init__.py new file mode 100644 index 00000000000..80641d4ad09 --- /dev/null +++ b/src/site/azext_site/aaz/latest/site/key/__init__.py @@ -0,0 +1,16 @@ +# -------------------------------------------------------------------------------------------- +# 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 ._download import * diff --git a/src/site/azext_site/aaz/latest/site/key/_create.py b/src/site/azext_site/aaz/latest/site/key/_create.py new file mode 100644 index 00000000000..e4d72c5c4c9 --- /dev/null +++ b/src/site/azext_site/aaz/latest/site/key/_create.py @@ -0,0 +1,272 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "site key create", +) +class Create(AAZCommand): + """Create a site key linked to a site. + + :example: Create a site key at resource group scope + az site key create --name TestSiteKeyName --resource-group TestRGName --site-name TestSiteName + + :example: Create a site key with custom token expiry + az site key create --name TestSiteKeyName --resource-group TestRGName --site-name TestSiteName --token-expiry-date "2026-05-01T00:00:00Z" + """ + + _aaz_info = { + "version": "2026-04-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.edge/sitekeys/{}", "2026-04-01-preview"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.resource_group = AAZResourceGroupNameArg( + help="Name of the resource group", + required=True, + ) + _args_schema.site_key_name = AAZStrArg( + options=["-n", "--name", "--site-key-name"], + help="Name of the SiteKey", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9-_]{2,22}[a-zA-Z0-9]$", + ), + ) + + # define Arg Group "Properties" + + _args_schema = cls._args_schema + _args_schema.site_name = AAZStrArg( + options=["--site-name"], + arg_group="Properties", + help="Name of the site to link this key to. Used to construct the site ARM resource ID.", + required=True, + ) + _args_schema.token_expiry_date = AAZStrArg( + options=["--token-expiry-date"], + arg_group="Properties", + help="Token expiry date in ISO 8601 format (e.g., 2026-05-01T00:00:00Z). Defaults to 7 days from now.", + ) + # Hidden arg to hold the computed ARM resource ID for the site + _args_schema.site_resource_id = AAZStrArg( + options=["--site-resource-id"], + help="Full ARM resource ID of the site (computed automatically from --site-name).", + registered=False, # hidden from CLI + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.SiteKeysCreateOrUpdate(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + # Construct siteResourceId from --site-name, --resource-group, and subscription + args = self.ctx.args + site_resource_id = "/subscriptions/{}/resourceGroups/{}/providers/Microsoft.Edge/sites/{}".format( + self.ctx.subscription_id, + args.resource_group.to_serialized_data(), + args.site_name.to_serialized_data(), + ) + args.site_resource_id = site_resource_id + + @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 SiteKeysCreateOrUpdate(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.Edge/siteKeys/{siteKeyName}", + **self.url_parameters + ) + + @property + def method(self): + return "PUT" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siteKeyName", self.ctx.args.site_key_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-04-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Content-Type", "application/json", + ), + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + @property + def content(self): + _content_value, _builder = self.new_content_builder( + self.ctx.args, + typ=AAZObjectType, + typ_kwargs={"flags": {"required": True, "client_flatten": True}} + ) + _builder.set_prop("identity", AAZObjectType) + _builder.set_prop("properties", AAZObjectType) + + identity = _builder.get(".identity") + if identity is not None: + identity.set_const("type", "SystemAssigned", AAZStrType) + + properties = _builder.get(".properties") + if properties is not None: + properties.set_prop("siteResourceId", AAZStrType, ".site_resource_id") + properties.set_prop("tokenExpiryDate", AAZStrType, ".token_expiry_date") + + 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.generated_date = AAZStrType( + serialized_name="generatedDate", + flags={"read_only": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.site_resource_id = AAZStrType( + serialized_name="siteResourceId", + ) + properties.token_expiry_date = AAZStrType( + serialized_name="tokenExpiryDate", + ) + + 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/site/azext_site/aaz/latest/site/key/_delete.py b/src/site/azext_site/aaz/latest/site/key/_delete.py new file mode 100644 index 00000000000..054e8743127 --- /dev/null +++ b/src/site/azext_site/aaz/latest/site/key/_delete.py @@ -0,0 +1,140 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "site key delete", + confirmation="Are you sure you want to perform this operation?", +) +class Delete(AAZCommand): + """Delete a site key. + + :example: Delete a site key at resource group scope + az site key delete --name TestSiteKeyName --resource-group TestRGName + """ + + _aaz_info = { + "version": "2026-04-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.edge/sitekeys/{}", "2026-04-01-preview"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return None + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + _args_schema = cls._args_schema + _args_schema.resource_group = AAZResourceGroupNameArg( + help="Name of the resource group", + required=True, + ) + _args_schema.site_key_name = AAZStrArg( + options=["-n", "--name", "--site-key-name"], + help="Name of the SiteKey", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9-_]{2,22}[a-zA-Z0-9]$", + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.SiteKeysDelete(ctx=self.ctx)() + self.post_operations() + + @register_callback + def pre_operations(self): + pass + + @register_callback + def post_operations(self): + pass + + class SiteKeysDelete(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.Edge/siteKeys/{siteKeyName}", + **self.url_parameters + ) + + @property + def method(self): + return "DELETE" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siteKeyName", self.ctx.args.site_key_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-04-01-preview", + required=True, + ), + } + return parameters + + def on_200(self, session): + pass + + def on_204(self, session): + pass + + +class _DeleteHelper: + """Helper class for Delete""" + + +__all__ = ["Delete"] diff --git a/src/site/azext_site/aaz/latest/site/key/_download.py b/src/site/azext_site/aaz/latest/site/key/_download.py new file mode 100644 index 00000000000..9e03193ef05 --- /dev/null +++ b/src/site/azext_site/aaz/latest/site/key/_download.py @@ -0,0 +1,205 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + +import os +from azure.cli.core.aaz import * +from knack.log import get_logger + +logger = get_logger(__name__) + + +@register_command( + "site key download", +) +class Download(AAZCommand): + """Download the token for a site key. + + Downloads the site key token and saves it to a file. If --file is not specified, + the token is saved to .txt in the current directory. + + :example: Download a site key token + az site key download --name TestSiteKeyName --resource-group TestRGName + + :example: Download a site key token to a specific file + az site key download --name TestSiteKeyName --resource-group TestRGName --file ./my-token.txt + """ + + _aaz_info = { + "version": "2026-04-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.edge/sitekeys/{}/download", "2026-04-01-preview"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + _args_schema = cls._args_schema + _args_schema.resource_group = AAZResourceGroupNameArg( + help="Name of the resource group", + required=True, + ) + _args_schema.site_key_name = AAZStrArg( + options=["-n", "--name", "--site-key-name"], + help="Name of the SiteKey", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9-_]{2,22}[a-zA-Z0-9]$", + ), + ) + _args_schema.file_path = AAZStrArg( + options=["-f", "--file"], + help="Output file path for the downloaded token. Defaults to .txt in the current directory.", + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.SiteKeysDownload(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) + + # Extract token and save to file + token = None + if result and "tokenResponse" in result and result["tokenResponse"]: + token = result["tokenResponse"].get("token") + + if token: + file_path = None + if has_value(self.ctx.args.file_path): + file_path = self.ctx.args.file_path.to_serialized_data() + else: + key_name = self.ctx.args.site_key_name.to_serialized_data() + file_path = "{}.txt".format(key_name) + + file_path = os.path.abspath(file_path) + with open(file_path, "w") as f: + f.write(token) + logger.warning("Token saved to %s", file_path) + return {"filePath": file_path, "message": "Token saved successfully"} + + return result + + class SiteKeysDownload(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.Edge/siteKeys/{siteKeyName}/download", + **self.url_parameters + ) + + @property + def method(self): + return "POST" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siteKeyName", self.ctx.args.site_key_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-04-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.token_response = AAZObjectType( + serialized_name="tokenResponse", + ) + + token_response = cls._schema_on_200.token_response + token_response.token = AAZStrType() + + return cls._schema_on_200 + + +class _DownloadHelper: + """Helper class for Download""" + + +__all__ = ["Download"] diff --git a/src/site/azext_site/aaz/latest/site/key/_list.py b/src/site/azext_site/aaz/latest/site/key/_list.py new file mode 100644 index 00000000000..fff0e222a29 --- /dev/null +++ b/src/site/azext_site/aaz/latest/site/key/_list.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( + "site key list", +) +class List(AAZCommand): + """List all site keys in a resource group. + + :example: List site keys at resource group scope + az site key list --resource-group TestRGName + """ + + _aaz_info = { + "version": "2026-04-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.edge/sitekeys", "2026-04-01-preview"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + _args_schema = cls._args_schema + _args_schema.resource_group = AAZResourceGroupNameArg( + help="Name of the resource group", + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.SiteKeysListByResourceGroup(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 SiteKeysListByResourceGroup(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.Edge/siteKeys", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-04-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.next_link = AAZStrType( + serialized_name="nextLink", + ) + _schema_on_200.value = AAZListType( + flags={"required": True}, + ) + + value = cls._schema_on_200.value + value.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element + _element.id = AAZStrType( + flags={"read_only": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.generated_date = AAZStrType( + serialized_name="generatedDate", + flags={"read_only": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.site_resource_id = AAZStrType( + serialized_name="siteResourceId", + ) + properties.token_expiry_date = AAZStrType( + serialized_name="tokenExpiryDate", + ) + + 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/site/azext_site/aaz/latest/site/key/_show.py b/src/site/azext_site/aaz/latest/site/key/_show.py new file mode 100644 index 00000000000..e6562ecbbfa --- /dev/null +++ b/src/site/azext_site/aaz/latest/site/key/_show.py @@ -0,0 +1,215 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "site key show", +) +class Show(AAZCommand): + """Get details of a specific site key. + + :example: Show a site key at resource group scope + az site key show --name TestSiteKeyName --resource-group TestRGName + """ + + _aaz_info = { + "version": "2026-04-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.edge/sitekeys/{}", "2026-04-01-preview"], + ] + } + + def _handler(self, command_args): + super()._handler(command_args) + self._execute_operations() + return self._output() + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + _args_schema = cls._args_schema + _args_schema.resource_group = AAZResourceGroupNameArg( + help="Name of the resource group", + required=True, + ) + _args_schema.site_key_name = AAZStrArg( + options=["-n", "--name", "--site-key-name"], + help="Name of the SiteKey", + required=True, + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9-_]{2,22}[a-zA-Z0-9]$", + ), + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.SiteKeysGet(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 SiteKeysGet(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.Edge/siteKeys/{siteKeyName}", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "resourceGroupName", self.ctx.args.resource_group, + required=True, + ), + **self.serialize_url_param( + "siteKeyName", self.ctx.args.site_key_name, + required=True, + ), + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2026-04-01-preview", + required=True, + ), + } + return parameters + + @property + def header_parameters(self): + parameters = { + **self.serialize_header_param( + "Accept", "application/json", + ), + } + return parameters + + def on_200(self, session): + data = self.deserialize_http_content(session) + self.ctx.set_var( + "instance", + data, + schema_builder=self._build_schema_on_200 + ) + + _schema_on_200 = None + + @classmethod + def _build_schema_on_200(cls): + if cls._schema_on_200 is not None: + return cls._schema_on_200 + + cls._schema_on_200 = AAZObjectType() + + _schema_on_200 = cls._schema_on_200 + _schema_on_200.id = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.name = AAZStrType( + flags={"read_only": True}, + ) + _schema_on_200.properties = AAZObjectType() + _schema_on_200.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _schema_on_200.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.properties + properties.generated_date = AAZStrType( + serialized_name="generatedDate", + flags={"read_only": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.site_resource_id = AAZStrType( + serialized_name="siteResourceId", + ) + properties.token_expiry_date = AAZStrType( + serialized_name="tokenExpiryDate", + ) + + 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/site/azext_site/tests/latest/test_site.py b/src/site/azext_site/tests/latest/test_site.py index 9e791a28a41..1d42bb7462e 100644 --- a/src/site/azext_site/tests/latest/test_site.py +++ b/src/site/azext_site/tests/latest/test_site.py @@ -131,3 +131,59 @@ def test_edge_site_crud(self): #Delete Site at subscription scope self.cmd("az site delete --site-name TestSubsSiteName --yes") + + +class SiteKeyScenario(ScenarioTest): + + @ResourceGroupPreparer(name_prefix="cli_test_site_key_", location="eastus") + def test_edge_site_key_crud(self): + # First create a site to link keys to + self.cmd( + "az site create --site-name TestSiteForKey --resource-group {rg} " + "--display-name 'Test Site' --description 'Site for key testing' " + "--street-address1='16 TOWNSEND ST' --city='San Francisco' " + "--state-or-province=CA --country=US --postal-code=94107", + checks=[ + self.check("name", "TestSiteForKey"), + ] + ) + + # Create a site key + self.cmd( + "az site key create --name TestSiteKey --resource-group {rg} --site-name TestSiteForKey", + checks=[ + self.check("name", "TestSiteKey"), + self.check("properties.provisioningState", "Succeeded"), + ] + ) + + # Show the site key + self.cmd( + "az site key show --name TestSiteKey --resource-group {rg}", + checks=[ + self.check("name", "TestSiteKey"), + self.check("properties.provisioningState", "Succeeded"), + ] + ) + + # List site keys + result = self.cmd( + "az site key list --resource-group {rg}" + ).get_output_in_json() + assert any(item.get("name") == "TestSiteKey" for item in result), \ + "'TestSiteKey' not found in site key list" + + # Download the site key token + self.cmd( + "az site key download --name TestSiteKey --resource-group {rg} --file test-token.txt", + checks=[ + self.exists("filePath"), + self.check("message", "Token saved successfully"), + ] + ) + + # Delete the site key + self.cmd("az site key delete --name TestSiteKey --resource-group {rg} --yes") + + # Clean up: delete the site + self.cmd("az site delete --site-name TestSiteForKey --resource-group {rg} --yes") From 8682bf5eda1268869e3884e23afc227df8eddfeb Mon Sep 17 00:00:00 2001 From: Ayushi Upmanyu Date: Fri, 17 Apr 2026 11:39:45 +0530 Subject: [PATCH 2/3] chore(site): bump version to 1.0.0b2 and update HISTORY.rst for site key commands Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/site/HISTORY.rst | 8 ++++++++ src/site/setup.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/site/HISTORY.rst b/src/site/HISTORY.rst index abbff5a61a7..0d3de4fc2db 100644 --- a/src/site/HISTORY.rst +++ b/src/site/HISTORY.rst @@ -3,6 +3,14 @@ Release History =============== +1.0.0b2 +++++++ +* Add `az site key create` command to create site keys linked to Azure Edge sites. +* Add `az site key show` command to get details of a specific site key. +* Add `az site key list` command to list all site keys in a resource group. +* Add `az site key delete` command to delete a site key. +* Add `az site key download` command to download a site key token to a file. + 1.0.0b1 ++++++ * Initial release. \ No newline at end of file diff --git a/src/site/setup.py b/src/site/setup.py index 69a27be0491..1ce3d0e2a75 100644 --- a/src/site/setup.py +++ b/src/site/setup.py @@ -10,7 +10,7 @@ # HISTORY.rst entry. -VERSION = '1.0.0b1' +VERSION = '1.0.0b2' # The full list of classifiers is available at # https://pypi.python.org/pypi?%3Aaction=list_classifiers From afecf40a26855d2d56609a337a8a66da2ec45839 Mon Sep 17 00:00:00 2001 From: Ayushi Upmanyu Date: Fri, 17 Apr 2026 14:59:57 +0530 Subject: [PATCH 3/3] fix(site key download): change default file extension from .txt to .SiteKey Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/site/azext_site/_help.py | 4 +- .../aaz/latest/site/key/_download.py | 6 +- src/site/azext_site/tests/latest/test_site.py | 2 +- src/site/docs/site-key-cli-commands.md | 195 ++++++++++++++++++ 4 files changed, 201 insertions(+), 6 deletions(-) create mode 100644 src/site/docs/site-key-cli-commands.md diff --git a/src/site/azext_site/_help.py b/src/site/azext_site/_help.py index abbb4a7578c..fedb28c2d9f 100644 --- a/src/site/azext_site/_help.py +++ b/src/site/azext_site/_help.py @@ -52,10 +52,10 @@ helps['site key download'] = """ type: command short-summary: Download the token for a site key. -long-summary: Downloads the site key token and saves it to a file. If --file is not specified, the token is saved to .txt in the current directory. +long-summary: Downloads the site key token and saves it to a file. If --file is not specified, the token is saved to .SiteKey in the current directory. examples: - name: Download a site key token text: az site key download --name TestSiteKeyName --resource-group TestRGName - name: Download to a specific file - text: az site key download --name TestSiteKeyName --resource-group TestRGName --file ./my-token.txt + text: az site key download --name TestSiteKeyName --resource-group TestRGName --file ./my-token.SiteKey """ diff --git a/src/site/azext_site/aaz/latest/site/key/_download.py b/src/site/azext_site/aaz/latest/site/key/_download.py index 9e03193ef05..8b528adff3b 100644 --- a/src/site/azext_site/aaz/latest/site/key/_download.py +++ b/src/site/azext_site/aaz/latest/site/key/_download.py @@ -22,7 +22,7 @@ class Download(AAZCommand): """Download the token for a site key. Downloads the site key token and saves it to a file. If --file is not specified, - the token is saved to .txt in the current directory. + the token is saved to .SiteKey in the current directory. :example: Download a site key token az site key download --name TestSiteKeyName --resource-group TestRGName @@ -66,7 +66,7 @@ def _build_arguments_schema(cls, *args, **kwargs): ) _args_schema.file_path = AAZStrArg( options=["-f", "--file"], - help="Output file path for the downloaded token. Defaults to .txt in the current directory.", + help="Output file path for the downloaded token. Defaults to .SiteKey in the current directory.", ) return cls._args_schema @@ -97,7 +97,7 @@ def _output(self, *args, **kwargs): file_path = self.ctx.args.file_path.to_serialized_data() else: key_name = self.ctx.args.site_key_name.to_serialized_data() - file_path = "{}.txt".format(key_name) + file_path = "{}.SiteKey".format(key_name) file_path = os.path.abspath(file_path) with open(file_path, "w") as f: diff --git a/src/site/azext_site/tests/latest/test_site.py b/src/site/azext_site/tests/latest/test_site.py index 1d42bb7462e..8abe2f30fb5 100644 --- a/src/site/azext_site/tests/latest/test_site.py +++ b/src/site/azext_site/tests/latest/test_site.py @@ -175,7 +175,7 @@ def test_edge_site_key_crud(self): # Download the site key token self.cmd( - "az site key download --name TestSiteKey --resource-group {rg} --file test-token.txt", + "az site key download --name TestSiteKey --resource-group {rg} --file test-token.SiteKey", checks=[ self.exists("filePath"), self.check("message", "Token saved successfully"), diff --git a/src/site/docs/site-key-cli-commands.md b/src/site/docs/site-key-cli-commands.md new file mode 100644 index 00000000000..857db741073 --- /dev/null +++ b/src/site/docs/site-key-cli-commands.md @@ -0,0 +1,195 @@ +# Azure Site Key CLI Commands + +> Quick reference for testing `az site key` commands. These commands manage **Site Keys** for Azure Edge sites. + +--- + +## Pre-requisites + +```bash +# 1. Install the extension (from .whl file) +az extension add --source --yes + +# 2. Login and set your subscription +az login +az account set --subscription + +# 3. (Only for preview/test environments) Point to regional endpoint +az cloud update --endpoint-resource-manager https://eastus2euap.management.azure.com + +# 4. When done testing, revert to production endpoint +az cloud update --endpoint-resource-manager https://management.azure.com +``` + +--- + +## Commands at a Glance + +| Command | What it does | +|---------|-------------| +| `az site key create` | Create a new site key linked to a site | +| `az site key show` | View details of a specific site key | +| `az site key list` | List all site keys in a resource group | +| `az site key download` | Download the site key token to a file | +| `az site key delete` | Delete a site key | + +--- + +## 1. Create a Site Key + +Creates a new site key and links it to an existing site. + +**Required arguments:** + +| Argument | Description | +|----------|-------------| +| `--name` or `-n` | Name for the new site key | +| `--resource-group` or `-g` | Resource group containing the site | +| `--site-name` | Name of the site to link this key to | + +**Optional arguments:** + +| Argument | Description | +|----------|-------------| +| `--token-expiry-date` | Token expiry date (ISO 8601 format). Defaults to 7 days from now. | + +### Examples + +**Minimum command:** +```bash +az site key create --name my-site-key -g MyResourceGroup --site-name MySite +``` + +**With custom token expiry:** +```bash +az site key create --name my-site-key -g MyResourceGroup --site-name MySite --token-expiry-date "2026-05-01T00:00:00Z" +``` + +--- + +## 2. Show a Site Key + +View details of a specific site key (name, provisioning state, linked site, token expiry date, etc.) + +**Required arguments:** + +| Argument | Description | +|----------|-------------| +| `--name` or `-n` | Name of the site key | +| `--resource-group` or `-g` | Resource group containing the site key | + +### Example + +```bash +az site key show --name my-site-key -g MyResourceGroup +``` + +--- + +## 3. List Site Keys + +List all site keys in a resource group. + +**Required arguments:** + +| Argument | Description | +|----------|-------------| +| `--resource-group` or `-g` | Resource group to list site keys from | + +### Example + +```bash +az site key list -g MyResourceGroup +``` + +--- + +## 4. Download a Site Key Token + +Downloads the site key token and saves it to a file. + +**Required arguments:** + +| Argument | Description | +|----------|-------------| +| `--name` or `-n` | Name of the site key | +| `--resource-group` or `-g` | Resource group containing the site key | + +**Optional arguments:** + +| Argument | Description | +|----------|-------------| +| `--file` or `-f` | Output file path. Defaults to `.SiteKey` in the current directory. | + +### Examples + +**Minimum command** (saves to `my-site-key.SiteKey`): +```bash +az site key download --name my-site-key -g MyResourceGroup +``` + +**Save to a custom file:** +```bash +az site key download --name my-site-key -g MyResourceGroup --file ./tokens/my-token.txt +``` + +--- + +## 5. Delete a Site Key + +Deletes a site key. Prompts for confirmation unless `--yes` is specified. + +**Required arguments:** + +| Argument | Description | +|----------|-------------| +| `--name` or `-n` | Name of the site key to delete | +| `--resource-group` or `-g` | Resource group containing the site key | + +**Optional arguments:** + +| Argument | Description | +|----------|-------------| +| `--yes` or `-y` | Skip the confirmation prompt | + +### Examples + +**With confirmation prompt:** +```bash +az site key delete --name my-site-key -g MyResourceGroup +``` + +**Skip confirmation:** +```bash +az site key delete --name my-site-key -g MyResourceGroup --yes +``` + +--- + +## Full Test Walkthrough + +A step-by-step sequence to test all commands end-to-end: + +```bash +# Setup +az cloud update --endpoint-resource-manager https://eastus2euap.management.azure.com +az account set --subscription + +# Step 1 — List existing site keys +az site key list -g MyResourceGroup + +# Step 2 — Create a new site key +az site key create --name test-key -g MyResourceGroup --site-name MySite + +# Step 3 — Show the newly created key +az site key show --name test-key -g MyResourceGroup + +# Step 4 — Download the token +az site key download --name test-key -g MyResourceGroup + +# Step 5 — Delete the test key +az site key delete --name test-key -g MyResourceGroup --yes + +# Cleanup — revert endpoint +az cloud update --endpoint-resource-manager https://management.azure.com +```