From 4a8ca0388b91836220bd8a15f1cb22dff099550a Mon Sep 17 00:00:00 2001 From: hyaish Date: Sun, 15 Sep 2024 11:54:44 +0300 Subject: [PATCH] ACA-1842: Image SKUs support in Ansible collection - Creaing azure_rm_imagesku_info module to get oll of the availble SKU options for VM image. Creating azure_test_image_skus to test the creation of a vm with an image from the SKU list of options Updating the test list in the inventory file with azure_test_imageskus test. Signed-off-by: hyaish --- plugins/modules/azure_rm_imagesku_info.py | 226 ++++++++++++++++++ .../azure_rm_virtualmachine/inventory.yml | 4 + .../tasks/azure_test_image_skus.yml | 98 ++++++++ 3 files changed, 328 insertions(+) create mode 100644 plugins/modules/azure_rm_imagesku_info.py create mode 100644 tests/integration/targets/azure_rm_virtualmachine/tasks/azure_test_image_skus.yml diff --git a/plugins/modules/azure_rm_imagesku_info.py b/plugins/modules/azure_rm_imagesku_info.py new file mode 100644 index 000000000..0f35f2418 --- /dev/null +++ b/plugins/modules/azure_rm_imagesku_info.py @@ -0,0 +1,226 @@ +#!/usr/bin/python +# +# Copyright (c) 2024 +# Hen Yaish +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: azure_rm_imagesku_info + +version_added: "2.4.0" + +short_description: Get compute-related Image SKUs list + +description: + - Get details for compute-related resource Image SKUs. + +options: + location: + description: + - A region supported by current subscription. + type: str + offer: + description: + - The `Offer` refers to the specific product line of the operating system or software. + - The offer usually includes multiple versions or configurations of the product. + type: str + publisher: + description: + - The `Publisher` is the entity that provides the OS image. It could be Microsoft, Canonical (for Ubuntu), Red Hat, etc. + - It is a mandatory field that identifies the provider of the image. + type: str + +extends_documentation_fragment: + - azure.azcollection.azure + +author: + - Hen Yaish (@hyaish) + +''' + +EXAMPLES = ''' +- name: Gather Resource Group info + azure.azcollection.azure_rm_resourcegroup_info: + name: "{{ resource_group }}" + register: rg_info + +- name: List available Image SKUs for 0001-com-ubuntu-server-focal + azure.azcollection.azure_rm_imagesku_info: + location: westus + offer: 0001-com-ubuntu-server-focal + publisher: Canonical + register: available_image_skus_results +''' + +RETURN = ''' +available_skus: + description: + - List of Azure image SKUs for provisioning virtual machines. + returned: always + type: complex + contains: + name: + description: + - The specific SKU name or version. + returned: always + type: str + sample: "20_04-lts" + id: + description: + - The full Azure resource ID for the SKU. + returned: always + type: str + sample: "0001-com-ubuntu-server-focal/Skus/20_04-lts" + location: + description: + - The Azure region where the SKU is available. + returned: always + type: str + sample: "westus" + automatic_os_upgrade_supported: + description: + - Whether automatic OS upgrades are supported for this SKU. + returned: always + type: bool + sample: false + restrictions: + description: + - Restrictions that may apply to the use of this image SKU, such as region limitations or feature incompatibility. + returned: always + type: complex + contains: + type: + description: + - The type of restriction, which could include location-based restrictions or hardware compatibility issues. + type: str + returned: always + sample: "location" + values: + description: + - A list of restricted locations, regions, or zones where the image SKU cannot be used. + type: list + returned: always + sample: ["eastus", "westeurope"] + reason_code: + description: + - The reason for the restriction, such as quota limitations or specific hardware requirements. + type: str + sample: "NotSupported" + restriction_info: + description: + - Additional information about the restrictions, such as unsupported regions or features. + returned: always + type: complex + contains: + locations: + description: + - Locations where this SKU is restricted or unavailable. + type: list + sample: ["eastus"] + zones: + description: + - Availability zones within the region where this SKU is restricted. + type: list + sample: ["1", "2"] +''' + + +from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common import AzureRMModuleBase +try: + from azure.mgmt.compute import ComputeManagementClient + from azure.core.exceptions import HttpResponseError +except ImportError: + # This is handled in azure_rm_common + pass + + +class AzureRMImageskuInfo(AzureRMModuleBase): + def __init__(self): + + self.module_arg_spec = dict( + location=dict(type='str'), + publisher=dict(type='str'), + offer=dict(type='str'), + ) + + self.results = dict( + available_skus=[], + count=0 + ) + self.location = None + self.publisher = None + self.offer = None + super(AzureRMImageskuInfo, self).__init__(derived_arg_spec=self.module_arg_spec, + supports_check_mode=True, + supports_tags=False) + + def exec_module(self, **kwargs): + for key in self.module_arg_spec: + setattr(self, key, kwargs[key]) + + available_skus = self.list_skus() + self.results['available_skus'] = available_skus + self.results['count'] = len(available_skus) + return self.results + + def list_skus(self): + try: + compute_client = self.get_mgmt_svc_client(ComputeManagementClient, + base_url=self._cloud_environment.endpoints.resource_manager, + api_version='2021-07-01') + skus_result = compute_client.virtual_machine_images.list_skus(location=self.location, + publisher_name=self.publisher, + offer=self.offer) + available_skus = [] + + for sku_info in skus_result: + available_skus.append(sku_info.as_dict()) + return available_skus + + except HttpResponseError as e: + # Handle exceptions + raise e + + +def _match_location(loc, locations): + return next((x for x in locations if x.lower() == loc.lower()), None) + + +def _is_sku_available(sku_info, zone): + """ + The SKU is unavailable in the following cases: + 1. regional restriction and the region is restricted + 2. parameter "zone" is input which indicates only showing skus with availability zones. + Meanwhile, zonal restriction and all zones are restricted + """ + is_available = True + is_restrict_zone = False + is_restrict_location = False + if not sku_info.restrictions: + return is_available + for restriction in sku_info.restrictions: + if restriction.reason_code == 'NotAvailableForSubscription': + if restriction.type == 'Zone' and not ( + set(sku_info.location_info[0].zones or []) - set(restriction.restriction_info.zones or [])): + is_restrict_zone = True + if restriction.type == 'Location' and ( + sku_info.location_info[0].location in (restriction.restriction_info.locations or [])): + is_restrict_location = True + if is_restrict_location or (is_restrict_zone and zone): + is_available = False + break + return is_available + + +def main(): + AzureRMImageskuInfo() + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/azure_rm_virtualmachine/inventory.yml b/tests/integration/targets/azure_rm_virtualmachine/inventory.yml index 62525210f..e1cb33410 100644 --- a/tests/integration/targets/azure_rm_virtualmachine/inventory.yml +++ b/tests/integration/targets/azure_rm_virtualmachine/inventory.yml @@ -48,6 +48,10 @@ all: network: 10.42.8.0/24 subnet: 10.42.8.0/28 + azure_test_image_skus: + network: 10.42.3.0/24 + subnet: 10.42.3.0/28 + vars: ansible_connection: local ansible_python_interpreter: "{{ ansible_playbook_python }}" diff --git a/tests/integration/targets/azure_rm_virtualmachine/tasks/azure_test_image_skus.yml b/tests/integration/targets/azure_rm_virtualmachine/tasks/azure_test_image_skus.yml new file mode 100644 index 000000000..ea8236d98 --- /dev/null +++ b/tests/integration/targets/azure_rm_virtualmachine/tasks/azure_test_image_skus.yml @@ -0,0 +1,98 @@ +- name: Set variables + ansible.builtin.include_tasks: setup.yml + +- name: Set variables + ansible.builtin.set_fact: + network: 10.42.0.0/24 + subnet: 10.42.0.0/28 + vm_name: "vm{{ resource_group | hash('md5') | truncate(6, True, '') }}" + network_name: "vnet{{ resource_group | hash('md5') | truncate(6, True, '') }}" + subnet_name: "subnet{{ resource_group | hash('md5') | truncate(6, True, '') }}" + location: eastus + offer: 0001-com-ubuntu-server-focal + publisher: Canonical + +- name: List available Image SKUs for 0001-com-ubuntu-server-focal + azure_rm_imagesku_info: + location: "{{ location }}" + offer: "{{ offer }}" + publisher: "{{ publisher }}" + register: available_image_skus_results + +- name: Set specific 0001-com-ubuntu-server-focal image version + ansible.builtin.set_fact: + specific_image_name: "16.04.202104140" + +- name: Get the first sku from the available skus list + ansible.builtin.set_fact: + first_image_sku_option: "{{ available_image_skus_results.available_skus[0] }}" + +- name: SETUP | Create virtual network + azure_rm_virtualnetwork: + resource_group: "{{ resource_group }}" + name: "{{ network_name }}" + location: "{{ location }}" + address_prefixes: "{{ network }}" + +- name: SETUP | Create subnet + azure_rm_subnet: + resource_group: "{{ resource_group }}" + name: "{{ subnet_name }}" + address_prefix: "{{ subnet }}" + virtual_network: "{{ network_name }}" + +- name: Create VM with first Image SKU result + azure_rm_virtualmachine: + resource_group: "{{ resource_group }}" + name: "{{ vm_name }}" + admin_username: "testuser" + ssh_password_enabled: false + ssh_public_keys: + - path: /home/testuser/.ssh/authorized_keys + key_data: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfoYlIV4lTPZTv7hXaVwQQuqBgGs4yeNRX0SPo2+HQt9u4X7IGwrtXc0nEUm6LfaCikMH58bOL8f20NTGz285kxdFHZRcBXtqmnMz2rXwhK9gwq5h1khc+GzHtdcJXsGA4y0xuaNcidcg04jxAlN/06fwb/VYwwWTVbypNC0gpGEpWckCNm8vlDlA55sU5et0SZ+J0RKVvEaweUOeNbFZqckGPA384imfeYlADppK/7eAxqfBVadVvZG8IJk4yvATgaIENIFj2cXxqu2mQ/Bp5Wr45uApvJsFXmi+v/nkiOEV1QpLOnEwAZo6EfFS4CCQtsymxJCl1PxdJ5LD4ZOtP xiuxi.sun@qq.com" + vm_size: Standard_B1ms + virtual_network: "{{ network_name }}" + image: + offer: "{{ offer }}" + publisher: "{{ publisher }}" + sku: "{{ first_image_sku_option.name }}" + version: "latest" + register: vm_output + +- name: Ensure VM was created using the specific image sku + ansible.builtin.assert: + that: + - vm_output.ansible_facts.azure_vm.storage_profile.image_reference.sku == first_image_sku_option.name + +- name: Delete VM + azure_rm_virtualmachine: + resource_group: "{{ resource_group }}" + name: "{{ vm_name }}" + remove_on_absent: all_autocreated + state: absent + +- name: Destroy subnet + azure_rm_subnet: + resource_group: "{{ resource_group }}" + virtual_network: "{{ network_name }}" + name: "{{ subnet_name }}" + state: absent + +- name: Destroy availability set + azure_rm_availabilityset: + resource_group: "{{ resource_group }}" + name: "{{ availability_set }}" + state: absent + +- name: Destroy storage account + azure_rm_storageaccount: + resource_group: "{{ resource_group }}" + name: "{{ storage_account }}" + force_delete_nonempty: true + state: absent + +- name: Destroy virtual network + azure_rm_virtualnetwork: + resource_group: "{{ resource_group }}" + name: "{{ network_name }}" + state: absent