diff --git a/src/azure-cli-core/azure/cli/core/profiles/_shared.py b/src/azure-cli-core/azure/cli/core/profiles/_shared.py index b5c0a89e0d5..ac1e748d5f5 100644 --- a/src/azure-cli-core/azure/cli/core/profiles/_shared.py +++ b/src/azure-cli-core/azure/cli/core/profiles/_shared.py @@ -10,7 +10,6 @@ from knack.log import get_logger - logger = get_logger(__name__) @@ -84,6 +83,8 @@ class ResourceType(Enum): # pylint: disable=too-few-public-methods MGMT_CUSTOMLOCATION = ('azure.mgmt.extendedlocation', 'CustomLocations') MGMT_CONTAINERSERVICE = ('azure.mgmt.containerservice', 'ContainerServiceClient') MGMT_APPCONTAINERS = ('azure.mgmt.appcontainers', 'ContainerAppsAPIClient') + MGMT_DISCONNECTEDOPERATIONS = ('azure.mgmt.disconnectedoperations', 'DisconnectedOperationsClient') + # the "None" below will stay till a command module fills in the type so "get_mgmt_service_client" # can be provided with "ResourceType.XXX" to initialize the client object. This usually happens @@ -155,6 +156,7 @@ def default_api_version(self): AZURE_API_PROFILES = { 'latest': { + ResourceType.MGMT_DISCONNECTEDOPERATIONS: '2024-12-01-preview', ResourceType.MGMT_STORAGE: '2024-01-01', ResourceType.MGMT_NETWORK: '2022-01-01', ResourceType.MGMT_COMPUTE: SDKProfile('2024-11-01', { diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/__init__.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/__init__.py new file mode 100644 index 00000000000..bd181e4def2 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/__init__.py @@ -0,0 +1,59 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +from azure.cli.command_modules.disconnectedoperations._client_factory import cf_image + +# pylint: disable=unused-import +from azure.cli.command_modules.disconnectedoperations._help import ( + helps, +) +from azure.cli.core import AzCommandsLoader + + +class DisconnectedoperationsCommandsLoader(AzCommandsLoader): + def __init__(self, cli_ctx=None): + from azure.cli.core.commands import CliCommandType + from azure.cli.core.profiles import ResourceType + + disconnectedoperations_custom = CliCommandType( + operations_tmpl="azure.cli.command_modules.disconnectedoperations.custom#{}", + client_factory=cf_image, + ) + super().__init__( + cli_ctx=cli_ctx, + resource_type=ResourceType.MGMT_DISCONNECTEDOPERATIONS, + custom_command_type=disconnectedoperations_custom, + ) + + def load_command_table(self, args): + from azure.cli.command_modules.disconnectedoperations.commands import ( + load_command_table, + ) + from azure.cli.core.aaz import load_aaz_command_table + try: + from . import aaz + except ImportError: + aaz = None + if aaz: + load_aaz_command_table( + loader=self, + aaz_pkg_name=aaz.__name__, + args=args + ) + + load_command_table(self, args) + return self.command_table + + def load_arguments(self, command): + from azure.cli.command_modules.disconnectedoperations._params import ( + load_arguments, + ) + + load_arguments(self, command) + + +COMMAND_LOADER_CLS = DisconnectedoperationsCommandsLoader diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/_client_factory.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/_client_factory.py new file mode 100644 index 00000000000..b02601ed52c --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/_client_factory.py @@ -0,0 +1,19 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + + +def get_disconnectedoperations_management_client(cli_ctx, *_): + from azure.cli.core.commands.client_factory import get_mgmt_service_client + + # pylint: disable=import-error no-name-in-module + from azure.mgmt.disconnectedoperations import ( + DisconnectedOperationsClient, + ) + + return get_mgmt_service_client(cli_ctx, DisconnectedOperationsClient) + + +def cf_image(cli_ctx, *_): + return get_disconnectedoperations_management_client(cli_ctx).image diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/_help.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/_help.py new file mode 100644 index 00000000000..27e78b57bff --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/_help.py @@ -0,0 +1,114 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +from knack.help_files import helps + +helps['edge'] = """ +type: group +short-summary: Manage Azure Edge services and operations for edge computing scenarios. +""" + +# Remove the duplicate entry and improve the remaining one +helps['edge disconnected-operation'] = """ +type: group +short-summary: Manage Azure Edge Marketplace services for disconnected (offline) environments. +long-summary: Enables downloading and packaging of marketplace offerings for deployment in environments with limited or no internet connectivity. +""" + +helps['edge disconnected-operation offer'] = """ +type: group +short-summary: Manage marketplace offers for disconnected Edge environments. +long-summary: View, download, and package marketplace VM images and solutions for deployment to disconnected edge environments. +""" + +helps['edge disconnected-operation offer list'] = """ +type: command +short-summary: List all available marketplace offers for disconnected operations. +long-summary: Retrieves a list of all VM images and solutions available in the marketplace that can be packaged for disconnected environments, including publisher, offer, SKU, and version information. +examples: + - name: List all marketplace offers for a specific resource + text: > + az edge disconnected-operation offer list --resource-group myResourceGroup --resource-name myResource + - name: List offers and format output as table + text: > + az edge disconnected-operation offer list -g myResourceGroup --resource-name myResource --output table + - name: List offers and filter output using JMESPath query + text: > + az edge disconnected-operation offer list -g myResourceGroup --resource-name myResource --query "[?OS_Type=='Linux']" +parameters: + - name: --resource-group -g + type: string + short-summary: Name of resource group containing the disconnected operations resource + - name: --resource-name + type: string + short-summary: Name of the disconnected operations resource +""" + +helps['edge disconnected-operation offer get'] = """ +type: command +short-summary: Get detailed information about a specific marketplace offer. +long-summary: Retrieves comprehensive details for a marketplace offer, including available SKUs, versions, OS type, and size information to help with disconnected environment planning. +examples: + - name: Get details of a specific marketplace offer + text: > + az edge disconnected-operation offer get --resource-group myResourceGroup --resource-name myResource --publisher-name publisherName --offer-id offerName + - name: Get offer details and output as JSON + text: > + az edge disconnected-operation offer get -g myResourceGroup --resource-name myResource --publisher-name publisherName --offer-id offerName --output json + - name: Get offer details with custom query + text: > + az edge disconnected-operation offer get -g myResourceGroup --resource-name myResource --publisher-name publisherName --offer-id offerName --query "[].{SKU:SKU,Version:Versions}" +parameters: + - name: --resource-group -g + type: string + short-summary: Name of resource group containing the disconnected operations resource + - name: --resource-name + type: string + short-summary: Name of the disconnected operations resource + - name: --publisher-name + type: string + short-summary: Publisher name of the marketplace offer (e.g., 'MicrosoftWindowsServer') + - name: --offer-id + type: string + short-summary: Offer identifier in the marketplace (e.g., 'WindowsServer') +""" + +helps['edge disconnected-operation offer package'] = """ +type: command +short-summary: Download and package a marketplace VM image with its metadata for offline use. +long-summary: Creates a complete package containing the VM image (VHD), metadata, and icons for deployment in disconnected environments. The package can be transported to an air-gapped environment and used for VM deployment without internet connectivity. +examples: + - name: Package a marketplace offer with specific version + text: > + az edge disconnected-operation offer package --resource-group myResourceGroup --resource-name myResource --publisher-name publisherName --offer-id offerName --sku skuName --version versionNumber --output-folder "D:\\MarketplacePackages" + - name: Package a marketplace offer with specific region + text: > + az edge disconnected-operation offer package --resource-group myResourceGroup --resource-name myResource --publisher-name publisherName --offer-id offerName --sku skuName --version versionNumber --output-folder "D:\\MarketplacePackages" --region eastus +parameters: + - name: --resource-group -g + type: string + short-summary: Name of resource group containing the disconnected operations resource + - name: --resource-name + type: string + short-summary: Name of the disconnected operations resource + - name: --publisher-name + type: string + short-summary: Publisher name of the marketplace offer (e.g., 'MicrosoftWindowsServer') + - name: --offer-id + type: string + short-summary: Offer identifier in the marketplace (e.g., 'WindowsServer') + - name: --sku + type: string + short-summary: SKU identifier for the specific offer variant (e.g., '2019-Datacenter') + - name: --version + type: string + short-summary: Version of the marketplace offer to download (e.g., '17763.3287.2210110541') + - name: --output-folder + type: string + short-summary: Local folder path where the package contents will be downloaded and organized + - name: --region + type: string + short-summary: Azure region to use for marketplace access (e.g., 'eastus', 'westeurope') +""" diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/_params.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/_params.py new file mode 100644 index 00000000000..befcc657151 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/_params.py @@ -0,0 +1,49 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +from azure.cli.core.commands.parameters import resource_group_name_type + + +def load_arguments(self, _): + with self.argument_context( + "disconnectedoperations edgemarketplace listoffers" + ) as c: + c.argument("resource_group_name", arg_type=resource_group_name_type) + c.argument( + "resource_name", type=str, help="Name of the resource to list offers for" + ) + + with self.argument_context("disconnectedoperations edgemarketplace getoffer") as c: + c.argument("resource_group_name", arg_type=resource_group_name_type) + c.argument( + "resource_name", type=str, help="Name of the resource to list offers for" + ) + c.argument("offer_id", type=str, help="Name of the offer") + c.argument("publisher_name", type=str, help="Name of the publisher") + + with self.argument_context( + "disconnectedoperations edgemarketplace packageoffer" + ) as c: + c.argument("resource_group_name", arg_type=resource_group_name_type) + c.argument( + "resource_name", type=str, help="Name of the resource to list offers for" + ) + c.argument("publisher_name", type=str, help="Name of the publisher") + c.argument("offer_id", type=str, help="Name of the offer to package") + c.argument("sku", type=str, help="SKU of the product") + c.argument("version", type=str, help="Version of the product") + c.argument( + "output_folder", + type=str, + help="Drive and directory to save the package to. Example: E:\\ or D:\\packages\\", + ) + c.argument( + "region", + type=str, + help="Azure region to use for marketplace access. If not specified, the current cloud's primary region will be used.", # pylint: disable=line-too-long + required=False, + ) diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/_utils.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/_utils.py new file mode 100644 index 00000000000..2aa45e133d8 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/_utils.py @@ -0,0 +1,181 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +"""Utility functions for disconnected operations module.""" + +import os +import platform +import shutil +import subprocess +from typing import Any, Dict, Optional + +import requests +from knack.log import get_logger + +# Constants +PROVIDER_NAMESPACE = "Microsoft.Edge" +SUB_PROVIDER = "Microsoft.EdgeMarketplace" +API_VERSION = "2023-08-01-preview" +CATALOG_API_VERSION = "2021-06-01" + +logger = get_logger(__name__) + + +# pylint: disable=too-few-public-methods +class OperationResult: + """Standard result object for operations.""" + + def __init__(self, success: bool, message: str = "", data: Any = None, error: str = ""): + self.success = success + self.message = message + self.data = data or {} + self.error = error + + def to_dict(self) -> Dict[str, Any]: + """Convert result to dictionary format.""" + result = { + "status": "succeeded" if self.success else "failed" + } + + if self.message: + result["message"] = self.message + + if self.error: + result["error"] = self.error + + if self.data: + result.update(self.data) + + return result + + +def get_management_endpoint(cli_ctx) -> str: + """Get management endpoint based on cloud configuration.""" + cloud = cli_ctx.cloud + + # Remove ending slash if exists + endpoint = cloud.endpoints.resource_manager + if endpoint.endswith("/"): + endpoint = endpoint[:-1] + + # Append https:// if not exists + if not endpoint.startswith("https://"): + endpoint = "https://" + endpoint + + return endpoint + + +def handle_directory_cleanup(path: str) -> Optional[OperationResult]: + """Clean up existing directory. + + Args: + path: Directory path to clean up + + Returns: + None if successful, OperationResult with error details if failed + """ + if os.path.exists(path): + try: + # Remove directory and all its contents + shutil.rmtree(path) + logger.info("Cleaned up existing directory: %s", path) + return None + except OSError as e: + error_message = f"Failed to clean up directory {path}: {str(e)}" + logger.error(error_message) + return OperationResult( + success=False, + error=error_message, + data={"path": path} + ) + return None + + +def download_file(url: str, file_path: str) -> bool: + """Download a file from URL to specified path. + + Args: + url: Source URL + file_path: Destination file path + + Returns: + True if successful, False otherwise + """ + try: + response = requests.get(url) + if response.status_code == 200: + with open(file_path, "wb") as f: + f.write(response.content) + logger.info("Downloaded file to %s", file_path) + return True + + logger.error("Failed to download file: %s", response.status_code) + return False + except requests.RequestException as e: + logger.error("Error downloading file: %s", str(e)) + return False + + +def is_azcopy_available() -> bool: + """Check if azcopy is available in the system path.""" + # First try using shutil.which which is the proper way to check for executables + if shutil.which("azcopy"): + return True + + # Fallback to trying the command directly + try: + result = subprocess.run( + ["azcopy", "--version"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False + ) + return result.returncode == 0 + except FileNotFoundError: + return False + + +def get_azcopy_install_info() -> Dict[str, str]: + """Get OS-specific AzCopy installation information.""" + system = platform.system().lower() + + if system == 'windows': + return { + "url": "https://aka.ms/downloadazcopy-v10-windows", + "instructions": "Download, extract the ZIP file, and add the extracted folder to your PATH." + } + if system == 'linux': + return { + "url": "https://aka.ms/downloadazcopy-v10-linux", + "instructions": "Download, extract the tar.gz file, and move the azcopy binary to a directory in your PATH." + } + if system == 'darwin': # macOS + return { + "url": "https://aka.ms/downloadazcopy-v10-mac", + "instructions": "Download, extract the .zip file, and move the azcopy binary to a directory in your PATH." + } + + return { + "url": "https://aka.ms/downloadazcopy", + "instructions": "Download and install AzCopy for your platform." + } + + +def construct_resource_uri(subscription_id: str, resource_group_name: str, resource_name: str) -> str: + """Construct a resource URI for disconnected operations. + + Args: + subscription_id: Azure subscription ID + resource_group_name: Resource group name + resource_name: Resource name + + Returns: + Resource URI string + """ + return ( + f"/subscriptions/{subscription_id}" + f"/resourceGroups/{resource_group_name}" + f"/providers/{PROVIDER_NAMESPACE}/disconnectedOperations/{resource_name}" + ) diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/__init__.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/__init__.py new file mode 100644 index 00000000000..5757aea3175 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/__init__.py @@ -0,0 +1,6 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/__init__.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/__init__.py new file mode 100644 index 00000000000..f6acc11aa4e --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/__init__.py @@ -0,0 +1,10 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: skip-file +# flake8: noqa + diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/__cmd_group.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/__cmd_group.py new file mode 100644 index 00000000000..30f0e46625f --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/__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( + "edge", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Edge disconnected operations CLI + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/__init__.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/__init__.py new file mode 100644 index 00000000000..5a9d61963d6 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/__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/disconnectedoperations/aaz/latest/edge/disconnected_operation/__cmd_group.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/__cmd_group.py new file mode 100644 index 00000000000..fce13eff9a7 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/__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( + "edge disconnected-operation", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Disconnected operations cli + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/__init__.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/__init__.py new file mode 100644 index 00000000000..d63ae5a6fc9 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/__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/disconnectedoperations/aaz/latest/edge/disconnected_operation/_list.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/_list.py new file mode 100644 index 00000000000..45101687c88 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/_list.py @@ -0,0 +1,396 @@ +# -------------------------------------------------------------------------------------------- +# 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( + "edge disconnected-operation list", + is_preview=True, +) +class List(AAZCommand): + """List DisconnectedOperation resources + + List DisconnectedOperation resources by subscription ID and resource group + """ + + _aaz_info = { + "version": "2024-12-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/providers/microsoft.edge/disconnectedoperations", "2024-12-01-preview"], + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.edge/disconnectedoperations", "2024-12-01-preview"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.resource_group = AAZResourceGroupNameArg() + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + condition_0 = has_value(self.ctx.subscription_id) and has_value(self.ctx.args.resource_group) is not True + condition_1 = has_value(self.ctx.args.resource_group) and has_value(self.ctx.subscription_id) + if condition_0: + self.DisconnectedOperationsListBySubscription(ctx=self.ctx)() + if condition_1: + self.DisconnectedOperationsListByResourceGroup(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 DisconnectedOperationsListBySubscription(AAZHttpOperation): + CLIENT_TYPE = "MgmtClient" + + def __call__(self, *args, **kwargs): + request = self.make_request() + session = self.client.send_request(request=request, stream=False, **kwargs) + if session.http_response.status_code in [200]: + return self.on_200(session) + + return self.on_error(session.http_response) + + @property + def url(self): + return self.client.format_url( + "/subscriptions/{subscriptionId}/providers/Microsoft.Edge/disconnectedOperations", + **self.url_parameters + ) + + @property + def method(self): + return "GET" + + @property + def error_format(self): + return "MgmtErrorFormat" + + @property + def url_parameters(self): + parameters = { + **self.serialize_url_param( + "subscriptionId", self.ctx.subscription_id, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2024-12-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.location = AAZStrType( + flags={"required": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.tags = AAZDictType() + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.billing_model = AAZStrType( + serialized_name="billingModel", + flags={"read_only": True}, + ) + properties.connection_intent = AAZStrType( + serialized_name="connectionIntent", + flags={"required": True}, + ) + properties.connection_status = AAZStrType( + serialized_name="connectionStatus", + flags={"read_only": True}, + ) + properties.device_version = AAZStrType( + serialized_name="deviceVersion", + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.registration_status = AAZStrType( + serialized_name="registrationStatus", + ) + properties.stamp_id = AAZStrType( + serialized_name="stampId", + 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", + ) + + tags = cls._schema_on_200.value.Element.tags + tags.Element = AAZStrType() + + return cls._schema_on_200 + + class DisconnectedOperationsListByResourceGroup(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/disconnectedOperations", + **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", "2024-12-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.location = AAZStrType( + flags={"required": True}, + ) + _element.name = AAZStrType( + flags={"read_only": True}, + ) + _element.properties = AAZObjectType() + _element.system_data = AAZObjectType( + serialized_name="systemData", + flags={"read_only": True}, + ) + _element.tags = AAZDictType() + _element.type = AAZStrType( + flags={"read_only": True}, + ) + + properties = cls._schema_on_200.value.Element.properties + properties.billing_model = AAZStrType( + serialized_name="billingModel", + flags={"read_only": True}, + ) + properties.connection_intent = AAZStrType( + serialized_name="connectionIntent", + flags={"required": True}, + ) + properties.connection_status = AAZStrType( + serialized_name="connectionStatus", + flags={"read_only": True}, + ) + properties.device_version = AAZStrType( + serialized_name="deviceVersion", + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + properties.registration_status = AAZStrType( + serialized_name="registrationStatus", + ) + properties.stamp_id = AAZStrType( + serialized_name="stampId", + 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", + ) + + tags = cls._schema_on_200.value.Element.tags + tags.Element = AAZStrType() + + return cls._schema_on_200 + + +class _ListHelper: + """Helper class for List""" + + +__all__ = ["List"] diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/image/__cmd_group.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/image/__cmd_group.py new file mode 100644 index 00000000000..79a62e83275 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/image/__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( + "edge disconnected-operation image", + is_preview=True, +) +class __CMDGroup(AAZCommandGroup): + """Disconnected operations image CLI + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/image/__init__.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/image/__init__.py new file mode 100644 index 00000000000..5e75ed17830 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/image/__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_download_uri import * diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/image/_list_download_uri.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/image/_list_download_uri.py new file mode 100644 index 00000000000..2629febe4e2 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge/disconnected_operation/image/_list_download_uri.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( + "edge disconnected-operation image list-download-uri", + is_preview=True, +) +class ListDownloadUri(AAZCommand): + """Get deployment manifest. + """ + + _aaz_info = { + "version": "2024-12-01-preview", + "resources": [ + ["mgmt-plane", "/subscriptions/{}/resourcegroups/{}/providers/microsoft.edge/disconnectedoperations/{}/images/{}/listdownloaduri", "2024-12-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.image_name = AAZStrArg( + options=["--image-name"], + help="The name of the Image", + required=True, + id_part="child_name_1", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9-]{3,24}$", + ), + ) + _args_schema.name = AAZStrArg( + options=["--name"], + help="Name of the resource", + required=True, + id_part="name", + fmt=AAZStrArgFormat( + pattern="^[a-zA-Z0-9][a-zA-Z0-9-_]{2,22}[a-zA-Z0-9]$", + ), + ) + _args_schema.resource_group = AAZResourceGroupNameArg( + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.ImagesListDownloadUri(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 ImagesListDownloadUri(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/disconnectedOperations/{name}/images/{imageName}/listDownloadUri", + **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( + "imageName", self.ctx.args.image_name, + required=True, + ), + **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", "2024-12-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.compatible_versions = AAZListType( + serialized_name="compatibleVersions", + flags={"read_only": True}, + ) + _schema_on_200.download_link = AAZStrType( + serialized_name="downloadLink", + flags={"read_only": True}, + ) + _schema_on_200.link_expiry = AAZStrType( + serialized_name="linkExpiry", + flags={"read_only": True}, + ) + _schema_on_200.provisioning_state = AAZStrType( + serialized_name="provisioningState", + flags={"read_only": True}, + ) + _schema_on_200.release_date = AAZStrType( + serialized_name="releaseDate", + flags={"read_only": True}, + ) + _schema_on_200.release_display_name = AAZStrType( + serialized_name="releaseDisplayName", + flags={"read_only": True}, + ) + _schema_on_200.release_notes = AAZStrType( + serialized_name="releaseNotes", + flags={"read_only": True}, + ) + _schema_on_200.release_type = AAZStrType( + serialized_name="releaseType", + flags={"read_only": True}, + ) + _schema_on_200.release_version = AAZStrType( + serialized_name="releaseVersion", + flags={"read_only": True}, + ) + _schema_on_200.transaction_id = AAZStrType( + serialized_name="transactionId", + flags={"read_only": True}, + ) + + compatible_versions = cls._schema_on_200.compatible_versions + compatible_versions.Element = AAZStrType() + + return cls._schema_on_200 + + +class _ListDownloadUriHelper: + """Helper class for ListDownloadUri""" + + +__all__ = ["ListDownloadUri"] diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/__cmd_group.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/__cmd_group.py new file mode 100644 index 00000000000..727e9377e23 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/__cmd_group.py @@ -0,0 +1,20 @@ +# -------------------------------------------------------------------------------------------- +# 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 * + + +class __CMDGroup(AAZCommandGroup): + """Manage Edge Marketplace + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/__init__.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/__init__.py new file mode 100644 index 00000000000..5a9d61963d6 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/__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/disconnectedoperations/aaz/latest/edge_marketplace/offer/__cmd_group.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/__cmd_group.py new file mode 100644 index 00000000000..42606e38b61 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/__cmd_group.py @@ -0,0 +1,20 @@ +# -------------------------------------------------------------------------------------------- +# 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 * + + +class __CMDGroup(AAZCommandGroup): + """Manage Offer + """ + pass + + +__all__ = ["__CMDGroup"] diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/__init__.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/__init__.py new file mode 100644 index 00000000000..040c7a7d7a7 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/__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 ._generate_access_token import * +from ._get_access_token import * +from ._list import * +from ._show import * diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/_generate_access_token.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/_generate_access_token.py new file mode 100644 index 00000000000..8f9c1a58d24 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/_generate_access_token.py @@ -0,0 +1,247 @@ +# -------------------------------------------------------------------------------------------- +# 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 * + + +class GenerateAccessToken(AAZCommand): + """A long-running resource action. + """ + + _aaz_info = { + "version": "2023-08-01-preview", + "resources": [ + ["mgmt-plane", "/{resourceuri}/providers/microsoft.edgemarketplace/offers/{}/generateaccesstoken", "2023-08-01-preview"], + ] + } + + 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.offer_id = AAZStrArg( + options=["--offer-id"], + help="Id of the offer", + required=True, + ) + _args_schema.resource_uri = AAZStrArg( + options=["--resource-uri"], + help="The fully qualified Azure Resource manager identifier of the resource.", + required=True, + ) + + # define Arg Group "Body" + + _args_schema = cls._args_schema + _args_schema.device_sku = AAZStrArg( + options=["--device-sku"], + arg_group="Body", + help="The device sku.", + ) + _args_schema.device_version = AAZStrArg( + options=["--device-version"], + arg_group="Body", + help="The device sku version.", + ) + _args_schema.edge_market_place_region = AAZStrArg( + options=["--edge-market-place-region"], + arg_group="Body", + help="The region where the disk will be created.", + required=True, + ) + _args_schema.ege_market_place_resource_id = AAZStrArg( + options=["--ege-market-place-resource-id"], + arg_group="Body", + help="The region where the disk will be created.", + ) + _args_schema.hyperv_generation = AAZStrArg( + options=["--hyperv-generation"], + arg_group="Body", + help="The hyperv version.", + ) + _args_schema.market_place_sku = AAZStrArg( + options=["--market-place-sku"], + arg_group="Body", + help="The marketplace sku.", + ) + _args_schema.market_place_sku_version = AAZStrArg( + options=["--market-place-sku-version"], + arg_group="Body", + help="The marketplace sku version.", + ) + _args_schema.publisher_name = AAZStrArg( + options=["--publisher-name"], + arg_group="Body", + help="The name of the publisher.", + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + yield self.OffersGenerateAccessToken(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 OffersGenerateAccessToken(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, + self.on_error, + lro_options={"final-state-via": "location"}, + path_format_arguments=self.url_parameters, + ) + if session.http_response.status_code in [200]: + return self.client.build_lro_polling( + self.ctx.args.no_wait, + session, + self.on_200, + 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( + "/{resourceUri}/providers/Microsoft.EdgeMarketplace/offers/{offerId}/generateAccessToken", + **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( + "offerId", self.ctx.args.offer_id, + required=True, + ), + **self.serialize_url_param( + "resourceUri", self.ctx.args.resource_uri, + skip_quote=True, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-08-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("deviceSku", AAZStrType, ".device_sku") + _builder.set_prop("deviceVersion", AAZStrType, ".device_version") + _builder.set_prop("edgeMarketPlaceRegion", AAZStrType, ".edge_market_place_region", typ_kwargs={"flags": {"required": True}}) + _builder.set_prop("egeMarketPlaceResourceId", AAZStrType, ".ege_market_place_resource_id") + _builder.set_prop("hypervGeneration", AAZStrType, ".hyperv_generation") + _builder.set_prop("marketPlaceSku", AAZStrType, ".market_place_sku") + _builder.set_prop("marketPlaceSkuVersion", AAZStrType, ".market_place_sku_version") + _builder.set_prop("publisherName", AAZStrType, ".publisher_name") + + 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.access_token = AAZStrType( + serialized_name="accessToken", + flags={"required": True}, + ) + _schema_on_200.disk_id = AAZStrType( + serialized_name="diskId", + ) + _schema_on_200.status = AAZStrType() + + return cls._schema_on_200 + + +class _GenerateAccessTokenHelper: + """Helper class for GenerateAccessToken""" + + +__all__ = ["GenerateAccessToken"] diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/_get_access_token.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/_get_access_token.py new file mode 100644 index 00000000000..45da835ab22 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/_get_access_token.py @@ -0,0 +1,188 @@ +# -------------------------------------------------------------------------------------------- +# 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 * + + +class GetAccessToken(AAZCommand): + """get access token. + """ + + _aaz_info = { + "version": "2023-08-01-preview", + "resources": [ + ["mgmt-plane", "/{resourceuri}/providers/microsoft.edgemarketplace/offers/{}/getaccesstoken", "2023-08-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.offer_id = AAZStrArg( + options=["--offer-id"], + help="Id of the offer", + required=True, + ) + _args_schema.resource_uri = AAZStrArg( + options=["--resource-uri"], + help="The fully qualified Azure Resource manager identifier of the resource.", + required=True, + ) + + # define Arg Group "Body" + + _args_schema = cls._args_schema + _args_schema.request_id = AAZStrArg( + options=["--request-id"], + arg_group="Body", + help="The name of the publisher.", + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.OffersGetAccessToken(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 OffersGetAccessToken(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( + "/{resourceUri}/providers/Microsoft.EdgeMarketplace/offers/{offerId}/getAccessToken", + **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( + "offerId", self.ctx.args.offer_id, + required=True, + ), + **self.serialize_url_param( + "resourceUri", self.ctx.args.resource_uri, + skip_quote=True, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-08-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("requestId", AAZStrType, ".request_id", typ_kwargs={"flags": {"required": True}}) + + 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.access_token = AAZStrType( + serialized_name="accessToken", + flags={"required": True}, + ) + _schema_on_200.disk_id = AAZStrType( + serialized_name="diskId", + ) + _schema_on_200.status = AAZStrType() + + return cls._schema_on_200 + + +class _GetAccessTokenHelper: + """Helper class for GetAccessToken""" + + +__all__ = ["GetAccessToken"] diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/_list.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/_list.py new file mode 100644 index 00000000000..14cd016262c --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/_list.py @@ -0,0 +1,380 @@ +# -------------------------------------------------------------------------------------------- +# 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 * + + +class List(AAZCommand): + """List Offer resources by parent + """ + + _aaz_info = { + "version": "2023-08-01-preview", + "resources": [ + ["mgmt-plane", "/{resourceuri}/providers/microsoft.edgemarketplace/offers", "2023-08-01-preview"], + ] + } + + AZ_SUPPORT_PAGINATION = True + + def _handler(self, command_args): + super()._handler(command_args) + return self.build_paging(self._execute_operations, self._output) + + _args_schema = None + + @classmethod + def _build_arguments_schema(cls, *args, **kwargs): + if cls._args_schema is not None: + return cls._args_schema + cls._args_schema = super()._build_arguments_schema(*args, **kwargs) + + # define Arg Group "" + + _args_schema = cls._args_schema + _args_schema.resource_uri = AAZStrArg( + options=["--resource-uri"], + help="The fully qualified Azure Resource manager identifier of the resource.", + required=True, + ) + _args_schema.filter = AAZStrArg( + options=["--filter"], + help="Filter the result list using the given expression.", + ) + _args_schema.maxpagesize = AAZIntArg( + options=["--maxpagesize"], + help="The maximum number of result items per page.", + ) + _args_schema.skip = AAZIntArg( + options=["--skip"], + help="The number of result items to skip.", + default=0, + ) + _args_schema.skip_token = AAZStrArg( + options=["--skip-token"], + help="Skip over when retrieving results.", + ) + _args_schema.top = AAZIntArg( + options=["--top"], + help="The number of result items to return.", + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.OffersList(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 OffersList(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( + "/{resourceUri}/providers/Microsoft.EdgeMarketplace/offers", + **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( + "resourceUri", self.ctx.args.resource_uri, + skip_quote=True, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "$filter", self.ctx.args.filter, + ), + **self.serialize_query_param( + "$skipToken", self.ctx.args.skip_token, + ), + **self.serialize_query_param( + "$top", self.ctx.args.top, + ), + **self.serialize_query_param( + "maxpagesize", self.ctx.args.maxpagesize, + ), + **self.serialize_query_param( + "skip", self.ctx.args.skip, + ), + **self.serialize_query_param( + "api-version", "2023-08-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( + 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.value.Element.properties + properties.content_url = AAZStrType( + serialized_name="contentUrl", + ) + properties.content_version = AAZStrType( + serialized_name="contentVersion", + ) + properties.marketplace_skus = AAZListType( + serialized_name="marketplaceSkus", + ) + properties.offer_content = AAZObjectType( + serialized_name="offerContent", + flags={"required": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + ) + + marketplace_skus = cls._schema_on_200.value.Element.properties.marketplace_skus + marketplace_skus.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.marketplace_skus.Element + _element.catalog_plan_id = AAZStrType( + serialized_name="catalogPlanId", + flags={"required": True}, + ) + _element.description = AAZStrType() + _element.display_name = AAZStrType( + serialized_name="displayName", + ) + _element.display_rank = AAZIntType( + serialized_name="displayRank", + ) + _element.generation = AAZStrType() + _element.long_summary = AAZStrType( + serialized_name="longSummary", + ) + _element.marketplace_sku_id = AAZStrType( + serialized_name="marketplaceSkuId", + flags={"required": True}, + ) + _element.marketplace_sku_versions = AAZListType( + serialized_name="marketplaceSkuVersions", + ) + _element.operating_system = AAZObjectType( + serialized_name="operatingSystem", + ) + _element.summary = AAZStrType() + _element.type = AAZStrType() + + marketplace_sku_versions = cls._schema_on_200.value.Element.properties.marketplace_skus.Element.marketplace_sku_versions + marketplace_sku_versions.Element = AAZObjectType() + + _element = cls._schema_on_200.value.Element.properties.marketplace_skus.Element.marketplace_sku_versions.Element + _element.minimum_download_size_in_mb = AAZIntType( + serialized_name="minimumDownloadSizeInMb", + ) + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.size_on_disk_in_mb = AAZIntType( + serialized_name="sizeOnDiskInMb", + ) + _element.stage_name = AAZStrType( + serialized_name="stageName", + ) + + operating_system = cls._schema_on_200.value.Element.properties.marketplace_skus.Element.operating_system + operating_system.family = AAZStrType() + operating_system.name = AAZStrType( + flags={"required": True}, + ) + operating_system.type = AAZStrType() + + offer_content = cls._schema_on_200.value.Element.properties.offer_content + offer_content.availability = AAZStrType() + offer_content.category_ids = AAZListType( + serialized_name="categoryIds", + ) + offer_content.description = AAZStrType() + offer_content.display_name = AAZStrType( + serialized_name="displayName", + flags={"required": True}, + ) + offer_content.icon_file_uris = AAZObjectType( + serialized_name="iconFileUris", + ) + offer_content.long_summary = AAZStrType( + serialized_name="longSummary", + ) + offer_content.offer_id = AAZStrType( + serialized_name="offerId", + flags={"required": True}, + ) + offer_content.offer_publisher = AAZObjectType( + serialized_name="offerPublisher", + ) + offer_content.offer_type = AAZStrType( + serialized_name="offerType", + ) + offer_content.operating_systems = AAZListType( + serialized_name="operatingSystems", + ) + offer_content.popularity = AAZIntType() + offer_content.release_type = AAZStrType( + serialized_name="releaseType", + ) + offer_content.summary = AAZStrType() + offer_content.support_uri = AAZStrType( + serialized_name="supportUri", + ) + offer_content.terms_and_conditions = AAZObjectType( + serialized_name="termsAndConditions", + ) + + category_ids = cls._schema_on_200.value.Element.properties.offer_content.category_ids + category_ids.Element = AAZStrType() + + icon_file_uris = cls._schema_on_200.value.Element.properties.offer_content.icon_file_uris + icon_file_uris.large = AAZStrType() + icon_file_uris.medium = AAZStrType() + icon_file_uris.small = AAZStrType() + icon_file_uris.wide = AAZStrType() + + offer_publisher = cls._schema_on_200.value.Element.properties.offer_content.offer_publisher + offer_publisher.publisher_display_name = AAZStrType( + serialized_name="publisherDisplayName", + flags={"required": True}, + ) + offer_publisher.publisher_id = AAZStrType( + serialized_name="publisherId", + flags={"required": True}, + ) + + operating_systems = cls._schema_on_200.value.Element.properties.offer_content.operating_systems + operating_systems.Element = AAZStrType() + + terms_and_conditions = cls._schema_on_200.value.Element.properties.offer_content.terms_and_conditions + terms_and_conditions.legal_terms_type = AAZStrType( + serialized_name="legalTermsType", + ) + terms_and_conditions.legal_terms_uri = AAZStrType( + serialized_name="legalTermsUri", + ) + terms_and_conditions.privacy_policy_uri = AAZStrType( + serialized_name="privacyPolicyUri", + ) + + 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/disconnectedoperations/aaz/latest/edge_marketplace/offer/_show.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/_show.py new file mode 100644 index 00000000000..8e08fd91896 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/aaz/latest/edge_marketplace/offer/_show.py @@ -0,0 +1,340 @@ +# -------------------------------------------------------------------------------------------- +# 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 * + + +class Show(AAZCommand): + """Get a Offer + """ + + _aaz_info = { + "version": "2023-08-01-preview", + "resources": [ + ["mgmt-plane", "/{resourceuri}/providers/microsoft.edgemarketplace/offers/{}", "2023-08-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.offer_id = AAZStrArg( + options=["--offer-id"], + help="Id of the offer", + required=True, + ) + _args_schema.resource_uri = AAZStrArg( + options=["--resource-uri"], + help="The fully qualified Azure Resource manager identifier of the resource.", + required=True, + ) + return cls._args_schema + + def _execute_operations(self): + self.pre_operations() + self.OffersGet(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 OffersGet(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( + "/{resourceUri}/providers/Microsoft.EdgeMarketplace/offers/{offerId}", + **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( + "offerId", self.ctx.args.offer_id, + required=True, + ), + **self.serialize_url_param( + "resourceUri", self.ctx.args.resource_uri, + skip_quote=True, + required=True, + ), + } + return parameters + + @property + def query_parameters(self): + parameters = { + **self.serialize_query_param( + "api-version", "2023-08-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( + 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.content_url = AAZStrType( + serialized_name="contentUrl", + ) + properties.content_version = AAZStrType( + serialized_name="contentVersion", + ) + properties.marketplace_skus = AAZListType( + serialized_name="marketplaceSkus", + ) + properties.offer_content = AAZObjectType( + serialized_name="offerContent", + flags={"required": True}, + ) + properties.provisioning_state = AAZStrType( + serialized_name="provisioningState", + ) + + marketplace_skus = cls._schema_on_200.properties.marketplace_skus + marketplace_skus.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.marketplace_skus.Element + _element.catalog_plan_id = AAZStrType( + serialized_name="catalogPlanId", + flags={"required": True}, + ) + _element.description = AAZStrType() + _element.display_name = AAZStrType( + serialized_name="displayName", + ) + _element.display_rank = AAZIntType( + serialized_name="displayRank", + ) + _element.generation = AAZStrType() + _element.long_summary = AAZStrType( + serialized_name="longSummary", + ) + _element.marketplace_sku_id = AAZStrType( + serialized_name="marketplaceSkuId", + flags={"required": True}, + ) + _element.marketplace_sku_versions = AAZListType( + serialized_name="marketplaceSkuVersions", + ) + _element.operating_system = AAZObjectType( + serialized_name="operatingSystem", + ) + _element.summary = AAZStrType() + _element.type = AAZStrType() + + marketplace_sku_versions = cls._schema_on_200.properties.marketplace_skus.Element.marketplace_sku_versions + marketplace_sku_versions.Element = AAZObjectType() + + _element = cls._schema_on_200.properties.marketplace_skus.Element.marketplace_sku_versions.Element + _element.minimum_download_size_in_mb = AAZIntType( + serialized_name="minimumDownloadSizeInMb", + ) + _element.name = AAZStrType( + flags={"required": True}, + ) + _element.size_on_disk_in_mb = AAZIntType( + serialized_name="sizeOnDiskInMb", + ) + _element.stage_name = AAZStrType( + serialized_name="stageName", + ) + + operating_system = cls._schema_on_200.properties.marketplace_skus.Element.operating_system + operating_system.family = AAZStrType() + operating_system.name = AAZStrType( + flags={"required": True}, + ) + operating_system.type = AAZStrType() + + offer_content = cls._schema_on_200.properties.offer_content + offer_content.availability = AAZStrType() + offer_content.category_ids = AAZListType( + serialized_name="categoryIds", + ) + offer_content.description = AAZStrType() + offer_content.display_name = AAZStrType( + serialized_name="displayName", + flags={"required": True}, + ) + offer_content.icon_file_uris = AAZObjectType( + serialized_name="iconFileUris", + ) + offer_content.long_summary = AAZStrType( + serialized_name="longSummary", + ) + offer_content.offer_id = AAZStrType( + serialized_name="offerId", + flags={"required": True}, + ) + offer_content.offer_publisher = AAZObjectType( + serialized_name="offerPublisher", + ) + offer_content.offer_type = AAZStrType( + serialized_name="offerType", + ) + offer_content.operating_systems = AAZListType( + serialized_name="operatingSystems", + ) + offer_content.popularity = AAZIntType() + offer_content.release_type = AAZStrType( + serialized_name="releaseType", + ) + offer_content.summary = AAZStrType() + offer_content.support_uri = AAZStrType( + serialized_name="supportUri", + ) + offer_content.terms_and_conditions = AAZObjectType( + serialized_name="termsAndConditions", + ) + + category_ids = cls._schema_on_200.properties.offer_content.category_ids + category_ids.Element = AAZStrType() + + icon_file_uris = cls._schema_on_200.properties.offer_content.icon_file_uris + icon_file_uris.large = AAZStrType() + icon_file_uris.medium = AAZStrType() + icon_file_uris.small = AAZStrType() + icon_file_uris.wide = AAZStrType() + + offer_publisher = cls._schema_on_200.properties.offer_content.offer_publisher + offer_publisher.publisher_display_name = AAZStrType( + serialized_name="publisherDisplayName", + flags={"required": True}, + ) + offer_publisher.publisher_id = AAZStrType( + serialized_name="publisherId", + flags={"required": True}, + ) + + operating_systems = cls._schema_on_200.properties.offer_content.operating_systems + operating_systems.Element = AAZStrType() + + terms_and_conditions = cls._schema_on_200.properties.offer_content.terms_and_conditions + terms_and_conditions.legal_terms_type = AAZStrType( + serialized_name="legalTermsType", + ) + terms_and_conditions.legal_terms_uri = AAZStrType( + serialized_name="legalTermsUri", + ) + terms_and_conditions.privacy_policy_uri = AAZStrType( + serialized_name="privacyPolicyUri", + ) + + 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/disconnectedoperations/commands.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/commands.py new file mode 100644 index 00000000000..43ab51b66aa --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/commands.py @@ -0,0 +1,94 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + + +from collections import OrderedDict + +from azure.cli.core.commands import CliCommandType + + +def transform_offers_table(result): + if not result: + return result + + # Transform each row while preserving order + transformed = [] + for item in result: + row = OrderedDict( + [ + ("Publisher", item["Publisher"]), + ("Offer", item["Offer"]), + ("SKU", item["SKU"]), + ("Version(s)", item["Versions"]), + ("OS_Type", item["OS_Type"]), + ] + ) + transformed.append(row) + + return transformed + + +def transform_offer_table(result): + if not result: + return result + + # Transform each row while preserving order + transformed = [] + for item in result: + # Format versions to be on separate lines if it's a list/array + versions = item["Versions"] + if isinstance(versions, str): + # Split by comma if it's a comma-separated string + versions = [v.strip() for v in versions.split(",")] + + if isinstance(versions, (list, tuple)): + # Format each version on a new line, preserving the full format + formatted_versions = "\n".join(str(v).strip() for v in versions) + else: + formatted_versions = str(versions) + row = OrderedDict( + [ + ("Publisher", item["Publisher"]), + ("Offer", item["Offer"]), + ("SKU", item["SKU"]), + ("Version(s)", formatted_versions), + ("OS_Type", item["OS_Type"]), + ] + ) + transformed.append(row) + + return transformed + + +def load_command_table(self, _): + custom_command_type = CliCommandType( + operations_tmpl="azure.cli.command_modules.disconnectedoperations.custom#{}" + ) + + # Register the parent command group + with self.command_group( + "edge", + custom_command_type=custom_command_type, + is_preview=True, + ) as g: + pass # No commands directly at this level + + # Register the subgroup and its commands + with self.command_group( + "edge disconnected-operation", + custom_command_type=custom_command_type, + is_preview=True, + ) as g: + g.custom_command( + "offer list", "list_offers", table_transformer=transform_offers_table + ) + g.custom_command( + "offer get", "get_offer", table_transformer=transform_offer_table + ) + g.custom_command("offer package", "package_offer") + + return self.command_table diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/custom.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/custom.py new file mode 100644 index 00000000000..71136bec354 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/custom.py @@ -0,0 +1,813 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- + +# pylint: disable=too-many-lines +# pylint: disable=too-many-statements + +import json +import os +import subprocess +from typing import Any, Dict, List, Optional, Tuple + +import requests +from knack.log import get_logger + +from azure.cli.command_modules.disconnectedoperations._utils import ( + API_VERSION, + CATALOG_API_VERSION, + PROVIDER_NAMESPACE, + SUB_PROVIDER, + OperationResult, + construct_resource_uri, + download_file, + get_azcopy_install_info, + get_management_endpoint, + handle_directory_cleanup, + is_azcopy_available, +) +from azure.cli.core.commands.client_factory import get_subscription_id +from azure.cli.core.util import send_raw_request + +logger = get_logger(__name__) + + +def _download_icons( + icon_uris: Dict[str, str], + icon_path: str, + file_downloader=download_file, # Inject file_downloader + path_exists=os.path.exists, # Inject os.path.exists + file_extension: str = "png", # Added file_extension parameter +) -> None: + """Download icons from URIs to specified directory. + + Args: + icon_uris: Dictionary mapping size to icon URI + icon_path: Path to save icons + file_downloader: Function to download files (for mocking) + path_exists: Function to check if path exists (for mocking) + """ + for size, uri in icon_uris.items(): + file_path = os.path.join(icon_path, f"{size}.{file_extension}") + + # Skip if icon already exists + if path_exists(file_path): + logger.info( + "Icon %s already exists at %s, skipping download", size, file_path + ) + continue + + try: + file_downloader(uri, file_path) + except requests.RequestException as e: + logger.error("Failed to download icon from %s: %s", uri, str(e)) + # Consider raising the exception or returning an error status + + +# pylint: disable=too-many-arguments, too-many-locals +def _prepare_paths_and_metadata( + output_folder: str, + publisher_id: str, + offer_id: str, + sku: str, + version_id: str, + catalog_content: Dict[str, Any], + makedirs=os.makedirs, # Inject os.makedirs + file_open=open, # Inject open +) -> Tuple[Optional[OperationResult], Optional[str], Optional[str]]: + """Prepare directories and save metadata. + + Args: + output_folder: Base output folder + publisher_id: Publisher ID + offer_id: Offer ID + sku: SKU ID + version_id: Version ID + data: Offer data + catalog_content: Catalog content data + makedirs: Function to create directories (for mocking) + file_open: Function to open files (for mocking) + + Returns: + Tuple of (error_result, version_path, icon_path) + """ + # Create base path for this version + base_path = os.path.join( + output_folder, "catalog_artifacts", publisher_id, offer_id, sku + ) + version_level_path = os.path.join(base_path, version_id) + icon_path = os.path.join(base_path, "icons") + + # Clean up existing directory if needed + cleanup_result = handle_directory_cleanup(version_level_path) + if cleanup_result: + return cleanup_result, None, None + + try: + makedirs(icon_path, exist_ok=True) + makedirs(version_level_path, exist_ok=True) + except OSError as e: + error_message = f"Failed to create directories: {e}" + logger.error(error_message) + return OperationResult(success=False, error=error_message), None, None + + # Save metadata.json + metadata_path = os.path.join(base_path, "metadata.json") + try: + with file_open(metadata_path, "w", encoding="utf-8") as f: + json.dump(catalog_content, f, indent=2) + logger.info("Saved metadata to %s", metadata_path) + except OSError as e: + error_message = f"Failed to write metadata file: {e}" + logger.error(error_message) + return OperationResult(success=False, error=error_message), None, None + + return None, version_level_path, icon_path + + +def _find_sku_and_version( + skus: List[Dict[str, Any]], sku: str, version: str +) -> Tuple[Optional[str], Optional[str]]: + """Find matching SKU and version. + + Args: + skus: List of SKUs + sku: SKU ID to find + version: Version to find + + Returns: + Tuple of (version_id, generation) + """ + for _sku in skus: + sku_id = _sku.get("marketplaceSkuId", "") + if sku_id != sku: + continue + + # Store the generation information + generation = _sku.get("generation") + # Get all versions for this SKU + versions = _sku.get("marketplaceSkuVersions", []) + versions = [v for v in versions if v.get("name") == version] + + if not versions: + logger.warning("No matching version found for SKU %s", sku_id) + return None, None + + # print if version and generation are found + print(f"Found VM version: {versions[0].get('name')}") + print(f"VM Generation: {generation}") + version_id = versions[0].get("name") + return version_id, generation + + # If we get here, no matching SKU was found + logger.warning("No matching SKU found: %s", sku) + return None, None + + +def _handle_token_response( + token_response: Dict[str, Any], + output_folder: str, + subprocess_run=subprocess.run, # Inject subprocess.run + is_azcopy_available_func=is_azcopy_available, # Inject is_azcopy_available + get_azcopy_install_info_func=get_azcopy_install_info, # Inject get_azcopy_install_info +) -> Dict[str, Any]: + """Handle token response and download content. + + Args: + token_response: Token response containing access token + output_folder: Folder to save downloaded content + subprocess_run: Function to run subprocesses (for mocking) + is_azcopy_available_func: Function to check AzCopy availability (for mocking) + get_azcopy_install_info_func: Function to get AzCopy install info (for mocking) + + Returns: + Operation result dictionary + """ + download_url = token_response.get("accessToken") + + # Check if azcopy is available + if not is_azcopy_available_func(): + azcopy_info = get_azcopy_install_info_func() + + error_message = ( + f"AzCopy tool not found. Please install AzCopy and make sure it's available in your PATH.\n" + f"Download link: {azcopy_info['url']}\n" + f"Installation: {azcopy_info['instructions']}" + ) + logger.error(error_message) + return OperationResult( + success=False, + error=error_message, + data={"download_url": azcopy_info["url"]}, + ).to_dict() + + # Construct and execute azcopy command + print(f"Executing: azcopy copy [URL] {output_folder} --check-md5 NoCheck") + + # This will display output in real-time + try: + result = subprocess_run( + ["azcopy", "copy", download_url, output_folder, "--check-md5", "NoCheck"], + check=False, # Don't raise exception on non-zero exit + ) + + if result.returncode == 0: + print("Download completed successfully.") + return OperationResult( + success=True, message="Download completed successfully." + ).to_dict() + + error_msg = f"AzCopy failed with return code: {result.returncode}" + logger.error(error_msg) + return OperationResult(success=False, error=error_msg).to_dict() + except OSError as e: + error_msg = f"Failed to execute AzCopy: {e}" + logger.error(error_msg) + return OperationResult(success=False, error=error_msg).to_dict() + + +# pylint: disable=too-many-arguments, too-many-locals +def _process_download_operation( + cmd, + async_operation_url: str, + resource_group_name: str, + output_folder: str, + subscription_id: str, + resource_name: str, + offer_id: str +) -> Dict[str, Any]: + """Process async operation and monitor status. + + Args: + cmd: Command context object + async_operation_url: URL to check operation status + resource_group_name: Name of the resource group + output_folder: Folder to save downloaded content + subscription_id: Azure subscription ID + resource_name: Name of the disconnected operations resource + publisher_name: Marketplace publisher name + offer_id: Marketplace offer ID + + Returns: + Operation result dictionary + """ + from azure.cli.command_modules.disconnectedoperations.aaz.latest.edge_marketplace.offer import ( + GetAccessToken, + ) + + try: + # Get operation status - has to be raw request because this is an async operation + status_response = send_raw_request( + cmd.cli_ctx, + "get", + async_operation_url, + resource="https://management.azure.com", + ) + + if status_response.status_code not in (200, 202): + error_message = f"Status check failed: {status_response.status_code}" + logger.error(error_message) + return OperationResult( + success=False, + error=error_message, + data={ + "operation_url": async_operation_url, + "resource_group_name": resource_group_name, + }, + ).to_dict() + + status_data = status_response.json() + status = status_data.get("status", "").lower() + logger.info("Current status: %s", status) + + # Handle successful completion + if status == "succeeded": + logger.info("VHD download URL generation succeeded") + requestId = status_data.get("properties", {}).get("requestId") + + if not requestId: + error_message = "Download URL not found in response" + logger.error(error_message) + return OperationResult( + success=False, + error=error_message, + data={"resource_group_name": resource_group_name}, + ).to_dict() + + logger.info("Fetched request Id for VHD Download: %s", requestId) + + # Obtaining SAS token using request Id + resource_uri = construct_resource_uri( + subscription_id, resource_group_name, resource_name + ) + + # Create command arguments dictionary + command_args = { + "resource_uri": resource_uri, + "offer_id": offer_id, + "request_id": requestId, + } + + token_command = GetAccessToken(cmd) + result = token_command(command_args=command_args) + return _handle_token_response(result, output_folder) + + # Handle failure + if status == "failed": + error_message = status_data.get("error", {}).get("message", "Unknown error") + logger.error("Operation failed: %s", error_message) + return OperationResult( + success=False, + error=error_message, + data={"resource_group_name": resource_group_name}, + ).to_dict() + + # If we get here, the operation is still in progress + return OperationResult( + success=True, + message=f"Operation status: {status}", + data={"status": "in_progress", "operation_url": async_operation_url}, + ).to_dict() + + except requests.RequestException as e: + error_message = f"Failed to process async operation: {str(e)}" + logger.error(error_message) + return OperationResult( + success=False, + error=error_message, + data={ + "resource_group_name": resource_group_name, + "operation_url": async_operation_url, + }, + ).to_dict() + + +# Main command functions + +# pylint: disable=too-many-arguments, too-many-locals +def package_offer( + cmd, + resource_group_name: str, + resource_name: str, + publisher_name: str, + offer_id: str, + sku: str, + version: str, + output_folder: str, + region: Optional[str] = None +) -> Dict[str, Any]: + """Get details of a specific marketplace offer and download its logos. + + Args: + cmd: Command context object + resource_group_name: Name of the resource group + resource_name: Name of the disconnected operations resource + publisher_name: Marketplace publisher name + offer_id: Marketplace offer ID + sku: Marketplace SKU + version: SKU version + output_folder: Folder to save downloaded content + region: Optional. Azure region to use for marketplace access + + Returns: + Operation result dictionary + """ + management_endpoint = get_management_endpoint(cmd.cli_ctx) + subscription_id = get_subscription_id(cmd.cli_ctx) + + # Construct URL with parameters + url = ( + f"{management_endpoint}" + f"/subscriptions/{subscription_id}" + f"/resourceGroups/{resource_group_name}" + f"/providers/{PROVIDER_NAMESPACE}/disconnectedOperations/{resource_name}" + f"/providers/{SUB_PROVIDER}/offers/{publisher_name}:{offer_id}" + f"?api-version={API_VERSION}" + ) + + catalog_url = ( + f"https://catalogapi.azure.com" + f"/offers/{publisher_name}.{offer_id}" + f"?api-version={CATALOG_API_VERSION}" + ) + + try: + response = send_raw_request( + cmd.cli_ctx, "get", url, resource="https://management.azure.com" + ) + + if response.status_code != 200: + error_message = f"Request failed with status code: {response.status_code}" + logger.error(error_message) + return OperationResult( + success=False, + error=error_message, + data={ + "resource_group_name": resource_group_name, + "response": response.text, + }, + ).to_dict() + + catalog_content = requests.get(catalog_url) + + if catalog_content.status_code != 200: + error_message = f"Catalog request failed with status code: {catalog_content.status_code}" + logger.error(error_message) + return OperationResult( + success=False, + error=error_message, + data={"response": catalog_content.text}, + ).to_dict() + + data = response.json() + catalog_data = catalog_content.json() + offer_content = data.get("properties", {}).get("offerContent", {}) + icon_uris = offer_content.get("iconFileUris", {}) + + # Download logos and metadata if output folder is specified + if output_folder: + publisher_id = offer_content.get("offerPublisher", {}).get( + "publisherId", "" + ) + offer_id = offer_content.get("offerId", "") + skus = data.get("properties", {}).get("marketplaceSkus", []) + + # Find matching SKU and version + version_id, generation = _find_sku_and_version(skus, sku, version) + + if not version_id: + return OperationResult( + success=False, + error=f"Could not find version {version} for SKU {sku}", + ).to_dict() + + # Prepare directories and save metadata + result, version_level_path, icon_path = _prepare_paths_and_metadata( + output_folder, + publisher_id, + offer_id, + sku, + version_id, + catalog_data, + ) + + if result: # Error occurred + return result.to_dict() + + # Download icons + if icon_uris: + _download_icons(icon_uris, icon_path) + + print("Metadata and icons downloaded successfully") + print("Offer details retrieved successfully. Proceeding to download VHD.") + + # Downloading VM image + return _download_vhd( + cmd, + resource_group_name, + resource_name, + publisher_name, + offer_id, + sku, + version, + generation, + version_level_path, + region, + ) + + except requests.RequestException as e: + logger.error("Failed to retrieve offer: %s", str(e)) + return OperationResult( + success=False, + error=str(e), + data={"resource_group_name": resource_group_name}, + ).to_dict() + + +def _download_vhd( + cmd, + resource_group_name: str, + resource_name: str, + publisher_name: str, + offer_id: str, + sku: str, + version: str, + generation: str, + output_folder: str, + region: Optional[str] = None, +) -> Dict[str, Any]: + """Generate access token for VHD download. + + Args: + cmd: Command context object + resource_group_name: Name of the resource group + resource_name: Name of the disconnected operations resource + publisher_name: Marketplace publisher name + offer_id: Marketplace offer ID + sku: Marketplace SKU + version: SKU version + generation: HyperV generation + output_folder: Folder to save downloaded content + region: Optional. Azure region to use for marketplace access + + Returns: + Operation result dictionary + """ + from azure.cli.command_modules.disconnectedoperations.aaz.latest.edge_marketplace.offer import ( + GenerateAccessToken, + ) + + class CustomGenerateAccessToken(GenerateAccessToken): + """Extended version of GenerateAccessToken that captures headers properly""" + + def _output(self, *args, **kwargs): + # Get the original result + result = super()._output(*args, **kwargs) + # Convert to dict if not already + if not isinstance(result, dict): + result = {} + # Add headers if they were captured in the ctx + if hasattr(self.ctx, "captured_headers"): + result["headers"] = self.ctx.captured_headers + return result + + class OffersGenerateAccessToken(GenerateAccessToken.OffersGenerateAccessToken): + def __init__(self, ctx): + super().__init__(ctx) + # Initialize headers on context + if not hasattr(ctx, "captured_headers"): + ctx.captured_headers = {} + + def __call__(self, *args, **kwargs): + # Override the send_request method to capture headers + original_send_request = self.client.send_request + + def intercepted_send_request(request, **kwargs): + # Call the original method + response = original_send_request(request, **kwargs) + # Capture headers from the response + if hasattr(response, "http_response") and hasattr( + response.http_response, "headers" + ): + headers = dict(response.http_response.headers) + # Store headers on the context object + self.ctx.captured_headers.update(headers) + + # Check for the specific header + if "Azure-AsyncOperation" in headers: + logger.info("✅ Captured Azure-AsyncOperation header") + return response + + # Replace the send_request method + self.client.send_request = intercepted_send_request + + try: + # Call the original method to get the poller + return super().__call__(*args, **kwargs) + finally: + # Restore the original send_request method + self.client.send_request = original_send_request + + subscription_id = get_subscription_id(cmd.cli_ctx) + + # Determine region to use + region = _determine_region(cmd, region) + print(f"Using region {region} for marketplace access") + + # Create resource URI + resource_uri = construct_resource_uri( + subscription_id, resource_group_name, resource_name + ) + + command_args = { + # Required URL parameters + "resource_uri": resource_uri, + "offer_id": publisher_name + ":" + offer_id, # Format required by the API + # Required body parameters + "edge_market_place_region": region, + # Optional body parameters as needed + "hyperv_generation": generation, + "market_place_sku": sku, + "market_place_sku_version": version, + "publisher_name": publisher_name, + # For long-running operations, you can set no_wait + "no_wait": False, + } + + try: + # Create and call the command + generate_token_command = CustomGenerateAccessToken(cmd) + poller = generate_token_command(command_args=command_args) + + print("Generating VHD download SAS token... (This might take some time)") + # Wait for completion and get the result + result = poller.result() + + # Try to get headers from either the result or command object + headers = result.get("headers") if isinstance(result, dict) else None + if not headers and hasattr(generate_token_command, "headers"): + headers = generate_token_command.headers + + if headers: + async_op_url = headers.get("Azure-AsyncOperation") + if async_op_url: + # Process the async operation + return _process_download_operation( + cmd, + async_op_url, + resource_group_name, + output_folder, + subscription_id, + resource_name, + offer_id, + ) + + # If we get here, couldn't find the async operation URL + return OperationResult( + success=False, + error="Could not find Azure-AsyncOperation header in response", + data={"resource_group_name": resource_group_name}, + ).to_dict() + + except requests.RequestException as e: + logger.error("Failed to generate access token: %s", str(e)) + return OperationResult( + success=False, + error=str(e), + data={"resource_group_name": resource_group_name}, + ).to_dict() + + +def _determine_region(cmd, region: Optional[str] = None) -> str: + """Determine region to use based on priorities. + + Args: + cmd: Command context object + region: Explicitly provided region + + Returns: + Region to use + """ + # Priority order: + # 1. Explicitly provided region parameter + # 2. Configuration setting + # 3. Current cloud's primary region + # 4. Fallback to eastus + if not region: + # Try to get from configuration + try: + region = cmd.cli_ctx.config.get( + "disconnectedoperations", "default_region", None + ) + except (AttributeError, KeyError): + pass + + # If still not set, try to determine from cloud configuration + if not region: + # Get the current cloud configuration + cloud = cmd.cli_ctx.cloud + # Use the cloud's default region if available, or fall back to eastus + region = getattr(cloud, "primary_endpoint_region", "eastus") + + return region + + +def list_offers( + cmd, resource_group_name: str, resource_name: str +) -> List[Dict[str, str]]: + """List all offers for disconnected operations. + + Args: + cmd: Command context object + resource_group_name: Name of the resource group + resource_name: Name of the disconnected operations resource + + Returns: + List of offer dictionaries + """ + from azure.cli.command_modules.disconnectedoperations.aaz.latest.edge_marketplace.offer import ( + List as OfferList, + ) + + subscription_id = get_subscription_id(cmd.cli_ctx) + + # Construct URL with parameters + resource_uri = construct_resource_uri( + subscription_id, resource_group_name, resource_name + ) + + command_args = { + "resource_uri": resource_uri, + } + + try: + list_command = OfferList(cmd) + result_items_iterator = list_command(command_args=command_args) + result_items = list(result_items_iterator) + + result = [] + for offer in result_items: + offer_content = offer.get("offerContent", {}) + skus = offer.get("marketplaceSkus", []) + + for sku in skus: + versions = sku.get("marketplaceSkuVersions", [])[:] + row = { + "Publisher": offer_content.get("offerPublisher", {}).get( + "publisherId" + ), + "Offer": offer_content.get("offerId"), + "SKU": sku.get("marketplaceSkuId"), + "Versions": f"{len(versions)} {'version' if len(versions) == 1 else 'versions'} available", + "OS_Type": sku.get("operatingSystem", {}).get("type"), + } + result.append(row) + + return result + + except requests.RequestException as e: + logger.error("Failed to retrieve offers: %s", str(e)) + return OperationResult( + success=False, + error=str(e), + data={"resource_group_name": resource_group_name}, + ).to_dict() + + +def get_offer( + cmd, + resource_group_name: str, + resource_name: str, + publisher_name: str, + offer_id: str, +) -> List[Dict[str, str]]: + """Get a specific offer for disconnected operations. + + Args: + cmd: Command context object + resource_group_name: Name of the resource group + resource_name: Name of the disconnected operations resource + publisher_name: Marketplace publisher name + offer_id: Marketplace offer ID + + Returns: + List of offer dictionaries with SKU details + """ + from azure.cli.command_modules.disconnectedoperations.aaz.latest.edge_marketplace.offer import ( + Show, + ) + + subscription_id = get_subscription_id(cmd.cli_ctx) + + # Create resource URI + resource_uri = construct_resource_uri( + subscription_id, resource_group_name, resource_name + ) + + # Set up command arguments + command_args = { + "resource_uri": resource_uri, + "offer_id": publisher_name + ":" + offer_id, # Format required by the API + } + + try: + # Create and call the Show command + show_command = Show(cmd) + show_result = show_command(command_args=command_args) + + result = [] + offer_content = show_result.get("offerContent", {}) + skus = show_result.get("marketplaceSkus", []) + + for sku in skus: + # Get all versions for this SKU + versions = sku.get("marketplaceSkuVersions", [])[:] + + # transform versions and size array into a multi-line string + version_str = ", ".join( + f"{v.get('name')}({v.get('minimumDownloadSizeInMb')}MB)" + for v in versions + ) + + # Create a single row with flattened version info + row = { + "Publisher": offer_content.get("offerPublisher", {}).get("publisherId"), + "Offer": offer_content.get("offerId"), + "SKU": sku.get("marketplaceSkuId"), + "Versions": version_str, + "OS_Type": sku.get("operatingSystem", {}).get("type"), + } + result.append(row) + + return result + + except requests.RequestException as e: + logger.error("Failed to retrieve offer: %s", str(e)) + return OperationResult( + success=False, + error=str(e), + data={"resource_group_name": resource_group_name}, + ).to_dict() diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/tests/__init__.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/tests/__init__.py new file mode 100644 index 00000000000..5757aea3175 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/tests/__init__.py @@ -0,0 +1,6 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/tests/latest/__init__.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/tests/latest/__init__.py new file mode 100644 index 00000000000..5757aea3175 --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/tests/latest/__init__.py @@ -0,0 +1,6 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# +# Code generated by aaz-dev-tools +# -------------------------------------------------------------------------------------------- diff --git a/src/azure-cli/azure/cli/command_modules/disconnectedoperations/tests/latest/test_disconnectedoperations.py b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/tests/latest/test_disconnectedoperations.py new file mode 100644 index 00000000000..872e8659c9f --- /dev/null +++ b/src/azure-cli/azure/cli/command_modules/disconnectedoperations/tests/latest/test_disconnectedoperations.py @@ -0,0 +1,208 @@ +import os +import unittest +from unittest import mock + +import requests + +from azure.cli.command_modules.disconnectedoperations import custom +from azure.cli.command_modules.disconnectedoperations._utils import ( + OperationResult, + get_management_endpoint, + handle_directory_cleanup, +) +from azure.cli.testsdk import ResourceGroupPreparer, ScenarioTest + + +class DisconnectedOperationsUnitTests(unittest.TestCase): + def setUp(self): + self.mock_cmd = mock.MagicMock() + self.mock_cli_ctx = mock.MagicMock() + self.mock_cmd.cli_ctx = self.mock_cli_ctx + self.mock_cloud = mock.MagicMock() + self.mock_cli_ctx.cloud = self.mock_cloud + self.mock_cloud.endpoints.resource_manager = "management.azure.com" + + def test_get_management_endpoint(self): + endpoint = get_management_endpoint(self.mock_cli_ctx) + self.assertEqual(endpoint, "https://management.azure.com") + + @mock.patch('os.path.exists') + @mock.patch('shutil.rmtree') + def test_handle_directory_cleanup_success(self, mock_rmtree, mock_exists): + mock_exists.return_value = True + result = handle_directory_cleanup('/test/path') + mock_exists.assert_called_once_with('/test/path') + mock_rmtree.assert_called_once_with('/test/path') + self.assertIsNone(result) + + @mock.patch('os.path.exists') + @mock.patch('shutil.rmtree') + def test_handle_directory_cleanup_error(self, mock_rmtree, mock_exists): + mock_exists.return_value = True + mock_rmtree.side_effect = OSError("Test error") + result = handle_directory_cleanup('/test/path') + mock_exists.assert_called_once_with('/test/path') + mock_rmtree.assert_called_once_with('/test/path') + self.assertIsInstance(result, OperationResult) + self.assertFalse(result.success) + self.assertIsNotNone(result.error) + + def test_download_icons_success(self): + mock_exists = mock.MagicMock(return_value=False) + mock_download_file = mock.MagicMock() + + icons = {"small": "http://example.com/small.png"} + custom._download_icons( + icons, '/test/icons', + file_downloader=mock_download_file, + path_exists=mock_exists + ) + + expected_path = os.path.join('/test/icons', 'small.png') + mock_download_file.assert_called_once_with("http://example.com/small.png", expected_path) + + def test_download_icons_already_exists(self): + mock_exists = mock.MagicMock(return_value=True) + mock_download_file = mock.MagicMock() + + icons = {"small": "http://example.com/small.png"} + custom._download_icons( + icons, '/test/icons', + file_downloader=mock_download_file, + path_exists=mock_exists + ) + + mock_download_file.assert_not_called() + + def test_download_icons_download_error(self): + mock_exists = mock.MagicMock(return_value=False) + mock_download_file = mock.MagicMock(side_effect=requests.RequestException("Download failed")) + + icons = {"small": "http://example.com/small.png"} + custom._download_icons( + icons, '/test/icons', + file_downloader=mock_download_file, + path_exists=mock_exists + ) + + expected_path = os.path.join('/test/icons', 'small.png') + mock_download_file.assert_called_once_with("http://example.com/small.png", expected_path) + + def test_list_offers_transformation(self): + """Test the transformation logic of list_offers without calling API.""" + # Mock API response data + api_data = [{ + "offerContent": { + "offerPublisher": {"publisherId": "test-publisher"}, + "offerId": "test-offer" + }, + "marketplaceSkus": [{ + "marketplaceSkuId": "test-sku", + "marketplaceSkuVersions": ["1.0", "2.0"], + "operatingSystem": {"type": "Windows"} + }] + }] + + # Test transformation logic only + result = [] + for offer in api_data: + offer_content = offer.get("offerContent", {}) + skus = offer.get("marketplaceSkus", []) + + for sku in skus: + versions = sku.get("marketplaceSkuVersions", [])[:] + row = { + "Publisher": offer_content.get("offerPublisher", {}).get("publisherId"), + "Offer": offer_content.get("offerId"), + "SKU": sku.get("marketplaceSkuId"), + "Versions": f"{len(versions)} {'version' if len(versions) == 1 else 'versions'} available", + "OS_Type": sku.get("operatingSystem", {}).get("type"), + } + result.append(row) + + # Assertions + self.assertEqual(len(result), 1) + self.assertEqual(result[0]["Publisher"], "test-publisher") + self.assertEqual(result[0]["Offer"], "test-offer") + self.assertEqual(result[0]["SKU"], "test-sku") + self.assertEqual(result[0]["Versions"], "2 versions available") + + def test_get_offer_transformation(self): + """Test the transformation logic of get_offer without calling API.""" + # Mock API response data + api_data = { + "offerContent": { + "offerPublisher": {"publisherId": "test-publisher"}, + "offerId": "test-offer" + }, + "marketplaceSkus": [{ + "marketplaceSkuId": "test-sku", + "marketplaceSkuVersions": [ + {"name": "1.0", "minimumDownloadSizeInMb": 100}, + {"name": "2.0", "minimumDownloadSizeInMb": 200} + ], + "operatingSystem": {"type": "Windows"} + }] + } + + # Test transformation logic only + result = [] + offer_content = api_data.get("offerContent", {}) + skus = api_data.get("marketplaceSkus", []) + + for sku in skus: + # Get all versions for this SKU + versions = sku.get("marketplaceSkuVersions", [])[:] + + # Transform versions and size array into a string + version_str = ", ".join( + f"{v.get('name')}({v.get('minimumDownloadSizeInMb')}MB)" + for v in versions + ) + + # Create a single row with flattened version info + row = { + "Publisher": offer_content.get("offerPublisher", {}).get("publisherId"), + "Offer": offer_content.get("offerId"), + "SKU": sku.get("marketplaceSkuId"), + "Versions": version_str, + "OS_Type": sku.get("operatingSystem", {}).get("type"), + } + result.append(row) + + # Assertions + self.assertEqual(len(result), 1) + self.assertEqual(result[0]["Publisher"], "test-publisher") + self.assertEqual(result[0]["Offer"], "test-offer") + self.assertEqual(result[0]["SKU"], "test-sku") + self.assertIn("1.0(100MB)", result[0]["Versions"]) + self.assertIn("2.0(200MB)", result[0]["Versions"]) + + +class DisconnectedOperationsScenarioTests(ScenarioTest): + @ResourceGroupPreparer(name_prefix='cli_test_disconnectedops') + def test_list_offers(self, resource_group): + self.kwargs.update({ + 'resource_group': resource_group, + 'resource': self.create_random_name('edgedevice', 20) + }) + if self.is_live: + self.cmd('az databoxedge device create --resource-group-name {resource_group} -n {resource}') + offers = self.cmd('az edge disconnected-operation offer list --resource-group {resource_group} --resource-name {resource}').get_output_in_json() + self.assertIsNotNone(offers) + + @ResourceGroupPreparer(name_prefix='cli_test_disconnectedops') + def test_get_offer(self, resource_group): + self.kwargs.update({ + 'resource_group': resource_group, + 'resource': self.create_random_name('edgedevice', 20), + 'publisher': 'microsoftwindowsserver', + 'offer': 'windowsserver' + }) + if self.is_live: + result = self.cmd('az edge disconnected-operation offer get --resource-group {resource_group} --resource-name {resource} --publisher-name {publisher} --offer-id {offer}').get_output_in_json() + self.assertIsNotNone(result) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file