#!/usr/bin/python
#
# Copyright (c) 2016 Sertac Ozercan, <seozerca@microsoft.com>
#
# 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_virtualmachinescaleset

version_added: "0.1.2"

short_description: Manage Azure virtual machine scale sets

description:
    - Create and update a virtual machine scale set.
    - Note that this module was called M(azure.azcollection.azure_rm_virtualmachine_scaleset) before Ansible 2.8. The usage did not change.

options:
    resource_group:
        description:
            - Name of the resource group containing the virtual machine scale set.
        required: true
        type: str
    name:
        description:
            - Name of the virtual machine.
        required: true
        type: str
    state:
        description:
            - Assert the state of the virtual machine scale set.
            - State C(present) will check that the machine exists with the requested configuration. If the configuration
              of the existing machine does not match, the machine will be updated.
            - State C(absent) will remove the virtual machine scale set.
        default: present
        type: str
        choices:
            - absent
            - present
    location:
        description:
            - Valid Azure location. Defaults to location of the resource group.
        type: str
    short_hostname:
        description:
            - Short host name.
        type: str
    vm_size:
        description:
            - A valid Azure VM size value. For example, C(Standard_D4).
            - The list of choices varies depending on the subscription and location. Check your subscription for available choices.
        type: str
    capacity:
        description:
            - Capacity of VMSS.
        default: 1
        type: int
    tier:
        description:
            - SKU Tier.
        type: str
        choices:
            - Basic
            - Standard
    upgrade_policy:
        description:
            - Upgrade policy.
            - Required when creating the Azure virtual machine scale sets.
        type: str
        choices:
            - Manual
            - Automatic
    priority:
        description:
            - Priority of the VMSS.
            - C(None) is the equivalent of Regular VM.
        type: str
        choices:
            - None
            - Spot
            - Regular
    eviction_policy:
        description:
            - Specifies the eviction policy for the Azure Spot virtual machine.
            - Requires priority to be set to Spot.
        type: str
        choices:
            - Deallocate
            - Delete
    max_price:
        description:
            - Specifies the maximum price you are willing to pay for a Azure Spot VM/VMSS.
            - This price is in US Dollars.
            - C(-1) indicates default price to be up-to on-demand.
            - Requires priority to be set to Spot.
        default: -1
        type: float
    admin_username:
        description:
            - Admin username used to access the host after it is created. Required when creating a VM.
        type: str
    admin_password:
        description:
            - Password for the admin username.
            - Not required if the os_type is Linux and SSH password authentication is disabled by setting I(ssh_password_enabled=false).
        type: str
    ssh_password_enabled:
        description:
            - When the os_type is Linux, setting I(ssh_password_enabled=false) will disable SSH password authentication and require use of SSH keys.
        type: bool
        default: true
    ssh_public_keys:
        description:
            - For I(os_type=Linux) provide a list of SSH keys.
            - Each item in the list should be a dictionary where the dictionary contains two keys, C(path) and C(key_data).
            - Set the C(path) to the default location of the authorized_keys files.
            - On an Enterprise Linux host, for example, the I(path=/home/<admin username>/.ssh/authorized_keys).
              Set C(key_data) to the actual value of the public key.
        type: list
        elements: dict
    image:
        description:
            - Specifies the image used to build the VM.
            - If a string, the image is sourced from a custom image based on the name.
            - If a dict with the keys I(publisher), I(offer), I(sku), and I(version), the image is sourced from a Marketplace image.
               Note that set I(version=latest) to get the most recent version of a given image.
            - If a dict with the keys I(name) and I(resource_group), the image is sourced from a custom image based on the I(name) and I(resource_group) set.
              Note that the key I(resource_group) is optional and if omitted, all images in the subscription will be searched for by I(name).
            - Custom image support was added in Ansible 2.5.
            - For community images, a dict with the keys I(community_gallery_image_id). Specified the community gallery image unique id for vm demployment.
            - The I(community_gallery_image_id) can be fetched from shared gallery image GET call.
            - Required when creating.
        type: raw
    os_disk_caching:
        description:
            - Type of OS disk caching.
        type: str
        choices:
            - ReadOnly
            - ReadWrite
        default: ReadOnly
        aliases:
            - disk_caching
    os_disk_size_gb:
        description:
            - Specifies the size of the operating system disk in gigabytes.
            - This can be used to overwrite the size of the disk in a virtual machine image.
        type: int
    os_disk_encryption_set:
        description:
            - The resource ID of the disk encryption set for the OS disk.
        type: str
    os_type:
        description:
            - Base type of operating system.
        type: str
        choices:
            - Windows
            - Linux
            - windows
            - linux
        default: Linux
    ephemeral_os_disk:
        description:
            - Parameters of ephemeral disk settings that can be specified for operating system disk.
            - Ephemeral OS disk is only supported for VMS Instances using Managed Disk.
        type: bool
    managed_disk_type:
        description:
            - Managed disk type.
        type: str
        choices:
            - Standard_LRS
            - Premium_LRS
            - StandardSSD_LRS
            - UltraSSD_LRS
            - Premium_ZRS
            - StandardSSD_ZRS
    data_disks:
        description:
            - Describes list of data disks.
        type: list
        elements: dict
        suboptions:
            lun:
                description:
                    - The logical unit number for data disk.
                default: '0'
                type: str
            disk_size_gb:
                description:
                    - The initial disk size in GB for blank data disks.
                type: int
            managed_disk_type:
                description:
                    - Managed data disk type.
                type: str
                choices:
                    - Standard_LRS
                    - Premium_LRS
                    - StandardSSD_LRS
                    - UltraSSD_LRS
                    - Premium_ZRS
                    - StandardSSD_ZRS
            caching:
                description:
                    - Type of data disk caching.
                type: str
                choices:
                    - ReadOnly
                    - ReadWrite
                default: ReadOnly
            disk_encryption_set:
                description:
                    - The resource ID of the disk encryption set for the data disks.
                type: str
    virtual_network_resource_group:
        description:
            - When creating a virtual machine, if a specific virtual network from another resource group should be
              used.
            - Use this parameter to specify the resource group to use.
        type: str
    virtual_network_name:
        description:
            - Virtual Network name.
        aliases:
            - virtual_network
        type: str
    subnet_name:
        description:
            - Subnet name.
        type: str
        aliases:
            - subnet
    public_ip_per_vm:
        description:
            - Assign a public IP to each virtual machine of the scale set
        type: bool
        default: False
    load_balancer:
        description:
            - Load balancer name.
        type: str
    application_gateway:
        description:
            - Application gateway name.
        type: str
    remove_on_absent:
        description:
            - When removing a VM using I(state=absent), also remove associated resources.
            - It can be C(all) or a list with any of the following ['network_interfaces', 'virtual_storage', 'public_ips'].
            - Any other input will be ignored.
        type: list
        elements: str
        default: ['all']
    enable_accelerated_networking:
        description:
            - Indicates whether user wants to allow accelerated networking for virtual machines in scaleset being created.
        type: bool
    security_group:
        description:
            - Existing security group with which to associate the subnet.
            - It can be the security group name which is in the same resource group.
            - It can be the resource ID.
            - It can be a dict which contains I(name) and I(resource_group) of the security group.
        aliases:
            - security_group_name
        type: raw
    overprovision:
        description:
            - Specifies whether the Virtual Machine Scale Set should be overprovisioned.
        type: bool
    single_placement_group:
        description:
            - When true this limits the scale set to a single placement group, of max size 100 virtual machines.
        type: bool
        default: False
    plan:
        description:
            - Third-party billing plan for the VM.
        type: dict
        suboptions:
            name:
                description:
                    - Billing plan name.
                required: true
                type: str
            product:
                description:
                    - Product name.
                required: true
                type: str
            publisher:
                description:
                    - Publisher offering the plan.
                required: true
                type: str
            promotion_code:
                description:
                    - Optional promotion code.
                type: str
    zones:
        description:
            - A list of Availability Zones for your virtual machine scale set.
        type: list
        elements: str
    custom_data:
        description:
            - Data which is made available to the virtual machine and used by e.g., C(cloud-init).
            - Many images in the marketplace are not cloud-init ready. Thus, data sent to I(custom_data) would be ignored.
            - If the image you are attempting to use is not listed in
              U(https://docs.microsoft.com/en-us/azure/virtual-machines/linux/using-cloud-init#cloud-init-overview),
              follow these steps U(https://docs.microsoft.com/en-us/azure/virtual-machines/linux/cloudinit-prepare-custom-image).
        type: str
    scale_in_policy:
        description:
            - define the order in which vmss instances are scaled-in
        type: str
        choices:
            - Default
            - NewestVM
            - OldestVM
    terminate_event_timeout_minutes:
        description:
            - timeout time for termination notification event
            - in range between 5 and 15
        type: int
    platform_fault_domain_count:
        description:
            - Fault Domain count for each placement group.
        type: int
        default: 1
    orchestration_mode:
        description:
            - Specifies the orchestration mode for the virtual machine scale set.
            - When I(orchestration_mode=Flexible), I(public_ip_per_vm=True) must be set.
            - When I(orchestration_mode=Flexible), I(platform_fault_domain_count) must be set.
            - When I(orchestration_mode=Flexible), I(single_placement_group=False) must be set.
            - When I(orchestration_mode=Flexible), it cannot be configured I(overprovision).
            - When I(orchestration_mode=Flexible), it cannot be configured I(upgrade_policy) and configured when I(orchestration_mode=Uniform).
        type: str
        choices:
            - Flexible
            - Uniform
        default: Flexible
    security_profile:
        description:
            - Specifies the Security related profile settings for the virtual machine sclaset.
        type: dict
        suboptions:
            encryption_at_host:
                description:
                    - This property can be used by user in the request to enable or disable the Host Encryption for the virtual machine sclaset.
                    - This will enable the encryption for all the disks including Resource/Temp disk at host itself.
                type: bool
            security_type:
                description:
                    - Specifies the SecurityType of the virtual machine sclaset.
                    - It is set as TrustedLaunch to enable UefiSettings.
                type: str
                choices:
                    - TrustedLaunch
            uefi_settings:
                description:
                    - Specifies the security settings like secure boot and vTPM used while creating the virtual machine scalset.
                type: dict
                suboptions:
                    secure_boot_enabled:
                        description:
                            - Specifies whether secure boot should be enabled on the virtual machine sclaset.
                        type: bool
                    v_tpm_enabled:
                        description:
                            - Specifies whether vTPM should be enabled on the virtual machine scalset.
                        type: bool
    application_security_groups:
        description:
            - Specifies an array of references to application security group. Resource ID of the application security group.
        type: list
        elements: str
    private_ip_address_version:
        description:
            - Available from Api-Version 2017-03-30 onwards, it represents whether the specific ipconfiguration is C(IPv4) or C(IPv6).
            - Default is taken as IPv4.
        type: str
        choices:
            - IPv4
            - IPv6

extends_documentation_fragment:
    - azure.azcollection.azure
    - azure.azcollection.azure_tags
    - azure.azcollection.azure_identity_multiple

author:
    - Sertac Ozercan (@sozercan)

'''
EXAMPLES = '''

- name: Create VMSS
  azure_rm_virtualmachinescaleset:
    resource_group: myResourceGroup
    name: testvmss
    vm_size: Standard_DS1_v2
    capacity: 2
    virtual_network_name: testvnet
    upgrade_policy: Manual
    subnet_name: testsubnet
    terminate_event_timeout_minutes: 10
    scale_in_policy: NewestVM
    admin_username: "{{ username }}"
    ssh_password_enabled: false
    ssh_public_keys:
      - path: /home/adminUser/.ssh/authorized_keys
        key_data: < insert your ssh public key here... >
    managed_disk_type: Standard_LRS
    image:
      offer: 0001-com-ubuntu-server-focal
      publisher: canonical
      sku: 20_04-lts-gen2
      version: latest
    data_disks:
      - lun: 0
        disk_size_gb: 64
        caching: ReadWrite
        managed_disk_type: Standard_LRS

- name: Create VMSS with an image that requires plan information
  azure_rm_virtualmachinescaleset:
    resource_group: myResourceGroup
    name: testvmss
    vm_size: Standard_DS1_v2
    capacity: 3
    virtual_network_name: testvnet
    upgrade_policy: Manual
    subnet_name: testsubnet
    admin_username: "{{ username }}"
    ssh_password_enabled: false
    ssh_public_keys:
      - path: /home/adminUser/.ssh/authorized_keys
        key_data: < insert your ssh public key here... >
    managed_disk_type: Standard_LRS
    image:
      offer: cis-ubuntu-linux-1804-l1
      publisher: center-for-internet-security-inc
      sku: Stable
      version: latest
    plan:
      name: cis-ubuntu-linux-1804-l1
      product: cis-ubuntu-linux-1804-l1
      publisher: center-for-internet-security-inc
    data_disks:
      - lun: 0
        disk_size_gb: 64
        caching: ReadWrite
        managed_disk_type: Standard_LRS

- name: Create a VMSS with a custom image
  azure_rm_virtualmachinescaleset:
    resource_group: myResourceGroup
    name: testvmss
    vm_size: Standard_DS1_v2
    capacity: 2
    virtual_network_name: testvnet
    upgrade_policy: Manual
    subnet_name: testsubnet
    admin_username: "{{ username }}"
    admin_password: "{{ password }}"
    managed_disk_type: Standard_LRS
    image: customimage001

- name: Create a VMSS with a community gallery image ID
  azure_rm_virtualmachinescaleset:
    resource_group: myResourceGroup
    name: testvmss
    vm_size: Standard_D8_v4
    admin_username: testuser
    single_placement_group: false
    platform_fault_domain_count: 1
    public_ip_per_vm: true
    ssh_password_enabled: false
    ssh_public_keys:
      - path: /home/testuser/.ssh/authorized_keys
        key_data: "ssh-rsa ********* xiuxi.sun@qq.com"
    virtual_network_name: VMSStestVnet
    subnet_name: VMSStestSubnet
    managed_disk_type: Standard_LRS
    orchestration_mode: Flexible
    os_disk_caching: ReadWrite
    image:
      community_gallery_image_id: "/CommunityGalleries/yellowbrick-fc7e81f1-87dd-4989-9ca8-03743762e873/Images/Ubuntu-5.15.0-1035-azure_22.04"
    data_disks:
      - lun: 0
        disk_size_gb: 64
        caching: ReadWrite
        managed_disk_type: Standard_LRS

- name: Create a VMSS with over 100 instances
  azure_rm_virtualmachinescaleset:
    resource_group: myResourceGroup
    name: testvmss
    vm_size: Standard_DS1_v2
    capacity: 120
    single_placement_group: false
    virtual_network_name: testvnet
    upgrade_policy: Manual
    subnet_name: testsubnet
    admin_username: "{{ username }}"
    admin_password: "{{ password }}"
    managed_disk_type: Standard_LRS
    image: customimage001

- name: Create a VMSS with a custom image from a particular resource group
  azure_rm_virtualmachinescaleset:
    resource_group: myResourceGroup
    name: testvmss
    vm_size: Standard_DS1_v2
    capacity: 2
    virtual_network_name: testvnet
    upgrade_policy: Manual
    subnet_name: testsubnet
    admin_username: "{{ username }}"
    admin_password: "{{ password }}"
    managed_disk_type: Standard_LRS
    image:
      name: customimage001
      resource_group: myResourceGroup

- name: Create a VMSS with Spot Instance
  azure_rm_virtualmachinescaleset:
    resource_group: myResourceGroup
    name: testvmss
    vm_size: Standard_DS1_v2
    capacity: 5
    priority: Spot
    eviction_policy: Deallocate
    virtual_network_name: testvnet
    upgrade_policy: Manual
    subnet_name: testsubnet
    admin_username: "{{ username }}"
    admin_password: "{{ password }}"
    managed_disk_type: Standard_LRS
    image: customimage001

- name: Create VMSS with security group
  azure_rm_virtualmachinescaleset:
    resource_group: "{{ resource_group }}"
    name: testVMSS{{ rpfx }}
    vm_size: Standard_D4s_v3
    admin_username: testuser
    single_placement_group: false
    platform_fault_domain_count: 1
    public_ip_per_vm: true
    ssh_password_enabled: false
    ssh_public_keys:
      - path: /home/testuser/.ssh/authorized_keys
        key_data: "ssh-rsa ****"
    virtual_network_name: VMSStestVnet
    subnet_name: VMSStestSubnet
    managed_disk_type: Standard_LRS
    orchestration_mode: Flexible
    os_disk_caching: ReadWrite
    security_profile:
      uefi_settings:
        secure_boot_enabled: true
        v_tpm_enabled: false
      encryption_at_host: false
      security_type: TrustedLaunch
    image:
      offer: 0001-com-ubuntu-server-jammy
      publisher: Canonical
      sku: 22_04-lts-gen2
      version: latest
    data_disks:
      - lun: 0
        disk_size_gb: 64
        caching: ReadWrite
        managed_disk_type: Standard_LRS
'''

RETURN = '''
azure_vmss:
    description:
        - Facts about the current state of the object.
        - Note that facts are not part of the registered output but available directly.
    returned: always
    type: dict
    sample: {
        "constrained_maximum_capacity": false,
        "etag": "3",
        "id": "/subscriptions/xxx-xxx/resourceGroups/v-xisuRG02/providers/Microsoft.Compute/virtualMachineScaleSets/testVMSStestvmss",
        "location": "eastus",
        "name": "testVMSStestvmss",
        "orchestration_mode": "Flexible",
        "platform_fault_domain_count": 1,
        "provisioning_state": "Succeeded",
        "single_placement_group": false,
        "sku": {
            "capacity": 1,
            "name": "Standard_A1_v2",
            "tier": "Standard"
        },
        "tags": {
            "key2": "value2",
            "key3": "value3"
        },
        "time_created": "2025-04-22T08:03:29.427822Z",
        "type": "Microsoft.Compute/virtualMachineScaleSets",
        "unique_id": "da4393f5-6060-4a0e-8ae5-7616316402b8",
        "upgrade_policy": {
            "mode": "Manual"
        },
        "virtual_machine_profile": {
            "network_profile": {
                "network_api_version": "2020-11-01",
                "network_interface_configurations": [
                    {
                        "auxiliary_mode": "None",
                        "auxiliary_sku": "None",
                        "delete_option": "Delete",
                        "disable_tcp_state_tracking": false,
                        "dns_settings": {
                            "dns_servers": []
                        },
                        "enable_ip_forwarding": false,
                        "ip_configurations": [
                            {
                                "application_gateway_backend_address_pools": [],
                                "application_security_groups": [
                                    {
                                        "id": "/subscriptions/xxx-xxx/resourceGroups/v-xisuRG02/providers/Microsoft.Network/applicationSecurityGroups/apptestvmss02"
                                    },
                                    {
                                        "id": "/subscriptions/xxx-xxx/resourceGroups/v-xisuRG02/providers/Microsoft.Network/applicationSecurityGroups/apptestvmss"
                                    }
                                ],
                                "load_balancer_backend_address_pools": [],
                                "name": "default",
                                "primary": true,
                                "private_ip_address_version": "IPv4",
                                "public_ip_address_configuration": {
                                    "idle_timeout_in_minutes": 4,
                                    "ip_tags": [],
                                    "name": "instancepublicip",
                                    "public_ip_address_version": "IPv4"
                                },
                                "subnet": {
                                    "id": "/subscriptions/xxx-xxx/resourceGroups/v-xisuRG02/providers/Microsoft.Network/virtualNetworks/VMSStestVnet/subnets/VMSStestSubnet"
                                }
                            }
                        ],
                        "name": "testVMSStestvmss",
                        "primary": true
                    }
                ]
            },
            "os_profile": {
                "admin_username": "testuser",
                "allow_extension_operations": true,
                "computer_name_prefix": "testVMSStestvmss",
                "linux_configuration": {
                    "disable_password_authentication": true,
                    "patch_settings": {
                        "assessment_mode": "ImageDefault",
                        "patch_mode": "ImageDefault"
                    },
                    "provision_vm_agent": true,
                    "ssh": {
                        "public_keys": [
                            {
                                "key_data": "ssh-rsa xxxxxxxxxx xx.yy@qq.com",
                                "path": "/home/testuser/.ssh/authorized_keys"
                            }
                        ]
                    }
                },
                "require_guest_provision_signal": true,
                "secrets": []
            },
            "storage_profile": {
                "data_disks": [
                    {
                        "caching": "ReadWrite",
                        "create_option": "Empty",
                        "delete_option": "Delete",
                        "disk_size_gb": 64,
                        "lun": 0,
                        "managed_disk": {
                            "storage_account_type": "Standard_LRS"
                        }
                    }
                ],
                "image_reference": {
                    "offer": "0001-com-ubuntu-server-focal",
                    "publisher": "Canonical",
                    "sku": "20_04-lts",
                    "version": "20.04.202504030"
                },
                "os_disk": {
                    "caching": "ReadWrite",
                    "create_option": "FromImage",
                    "delete_option": "Delete",
                    "disk_size_gb": 30,
                    "managed_disk": {
                        "storage_account_type": "Standard_LRS"
                    },
                    "os_type": "Linux"
                }
            },
            "time_created": "2025-04-22T08:53:17.201851Z"
        }
    }
'''  # NOQA

import base64
import time

try:
    from azure.core.exceptions import ResourceNotFoundError
    from azure.mgmt.core.tools import parse_resource_id
    from azure.core.exceptions import ResourceNotFoundError
    from azure.mgmt.compute.models import (VirtualMachineScaleSetIdentity, UserAssignedIdentitiesValue)

except ImportError:
    # This is handled in azure_rm_common
    pass

from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common import azure_id_to_dict, format_resource_id
from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common_ext import AzureRMModuleBaseExt
from ansible.module_utils.basic import to_native, to_bytes


AZURE_OBJECT_CLASS = 'VirtualMachineScaleSet'

AZURE_ENUM_MODULES = ['azure.mgmt.compute.models']


class AzureRMVirtualMachineScaleSet(AzureRMModuleBaseExt):

    def __init__(self):

        self.module_arg_spec = dict(
            resource_group=dict(type='str', required=True),
            name=dict(type='str', required=True),
            state=dict(choices=['present', 'absent'], default='present', type='str'),
            location=dict(type='str'),
            short_hostname=dict(type='str'),
            vm_size=dict(type='str'),
            tier=dict(type='str', choices=['Basic', 'Standard']),
            capacity=dict(type='int', default=1),
            upgrade_policy=dict(type='str', choices=['Automatic', 'Manual']),
            priority=dict(type='str', choices=['None', 'Spot', 'Regular']),
            eviction_policy=dict(type='str', choices=['Deallocate', 'Delete']),
            max_price=dict(type='float', default=-1),
            admin_username=dict(type='str'),
            admin_password=dict(type='str', no_log=True),
            ssh_password_enabled=dict(type='bool', default=True),
            ssh_public_keys=dict(type='list', elements='dict'),
            image=dict(type='raw'),
            os_disk_caching=dict(type='str', aliases=['disk_caching'], choices=['ReadOnly', 'ReadWrite'],
                                 default='ReadOnly'),
            os_type=dict(type='str', choices=['Linux', 'Windows', 'linux', 'windows'], default='Linux'),
            managed_disk_type=dict(type='str', choices=['Standard_LRS', 'Premium_LRS', 'StandardSSD_LRS', 'UltraSSD_LRS', 'Premium_ZRS', 'StandardSSD_ZRS']),
            os_disk_encryption_set=dict(type='str'),
            data_disks=dict(
                type='list',
                elements='dict',
                options=dict(
                    lun=dict(type='str', default='0'),
                    disk_size_gb=dict(type='int'),
                    caching=dict(type='str', default='ReadOnly', choices=['ReadOnly', 'ReadWrite']),
                    managed_disk_type=dict(
                        type='str',
                        choices=['Standard_LRS', 'Premium_LRS', 'StandardSSD_LRS', 'UltraSSD_LRS', 'Premium_ZRS', 'StandardSSD_ZRS']
                    ),
                    disk_encryption_set=dict(type='str')
                )
            ),
            subnet_name=dict(type='str', aliases=['subnet']),
            public_ip_per_vm=dict(type='bool', default=False),
            load_balancer=dict(type='str'),
            application_gateway=dict(type='str'),
            virtual_network_resource_group=dict(type='str'),
            virtual_network_name=dict(type='str', aliases=['virtual_network']),
            remove_on_absent=dict(type='list', default=['all'], elements='str'),
            enable_accelerated_networking=dict(type='bool'),
            security_group=dict(type='raw', aliases=['security_group_name']),
            overprovision=dict(type='bool'),
            single_placement_group=dict(type='bool', default=False),
            zones=dict(type='list', elements='str'),
            custom_data=dict(type='str'),
            plan=dict(type='dict', options=dict(publisher=dict(type='str', required=True),
                      product=dict(type='str', required=True), name=dict(type='str', required=True),
                      promotion_code=dict(type='str'))),
            scale_in_policy=dict(type='str', choices=['Default', 'OldestVM', 'NewestVM']),
            terminate_event_timeout_minutes=dict(type='int'),
            ephemeral_os_disk=dict(type='bool'),
            orchestration_mode=dict(type='str',
                                    choices=['Uniform', 'Flexible'],
                                    default='Flexible',),
            platform_fault_domain_count=dict(type='int', default=1),
            os_disk_size_gb=dict(type='int'),
            security_profile=dict(
                type='dict',
                options=dict(
                    encryption_at_host=dict(type='bool'),
                    security_type=dict(type='str', choices=['TrustedLaunch']),
                    uefi_settings=dict(
                        type='dict',
                        options=dict(
                            secure_boot_enabled=dict(type='bool'),
                            v_tpm_enabled=dict(type='bool'),
                        )
                    )
                )
            ),
            identity=dict(
                type='dict',
                options=self.managed_identity_multiple_spec
            ),
            application_security_groups=dict(
                type='list',
                elements='str'
            ),
            private_ip_address_version=dict(
                type='str',
                choices=['IPv4', 'IPv6']
            )
        )

        self.resource_group = None
        self.name = None
        self.state = None
        self.location = None
        self.short_hostname = None
        self.vm_size = None
        self.capacity = None
        self.tier = None
        self.upgrade_policy = None
        self.priority = None
        self.eviction_policy = None
        self.admin_username = None
        self.admin_password = None
        self.ssh_password_enabled = None
        self.ssh_public_keys = None
        self.image = None
        self.os_disk_caching = None
        self.managed_disk_type = None
        self.os_disk_encryption_set = None
        self.data_disks = None
        self.os_type = None
        self.subnet_name = None
        self.virtual_network_resource_group = None
        self.virtual_network_name = None
        self.public_ip_per_vm = None
        self.tags = None
        self.differences = None
        self.load_balancer = None
        self.application_gateway = None
        self.enable_accelerated_networking = None
        self.security_group = None
        self.overprovision = None
        self.zones = None
        self.custom_data = None
        self.plan = None
        self.scale_in_policy = None
        self.terminate_event_timeout_minutes = None
        self.ephemeral_os_disk = None
        self.orchestration_mode = None
        self.os_disk_size_gb = None
        self.security_profile = None
        self._managed_identity = None
        self.identity = None
        self.application_security_groups = None
        self.private_ip_address_version = None

        mutually_exclusive = [('load_balancer', 'application_gateway')]
        self.results = dict(
            changed=False,
            actions=[],
            ansible_facts=dict(azure_vmss=None)
        )

        super(AzureRMVirtualMachineScaleSet, self).__init__(
            derived_arg_spec=self.module_arg_spec,
            supports_check_mode=True,
            mutually_exclusive=mutually_exclusive)

    @property
    def managed_identity(self):
        if not self._managed_identity:
            self._managed_identity = {"identity": VirtualMachineScaleSetIdentity,
                                      "user_assigned": UserAssignedIdentitiesValue
                                      }
        return self._managed_identity

    def exec_module(self, **kwargs):

        for key in list(self.module_arg_spec.keys()) + ['tags']:
            setattr(self, key, kwargs[key])

        # make sure options are lower case
        self.remove_on_absent = set([resource.lower() for resource in self.remove_on_absent])

        # convert elements to ints
        self.zones = [int(i) for i in self.zones] if self.zones else None

        # default virtual_network_resource_group to resource_group
        if not self.virtual_network_resource_group:
            self.virtual_network_resource_group = self.resource_group

        changed = False
        results = dict()
        vmss = None
        disable_ssh_password = None
        subnet = None
        image_reference = None
        load_balancer_backend_address_pools = None
        load_balancer_inbound_nat_pools = None
        load_balancer = None
        application_gateway = None
        application_gateway_backend_address_pools = None
        support_lb_change = True
        public_ip_address_configuration = None
        application_security_groups = None
        if self.application_security_groups is not None:
            application_security_groups = [self.compute_models.SubResource(id=item) for item in self.application_security_groups]

        resource_group = self.get_resource_group(self.resource_group)
        if not self.location:
            # Set default location
            self.location = resource_group.location

        if self.custom_data:
            self.custom_data = to_native(base64.b64encode(to_bytes(self.custom_data)))

        if self.state == 'present':
            # Verify parameters and resolve any defaults

            if self.vm_size and not self.vm_size_is_valid():
                self.fail("Parameter error: vm_size {0} is not valid for your subscription and location.".format(
                    self.vm_size
                ))

            # if self.virtual_network_name:
            #     virtual_network = self.get_virtual_network(self.virtual_network_name)

            if self.ssh_public_keys:
                msg = "Parameter error: expecting ssh_public_keys to be a list of type dict where " \
                    "each dict contains keys: path, key_data."
                for key in self.ssh_public_keys:
                    if not isinstance(key, dict):
                        self.fail(msg)
                    if not key.get('path') or not key.get('key_data'):
                        self.fail(msg)

            if self.image and isinstance(self.image, dict):
                if all(key in self.image for key in ('publisher', 'offer', 'sku', 'version')):
                    marketplace_image = self.get_marketplace_image_version()
                    if self.image['version'] == 'latest':
                        self.image['version'] = marketplace_image.name
                        self.log("Using image version {0}".format(self.image['version']))

                    image_reference = self.compute_models.ImageReference(
                        publisher=self.image['publisher'],
                        offer=self.image['offer'],
                        sku=self.image['sku'],
                        version=self.image['version']
                    )
                elif self.image.get('name'):
                    custom_image = True
                    image_reference = self.get_custom_image_reference(
                        self.image.get('name'),
                        self.image.get('resource_group'))
                elif self.image.get('id'):
                    try:
                        image_reference = self.compute_models.ImageReference(id=self.image['id'])
                    except Exception as exc:
                        self.fail("id Error: Cannot get image from the reference id - {0}".format(self.image['id']))
                elif self.image.get('community_gallery_image_id'):
                    try:
                        image_reference = self.compute_models.ImageReference(community_gallery_image_id=self.image['community_gallery_image_id'])
                    except Exception as exc:
                        self.fail("id Error: Cannot get image from the cummunity gallery image id- {0}".format(self.image['community_gallery_image_id']))
                else:
                    self.fail("parameter error: expecting image to contain [publisher, offer, sku, version], [name, resource_group] or [id]")
            elif self.image and isinstance(self.image, str):
                custom_image = True
                image_reference = self.get_custom_image_reference(self.image)
            elif self.image:
                self.fail("parameter error: expecting image to be a string or dict not {0}".format(type(self.image).__name__))

            disable_ssh_password = not self.ssh_password_enabled

            if self.load_balancer:
                load_balancer = self.get_load_balancer(self.load_balancer)
                load_balancer_backend_address_pools = ([self.compute_models.SubResource(id=resource.id)
                                                        for resource in load_balancer.backend_address_pools]
                                                       if load_balancer.backend_address_pools else None)
                load_balancer_inbound_nat_pools = ([self.compute_models.SubResource(id=resource.id)
                                                    for resource in load_balancer.inbound_nat_pools]
                                                   if load_balancer.inbound_nat_pools else None)

            if self.application_gateway:
                application_gateway = self.get_application_gateway(self.application_gateway)
                application_gateway_backend_address_pools = ([self.compute_models.SubResource(id=resource.id)
                                                              for resource in application_gateway.backend_address_pools]
                                                             if application_gateway.backend_address_pools else None)

            if self.public_ip_per_vm:
                public_ip_address_configuration = self.compute_models.VirtualMachineScaleSetPublicIPAddressConfiguration(name='instancepublicip')

        try:
            self.log("Fetching virtual machine scale set {0}".format(self.name))
            vmss = self.compute_client.virtual_machine_scale_sets.get(self.resource_group, self.name)
            self.check_provisioning_state(vmss, self.state)
            vmss_dict = self.serialize_vmss(vmss)

            if self.state == 'present':
                differences = []
                results = vmss_dict
                current_osdisk = vmss_dict['virtual_machine_profile']['storage_profile']['os_disk']
                current_ephemeral = current_osdisk.get('diff_disk_settings', None)
                current_properties = vmss_dict['virtual_machine_profile']

                if self.priority and self.priority != current_properties.get('priority', 'None'):
                    self.fail('VM Priority is not updatable: requested virtual machine priority is {0}'.format(self.priority))
                if self.eviction_policy and \
                   self.eviction_policy != current_properties.get('eviction_policy', None):
                    self.fail('VM Eviction Policy is not updatable: requested virtual machine eviction policy is {0}'.format(self.eviction_policy))
                if self.max_price and \
                   vmss_dict['virtual_machine_profile'].get('billing_profile', None) and \
                   self.max_price != vmss_dict['virtual_machine_profile']['billing_profile'].get('max_price', None):
                    self.fail('VM Maximum Price is not updatable: requested virtual machine maximum price is {0}'.format(self.max_price))

                if self.ephemeral_os_disk and current_ephemeral is None:
                    self.fail('Ephemeral OS disk not updatable: virtual machine scale set ephemeral OS disk is {0}'.format(self.ephemeral_os_disk))
                elif not self.ephemeral_os_disk and current_ephemeral is not None:
                    self.fail('Ephemeral OS disk not updatable: virtual machine scale set ephemeral OS disk is {0}'.format(self.ephemeral_os_disk))

                if self.os_disk_size_gb and \
                   self.os_disk_size_gb != vmss_dict['virtual_machine_profile']['storage_profile']['os_disk']['disk_size_gb']:
                    self.fail('VMSS OS disk size is not updatable: requested virtual machine OS disk size is {0}'.format(self.os_disk_size_gb))

                if self.os_disk_caching and \
                   self.os_disk_caching != vmss_dict['virtual_machine_profile']['storage_profile']['os_disk']['caching']:
                    self.log('CHANGED: virtual machine scale set {0} - OS disk caching'.format(self.name))
                    differences.append('OS Disk caching')
                    changed = True
                    vmss_dict['virtual_machine_profile']['storage_profile']['os_disk']['caching'] = self.os_disk_caching

                if self.capacity and \
                   self.capacity != vmss_dict['sku']['capacity']:
                    self.log('CHANGED: virtual machine scale set {0} - Capacity'.format(self.name))
                    differences.append('Capacity')
                    changed = True
                    vmss_dict['sku']['capacity'] = self.capacity

                if self.data_disks and \
                   len(self.data_disks) != len(vmss_dict['virtual_machine_profile']['storage_profile'].get('data_disks', [])):
                    self.log('CHANGED: virtual machine scale set {0} - Data Disks'.format(self.name))
                    differences.append('Data Disks')
                    changed = True

                if self.upgrade_policy and \
                   self.upgrade_policy != vmss_dict['upgrade_policy']['mode']:
                    self.log('CHANGED: virtual machine scale set {0} - Upgrade Policy'.format(self.name))
                    differences.append('Upgrade Policy')
                    changed = True
                    vmss_dict['upgrade_policy']['mode'] = self.upgrade_policy

                if image_reference and \
                   image_reference.as_dict() != vmss_dict['virtual_machine_profile']['storage_profile']['image_reference']:
                    self.log('CHANGED: virtual machine scale set {0} - Image'.format(self.name))
                    differences.append('Image')
                    changed = True
                    vmss_dict['virtual_machine_profile']['storage_profile']['image_reference'] = image_reference.as_dict()

                if self.identity:
                    update_identity, self.identity = self.update_managed_identity(new_identity=self.identity, curr_identity=vmss_dict.get('identity', dict()))
                    if update_identity:
                        differences.append('Identity')
                        changed = True

                update_tags, self.tags = self.update_tags(vmss_dict.get('tags', dict()))
                if update_tags:
                    differences.append('Tags')
                    changed = True

                if self.overprovision is not None and bool(self.overprovision) != bool(vmss_dict.get('overprovision')):
                    differences.append('overprovision')
                    changed = True

                if bool(self.single_placement_group) != bool(vmss_dict['single_placement_group']):
                    differences.append('single_placement_group')
                    changed = True

                vmss_dict['zones'] = [int(i) for i in vmss_dict['zones']] if 'zones' in vmss_dict and vmss_dict['zones'] else None
                if self.zones != vmss_dict['zones']:
                    self.log("CHANGED: virtual machine scale sets {0} zones".format(self.name))
                    differences.append('Zones')
                    changed = True
                    vmss_dict['zones'] = self.zones

                if self.terminate_event_timeout_minutes:
                    timeout = self.terminate_event_timeout_minutes
                    if timeout < 5 or timeout > 15:
                        self.fail("terminate_event_timeout_minutes should >= 5 and <= 15")
                    iso_8601_format = "PT" + str(timeout) + "M"
                    old = vmss_dict['virtual_machine_profile'].get('scheduled_events_profile', {}).\
                        get('terminate_notification_profile', {}).get('not_before_timeout', "")
                    if old != iso_8601_format:
                        differences.append('terminateNotification')
                        changed = True
                        vmss_dict['virtual_machine_profile'].setdefault('scheduled_events_profile', {})['terminate_notification_profile'] = {
                            'not_before_timeout': iso_8601_format,
                            "enable": 'true'
                        }

                if self.scale_in_policy and self.scale_in_policy != vmss_dict.get('scale_in_policy', {}).get('rules', [""])[0]:
                    self.log("CHANGED: virtual machine sale sets {0} scale in policy".format(self.name))
                    differences.append('scaleInPolicy')
                    changed = True
                    vmss_dict.setdefault('scale_in_policy', {})['rules'] = [self.scale_in_policy]

                nicConfigs = vmss_dict['virtual_machine_profile']['network_profile']['network_interface_configurations']

                backend_address_pool = nicConfigs[0]['ip_configurations'][0].get('load_balancer_backend_address_pools', [])
                backend_address_pool += nicConfigs[0]['ip_configurations'][0].get('application_gateway_backend_address_pools', [])
                lb_or_ag_id = None
                if (len(nicConfigs) != 1 or len(backend_address_pool) != 1):
                    support_lb_change = False  # Currently not support for the vmss contains more than one loadbalancer
                    self.module.warn('Updating more than one load balancer on VMSS is currently not supported')
                else:
                    if load_balancer:
                        lb_or_ag_id = "{0}/".format(load_balancer.id)
                    elif application_gateway:
                        lb_or_ag_id = "{0}/".format(application_gateway.id)

                    backend_address_pool_id = backend_address_pool[0].get('id')
                    if lb_or_ag_id is not None and (bool(lb_or_ag_id) != bool(backend_address_pool_id) or not backend_address_pool_id.startswith(lb_or_ag_id)):
                        differences.append('load_balancer')
                        changed = True

                if self.application_security_groups is not None:
                    for item in nicConfigs[0]['ip_configurations'][0].get('application_security_groups', []):
                        if item['id'] not in self.application_security_groups:
                            self.application_security_groups.append(item['id'])
                    if len(self.application_security_groups) != len(nicConfigs[0]['ip_configurations'][0].get('application_security_groups', [])):
                        changed = True
                        differences.append('application_security_groups')

                if self.custom_data:
                    if self.custom_data != vmss_dict['virtual_machine_profile']['os_profile'].get('custom_data'):
                        differences.append('custom_data')
                        changed = True
                        vmss_dict['virtual_machine_profile']['os_profile']['custom_data'] = self.custom_data

                if self.orchestration_mode and self.orchestration_mode != vmss_dict.get('orchestration_mode'):
                    self.fail("The orchestration_mode parameter cannot be updated!")
                else:
                    self.orchestration_mode = vmss_dict.get('orchestration_mode')

                if self.platform_fault_domain_count and self.platform_fault_domain_count != vmss_dict.get('platform_fault_domain_count'):
                    self.fail("The platform_fault_domain_count parameter cannot be updated!")

                if self.security_profile is not None:
                    update_security_profile = False
                    if 'securityProfile' not in vmss_dict['virtual_machine_profile'].keys():
                        update_security_profile = True
                        differences.append('security_profile')
                    else:
                        if self.security_profile.get('encryption_at_host') is not None:
                            if bool(self.security_profile.get('encryption_at_host')) != \
                                    bool(vmss_dict['virtual_machine_profile']['security_profile']['encryption_at_host']):
                                update_security_profle = True
                            else:
                                self.security_profile['encryption_at_host'] = \
                                    vmss_dict['virtual_machine_profile']['security_profile']['encryption_at_host']
                        if self.security_profile.get('security_type') is not None:
                            if self.security_profile.get('security_type') != \
                                    vmss_dict['virtual_machine_profile']['security_profile']['security_type']:
                                update_security_profile = True
                        if self.security_profile.get('uefi_settings') is not None:
                            if self.security_profile['uefi_settings'].get('secure_boot_enabled') is not None:
                                if bool(self.security_profile['uefi_settings']['secure_boot_enabled']) != \
                                        bool(vmss_dict['virtual_machine_profile']['security_profile']['uefi_settings']['secure_boot_nabled']):
                                    update_security_profile = True
                            else:
                                self.security_profile['uefi_settings']['secure_boot_enabled'] = \
                                    vmss_dict['virtual_machine_profile']['security_profile']['uefi_settings']['secure_boot_enabled']
                            if self.security_profile['uefi_settings'].get('v_tpm_enabled') is not None:
                                if bool(self.security_profile['uefi_settings']['v_tpm_enabled']) != \
                                        bool(vmss_dict['virtual_machine_profile']['security_profile']['uefi_settings']['v_tpm_enabled']):
                                    update_security_profile = True
                            else:
                                self.security_profile['uefi_settings']['v_tpm_enabled'] = \
                                    vmss_dict['virtual_machine_profile']['security_profile']['uefi_settings']['v_tpm_enabled']
                        if update_security_profile:
                            changed = True
                            differences.append('security_profile')

                self.differences = differences

            elif self.state == 'absent':
                self.log("CHANGED: virtual machine scale set {0} exists and requested state is 'absent'".format(self.name))
                results = dict()
                changed = True

        except ResourceNotFoundError:
            self.log('Virtual machine scale set {0} does not exist'.format(self.name))
            if self.state == 'present':
                self.log("CHANGED: virtual machine scale set {0} does not exist but state is 'present'.".format(self.name))
                changed = True
                if self.identity:
                    update_identity, self.identity = self.update_managed_identity(new_identity=self.identity)

        self.results['changed'] = changed
        self.results['ansible_facts']['azure_vmss'] = results

        if self.check_mode:
            return self.results

        if changed:
            if self.state == 'present':
                if not vmss:
                    # Create the VMSS
                    if self.vm_size is None:
                        self.fail("vm size must be set")

                    self.log("Create virtual machine scale set {0}".format(self.name))
                    self.results['actions'].append('Created VMSS {0}'.format(self.name))

                    if self.os_type == 'Linux' or self.os_type == 'linux':
                        if disable_ssh_password and not self.ssh_public_keys:
                            self.fail("Parameter error: ssh_public_keys required when disabling SSH password.")

                    if not self.virtual_network_name:
                        self.fail("virtual network name is required")

                    if self.subnet_name:
                        subnet = self.get_subnet(self.virtual_network_name, self.subnet_name)

                    if not self.short_hostname:
                        self.short_hostname = self.name

                    if not image_reference:
                        self.fail("Parameter error: an image is required when creating a virtual machine.")

                    if self.os_disk_encryption_set:
                        os_disk_encryption_set = self.compute_models.DiskEncryptionSetParameters(id=self.os_disk_encryption_set)
                        managed_disk = self.compute_models.VirtualMachineScaleSetManagedDiskParameters(storage_account_type=self.managed_disk_type,
                                                                                                       disk_encryption_set=os_disk_encryption_set)
                    else:
                        managed_disk = self.compute_models.VirtualMachineScaleSetManagedDiskParameters(storage_account_type=self.managed_disk_type)

                    if self.security_group:
                        nsg = self.parse_nsg()
                        if nsg:
                            self.security_group = self.network_models.NetworkSecurityGroup(id=nsg.get('id'))

                    plan = None
                    if self.plan:
                        plan = self.compute_models.Plan(name=self.plan.get('name'), product=self.plan.get('product'),
                                                        publisher=self.plan.get('publisher'),
                                                        promotion_code=self.plan.get('promotion_code'))

                    os_profile = None
                    if self.admin_username or self.custom_data or self.ssh_public_keys:
                        os_profile = self.compute_models.VirtualMachineScaleSetOSProfile(
                            admin_username=self.admin_username,
                            computer_name_prefix=self.short_hostname,
                            custom_data=self.custom_data
                        )

                    vmss_resource = self.compute_models.VirtualMachineScaleSet(
                        location=self.location,
                        overprovision=self.overprovision,
                        single_placement_group=self.single_placement_group,
                        tags=self.tags,
                        orchestration_mode=self.orchestration_mode,
                        platform_fault_domain_count=self.platform_fault_domain_count,
                        upgrade_policy=self.compute_models.UpgradePolicy(
                            mode=self.upgrade_policy
                        ) if self.upgrade_policy is not None else None,
                        sku=self.compute_models.Sku(
                            name=self.vm_size,
                            capacity=self.capacity,
                            tier=self.tier,
                        ),
                        plan=plan,
                        virtual_machine_profile=self.compute_models.VirtualMachineScaleSetVMProfile(
                            os_profile=os_profile,
                            # pylint: disable=missing-kwoa
                            storage_profile=self.compute_models.VirtualMachineScaleSetStorageProfile(
                                os_disk=self.compute_models.VirtualMachineScaleSetOSDisk(
                                    managed_disk=managed_disk,
                                    create_option=self.compute_models.DiskCreateOptionTypes.from_image,
                                    caching=self.os_disk_caching,
                                    disk_size_gb=self.os_disk_size_gb,
                                    diff_disk_settings=self.compute_models.DiffDiskSettings(option='Local') if self.ephemeral_os_disk else None,
                                ),
                                image_reference=image_reference,
                            ),
                            network_profile=self.compute_models.VirtualMachineScaleSetNetworkProfile(
                                network_interface_configurations=[
                                    self.compute_models.VirtualMachineScaleSetNetworkConfiguration(
                                        name=self.name,
                                        primary=True,
                                        ip_configurations=[
                                            self.compute_models.VirtualMachineScaleSetIPConfiguration(
                                                name='default',
                                                subnet=self.compute_models.ApiEntityReference(
                                                    id=subnet.id
                                                ),
                                                public_ip_address_configuration=public_ip_address_configuration,
                                                primary=True,
                                                load_balancer_backend_address_pools=load_balancer_backend_address_pools,
                                                load_balancer_inbound_nat_pools=load_balancer_inbound_nat_pools,
                                                application_gateway_backend_address_pools=application_gateway_backend_address_pools,
                                                application_security_groups=application_security_groups,
                                                private_ip_address_version=self.private_ip_address_version
                                            )
                                        ],
                                        enable_accelerated_networking=self.enable_accelerated_networking,
                                        network_security_group=self.security_group
                                    )
                                ],
                                network_api_version='2020-11-01' if self.orchestration_mode == 'Flexible' else None
                            )
                        ),
                        zones=self.zones
                    )

                    if self.identity:
                        vmss_resource.identity = self.identity

                    if self.priority == 'Spot':
                        vmss_resource.virtual_machine_profile.priority = self.priority
                        vmss_resource.virtual_machine_profile.eviction_policy = self.eviction_policy
                        vmss_resource.virtual_machine_profile.billing_profile = self.compute_models.BillingProfile(
                            max_price=self.max_price
                        )
                    elif self.priority == 'Regular':
                        vmss_resource.virtual_machine_profile.priority = self.priority

                    if self.scale_in_policy:
                        vmss_resource.scale_in_policy = self.gen_scale_in_policy()

                    if self.terminate_event_timeout_minutes:
                        vmss_resource.virtual_machine_profile.scheduled_events_profile = self.gen_scheduled_event_profile()

                    if self.admin_password:
                        vmss_resource.virtual_machine_profile.os_profile.admin_password = self.admin_password

                    if (self.os_type == 'Linux' or self.os_type == 'linux') and os_profile:
                        vmss_resource.virtual_machine_profile.os_profile.linux_configuration = self.compute_models.LinuxConfiguration(
                            disable_password_authentication=disable_ssh_password
                        )

                    if self.ssh_public_keys:
                        ssh_config = self.compute_models.SshConfiguration()
                        ssh_config.public_keys = \
                            [self.compute_models.SshPublicKey(path=key['path'], key_data=key['key_data']) for key in self.ssh_public_keys]
                        vmss_resource.virtual_machine_profile.os_profile.linux_configuration.ssh = ssh_config

                    if self.data_disks:
                        data_disks = []

                        for data_disk in self.data_disks:
                            if data_disk.get('disk_encryption_set'):
                                data_disk_encryption_set = self.compute_models.DiskEncryptionSetParameters(id=data_disk['disk_encryption_set'])
                                data_disk_managed_disk = self.compute_models.VirtualMachineScaleSetManagedDiskParameters(
                                    storage_account_type=data_disk.get('managed_disk_type', None),
                                    disk_encryption_set=data_disk_encryption_set
                                )
                            else:
                                data_disk_managed_disk = self.compute_models.VirtualMachineScaleSetManagedDiskParameters(
                                    storage_account_type=data_disk.get('managed_disk_type', None)
                                )

                            data_disk['caching'] = data_disk.get(
                                'caching',
                                self.compute_models.CachingTypes.read_only
                            )

                            data_disks.append(self.compute_models.VirtualMachineScaleSetDataDisk(
                                lun=data_disk.get('lun', None),
                                caching=data_disk.get('caching', None),
                                create_option=self.compute_models.DiskCreateOptionTypes.empty,
                                disk_size_gb=data_disk.get('disk_size_gb', None),
                                managed_disk=data_disk_managed_disk,
                            ))

                        vmss_resource.virtual_machine_profile.storage_profile.data_disks = data_disks

                    if self.plan:
                        try:
                            plan_name = self.plan.get('name')
                            plan_product = self.plan.get('product')
                            plan_publisher = self.plan.get('publisher')
                            term = self.marketplace_client.marketplace_agreements.get(
                                offer_type='virtualmachine', publisher_id=plan_publisher, offer_id=plan_product, plan_id=plan_name)
                            term.accepted = True
                            self.marketplace_client.marketplace_agreements.create(
                                offer_type='virtualmachine', publisher_id=plan_publisher, offer_id=plan_product, plan_id=plan_name, parameters=term)
                        except Exception as exc:
                            self.fail(("Error accepting terms for virtual machine {0} with plan {1}. " +
                                       "Only service admin/account admin users can purchase images " +
                                       "from the marketplace. - {2}").format(self.name, self.plan, str(exc)))

                    if self.security_profile is not None:
                        uefi_settings_spec = None
                        if self.security_profile.get('uefi_settings') is not None:
                            uefi_settings_spec = self.compute_models.UefiSettings(
                                secure_boot_enabled=self.security_profile['uefi_settings'].get('secure_boot_enabled'),
                                v_tpm_enabled=self.security_profile['uefi_settings'].get('v_tpm_enabled'),
                            )
                        security_profile = self.compute_models.SecurityProfile(
                            uefi_settings=uefi_settings_spec,
                            encryption_at_host=self.security_profile.get('encryption_at_host'),
                            security_type=self.security_profile.get('security_type'),
                        )
                        vmss_resource.virtual_machine_profile.security_profile = security_profile

                    self.log("Create virtual machine with parameters:")
                    self.create_or_update_vmss(vmss_resource)

                elif self.differences and len(self.differences) > 0:
                    self.log("Update virtual machine scale set {0}".format(self.name))
                    self.results['actions'].append('Updated VMSS {0}'.format(self.name))

                    vmss_resource = self.get_vmss()
                    vmss_resource.virtual_machine_profile.storage_profile.os_disk.caching = self.os_disk_caching
                    vmss_resource.sku.capacity = self.capacity
                    vmss_resource.orchestration_mode = self.orchestration_mode
                    vmss_resource.platform_fault_domain_count = self.platform_fault_domain_count
                    vmss_resource.overprovision = self.overprovision
                    vmss_resource.single_placement_group = self.single_placement_group
                    vmss_resource.tags = self.tags

                    if support_lb_change:
                        if self.load_balancer:
                            vmss_resource.virtual_machine_profile.network_profile.network_interface_configurations[0] \
                                .ip_configurations[0].load_balancer_backend_address_pools = load_balancer_backend_address_pools
                            vmss_resource.virtual_machine_profile.network_profile.network_interface_configurations[0] \
                                .ip_configurations[0].load_balancer_inbound_nat_pools = load_balancer_inbound_nat_pools
                            vmss_resource.virtual_machine_profile.network_profile.network_interface_configurations[0] \
                                .ip_configurations[0].application_gateway_backend_address_pools = None
                        elif self.application_gateway:
                            vmss_resource.virtual_machine_profile.network_profile.network_interface_configurations[0] \
                                .ip_configurations[0].application_gateway_backend_address_pools = application_gateway_backend_address_pools
                            vmss_resource.virtual_machine_profile.network_profile.network_interface_configurations[0] \
                                .ip_configurations[0].load_balancer_backend_address_pools = None
                            vmss_resource.virtual_machine_profile.network_profile.network_interface_configurations[0] \
                                .ip_configurations[0].load_balancer_inbound_nat_pools = None

                    if self.application_security_groups is not None:
                        vmss_resource.virtual_machine_profile.network_profile.network_interface_configurations[0].ip_configurations[0] \
                            .application_security_groups = [self.compute_models.SubResource(id=item) for item in self.application_security_groups]

                    if self.data_disks is not None:
                        data_disks = []
                        for data_disk in self.data_disks:
                            if data_disk.get('disk_encryption_set'):
                                data_disk_encryption_set = self.compute_models.DiskEncryptionSetParameters(id=data_disk['disk_encryption_set'])
                                data_disk_managed_disk = self.compute_models.VirtualMachineScaleSetManagedDiskParameters(
                                    storage_account_type=data_disk.get('managed_disk_type', None),
                                    disk_encryption_set=data_disk_encryption_set
                                )
                            else:
                                data_disk_managed_disk = self.compute_models.VirtualMachineScaleSetManagedDiskParameters(
                                    storage_account_type=data_disk.get('managed_disk_type', None)
                                )
                            data_disks.append(self.compute_models.VirtualMachineScaleSetDataDisk(
                                lun=data_disk['lun'],
                                caching=data_disk['caching'],
                                create_option=self.compute_models.DiskCreateOptionTypes.empty,
                                disk_size_gb=data_disk['disk_size_gb'],
                                managed_disk=data_disk_managed_disk,
                            ))
                        vmss_resource.virtual_machine_profile.storage_profile.data_disks = data_disks

                    if self.security_profile is not None:
                        uefi_settings_spec = None
                        if self.security_profile.get('uefi_settings') is not None:
                            uefi_settings_spec = self.compute_models.UefiSettings(
                                secure_boot_enabled=self.security_profile['uefi_settings'].get('secure_boot_enabled'),
                                v_tpm_enabled=self.security_profile['uefi_settings'].get('v_tpm_enabled'),
                            )
                        security_profile = self.compute_models.SecurityProfile(
                            uefi_settings=uefi_settings_spec,
                            encryption_at_host=self.security_profile.get('encryption_at_host'),
                            security_type=self.security_profile.get('security_type'),
                        )
                        vmss_resource.virtual_machine_profile.security_profile = security_profile

                    if self.scale_in_policy:
                        vmss_resource.scale_in_policy = self.gen_scale_in_policy()

                    if self.terminate_event_timeout_minutes:
                        vmss_resource.virtual_machine_profile.scheduled_events_profile = self.gen_scheduled_event_profile()

                    if self.identity:
                        vmss_resource.identity = self.identity

                    if image_reference is not None:
                        vmss_resource.virtual_machine_profile.storage_profile.image_reference = image_reference

                    self.log("Update virtual machine with parameters:")
                    self.create_or_update_vmss(vmss_resource)

                self.results['ansible_facts']['azure_vmss'] = self.serialize_vmss(self.get_vmss())

            elif self.state == 'absent':
                # delete the VM
                self.log("Delete virtual machine scale set {0}".format(self.name))
                self.results['ansible_facts']['azure_vmss'] = None
                self.delete_vmss(vmss)

        # until we sort out how we want to do this globally
        del self.results['actions']

        return self.results

    def get_vmss(self):
        '''
        Get the VMSS

        :return: VirtualMachineScaleSet object
        '''
        try:
            retry_count = 0
            vmss = self.compute_client.virtual_machine_scale_sets.get(self.resource_group, self.name)
            while True:
                if retry_count == 20:
                    self.fail("Error {0} has a provisioning state of Updating. Expecting state to be Successed.".format(self.name))

                if vmss.provisioning_state != 'Succeeded':
                    retry_count = retry_count + 1
                    time.sleep(150)
                    vmss = self.compute_client.virtual_machine_scale_sets.get(self.resource_group, self.name)
                else:
                    break
            return vmss
        except ResourceNotFoundError as exc:
            self.fail("Error getting virtual machine scale set {0} - {1}".format(self.name, str(exc)))

    def get_virtual_network(self, name):
        try:
            vnet = self.network_client.virtual_networks.get(self.virtual_network_resource_group, name)
            return vnet
        except ResourceNotFoundError as exc:
            self.fail("Error fetching virtual network {0} - {1}".format(name, str(exc)))

    def get_subnet(self, vnet_name, subnet_name):
        self.log("Fetching subnet {0} in virtual network {1}".format(subnet_name, vnet_name))
        try:
            subnet = self.network_client.subnets.get(self.virtual_network_resource_group, vnet_name, subnet_name)
        except Exception as exc:
            self.fail("Error: fetching subnet {0} in virtual network {1} - {2}".format(
                subnet_name,
                vnet_name,
                str(exc)))
        return subnet

    def get_load_balancer(self, id):
        id_dict = parse_resource_id(id)
        try:
            return self.network_client.load_balancers.get(id_dict.get('resource_group', self.resource_group), id_dict.get('name'))
        except ResourceNotFoundError as exc:
            self.fail("Error fetching load balancer {0} - {1}".format(id, str(exc)))

    def get_application_gateway(self, id):
        id_dict = parse_resource_id(id)
        try:
            return self.network_client.application_gateways.get(id_dict.get('resource_group', self.resource_group), id_dict.get('name'))
        except ResourceNotFoundError as exc:
            self.fail("Error fetching application_gateway {0} - {1}".format(id, str(exc)))

    def serialize_vmss(self, vmss):
        '''
        Convert a VirtualMachineScaleSet object to dict.

        :param vm: VirtualMachineScaleSet object
        :return: dict
        '''

        result = self.serialize_obj(vmss, AZURE_OBJECT_CLASS, enum_modules=AZURE_ENUM_MODULES)
        result['id'] = vmss.id
        result['name'] = vmss.name
        result['type'] = vmss.type
        result['location'] = vmss.location
        result['tags'] = vmss.tags

        return result

    def delete_vmss(self, vmss):
        self.log("Deleting virtual machine scale set {0}".format(self.name))
        self.results['actions'].append("Deleted virtual machine scale set {0}".format(self.name))
        try:
            poller = self.compute_client.virtual_machine_scale_sets.begin_delete(self.resource_group, self.name)
            # wait for the poller to finish
            self.get_poller_result(poller)
        except Exception as exc:
            self.fail("Error deleting virtual machine scale set {0} - {1}".format(self.name, str(exc)))

        return True

    def get_marketplace_image_version(self):
        try:
            versions = self.compute_client.virtual_machine_images.list(self.location,
                                                                       self.image['publisher'],
                                                                       self.image['offer'],
                                                                       self.image['sku'])
        except ResourceNotFoundError as exc:
            self.fail("Error fetching image {0} {1} {2} - {3}".format(self.image['publisher'],
                                                                      self.image['offer'],
                                                                      self.image['sku'],
                                                                      str(exc)))
        if versions and len(versions) > 0:
            if self.image['version'] == 'latest':
                return versions[len(versions) - 1]
            for version in versions:
                if version.name == self.image['version']:
                    return version

        self.fail("Error could not find image {0} {1} {2} {3}".format(self.image['publisher'],
                                                                      self.image['offer'],
                                                                      self.image['sku'],
                                                                      self.image['version']))

    def get_custom_image_reference(self, name, resource_group=None):
        try:
            if resource_group:
                vm_images = self.compute_client.images.list_by_resource_group(resource_group)
            else:
                vm_images = self.compute_client.images.list()
        except ResourceNotFoundError as exc:
            self.fail("Error fetching custom images from subscription - {0}".format(str(exc)))

        for vm_image in vm_images:
            if vm_image.name == name:
                self.log("Using custom image id {0}".format(vm_image.id))
                return self.compute_models.ImageReference(id=vm_image.id)

        self.fail("Error could not find image with name {0}".format(name))

    def create_or_update_vmss(self, params):
        try:
            poller = self.compute_client.virtual_machine_scale_sets.begin_create_or_update(self.resource_group, self.name, params)
            self.get_poller_result(poller)
        except Exception as exc:
            self.fail("Error creating or updating virtual machine {0} - {1}".format(self.name, str(exc)))

    def vm_size_is_valid(self):
        '''
        Validate self.vm_size against the list of virtual machine sizes available for the account and location.

        :return: boolean
        '''
        try:
            sizes = self.compute_client.virtual_machine_sizes.list(self.location)
        except ResourceNotFoundError as exc:
            self.fail("Error retrieving available machine sizes - {0}".format(str(exc)))
        for size in sizes:
            if size.name == self.vm_size:
                return True
        return False

    def parse_nsg(self):
        nsg = self.security_group
        resource_group = self.resource_group
        if isinstance(self.security_group, dict):
            nsg = self.security_group.get('name')
            resource_group = self.security_group.get('resource_group', self.resource_group)
        id = format_resource_id(val=nsg,
                                subscription_id=self.subscription_id,
                                namespace='Microsoft.Network',
                                types='networkSecurityGroups',
                                resource_group=resource_group)
        name = azure_id_to_dict(id).get('name')
        return dict(id=id, name=name)

    def gen_scheduled_event_profile(self):
        if self.terminate_event_timeout_minutes is None:
            return None

        scheduledEventProfile = self.compute_models.ScheduledEventsProfile()
        terminationProfile = self.compute_models.TerminateNotificationProfile()
        terminationProfile.not_before_timeout = "PT" + str(self.terminate_event_timeout_minutes) + "M"
        terminationProfile.enable = True
        scheduledEventProfile.terminate_notification_profile = terminationProfile
        return scheduledEventProfile

    def gen_scale_in_policy(self):
        if self.scale_in_policy is None:
            return None

        return self.compute_models.ScaleInPolicy(rules=[self.scale_in_policy])


def main():
    AzureRMVirtualMachineScaleSet()


if __name__ == '__main__':
    main()
