Exemple #1
0
async def present(
    hub,
    ctx,
    name,
    resource_group,
    location,
    sku=None,
    version=None,
    ssl_enforcement=None,
    storage_profile=None,
    login=None,
    login_password=None,
    create_mode="Default",
    force_password=False,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 2.0.0

    Ensure a specified PostgreSQL Server exists.

    :param name: The name of the server.

    :param resource_group: The name of the resource group. The name is case insensitive.

    :param location: The location the resource resides in.

    :param sku: A dictionary representing the SKU (pricing tier) of the server. Parameters include:
        - ``name``: The name of the SKU, typically, tier + family + cores, e.g. B_Gen4_1, GP_Gen5_8.
        - ``tier``: The tier of the particular SKU, e.g. Basic. Possible values include: 'Basic', 'GeneralPurpose',
            and 'MemoryOptimized'.
        - ``capacity``: The scale up/out capacity, representing server's compute units.
        - ``size``: The size code, to be interpreted by resource as appropriate.
        - ``family``: The family of hardware.

    :param version: Server version. Possible values include: '9.5', '9.6', '10', '10.0', '10.2', '11'.

    :param ssl_enforcement: Enable ssl enforcement or not when connect to server.
        Possible values include: 'Enabled', 'Disabled'.

    :param storage_profile: A dictionary representing the storage profile of a server. Parameters include:
        - ``backup_retention_days``: Backup retention days for the server.
        - ``geo_redundant_backup``: Enable Geo-redundant or not for server backup. Possible values include:
            'Enabled', 'Disabled'.
        - ``storage_mb``: Max storage allowed for a server.
        - ``storage_autogrow``: Enable Storage Auto Grow. Possible values include: 'Enabled', 'Disabled'

    :param login: The administrator's login name of a server. Can only be specified when the server is being created
        (and is required for creation).

    :param login_password: The password of the administrator login.

    :param force_password: A Boolean flag that represents whether or not the password should be updated. If it is set
        to True, then the password will be updated if the server already exists. If it is set to False, then the
        password will not be updated unless other parameters also need to be updated. Defaults to False.

    :param tags: A dictionary of strings can be passed as tag metadata to the server.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure server exists:
            azurerm.postgresql.server.present:
                - name: my_server
                - resource_group: my_rg
                - location: my_location
                - login: my_login
                - login_password: my_password
                - tags:
                    contact_name: Elmer Fudd Gantry
                - connection_auth: {{ profile }}

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    server = await hub.exec.azurerm.postgresql.server.get(
        ctx=ctx,
        name=name,
        resource_group=resource_group,
        azurerm_log_level="info",
        **connection_auth,
    )

    if "error" not in server:
        action = "update"

        if tags:
            tag_changes = differ.deep_diff(server.get("tags", {}), tags or {})
            if tag_changes:
                ret["changes"]["tags"] = tag_changes

        if sku:
            sku_changes = differ.deep_diff(server.get("sku", {}), sku)
            if sku_changes:
                ret["changes"]["sku"] = sku_changes

        if storage_profile:
            profile_changes = differ.deep_diff(
                server.get("storage_profile", {}), storage_profile)
            if profile_changes:
                ret["changes"]["storage_profile"] = profile_changes

        if version:
            if version != server.get("version"):
                ret["changes"]["version"] = {
                    "old": server.get("version"),
                    "new": version,
                }

        if ssl_enforcement:
            if ssl_enforcement != server.get("ssl_enforcement"):
                ret["changes"]["ssl_enforcement"] = {
                    "old": server.get("ssl_enforcement"),
                    "new": ssl_enforcement,
                }

        if force_password:
            ret["changes"]["administrator_login_password"] = {
                "new": "REDACTED"
            }
        elif ret["changes"]:
            ret["changes"]["administrator_login_password"] = {
                "new": "REDACTED"
            }

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Server {0} is already present.".format(name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Server {0} would be updated.".format(name)
            return ret

    else:
        ret["changes"] = {
            "old": {},
            "new": {
                "name": name,
                "resource_group": resource_group,
                "location": location,
                "create_mode": create_mode,
            },
        }

        if tags:
            ret["changes"]["new"]["tags"] = tags
        if sku:
            ret["changes"]["new"]["sku"] = sku
        if version:
            ret["changes"]["new"]["version"] = version
        if create_mode:
            ret["changes"]["new"]["create_mode"] = create_mode
        if ssl_enforcement:
            ret["changes"]["new"]["ssl_enforcement"] = ssl_enforcement
        if storage_profile:
            ret["changes"]["new"]["storage_profile"] = storage_profile
        if login:
            ret["changes"]["new"]["administrator_login"] = login
        if login_password:
            ret["changes"]["new"][
                "administrator_login_password"] = "******"  # nosec

    if ctx["test"]:
        ret["comment"] = "Server {0} would be created.".format(name)
        ret["result"] = None
        return ret

    server_kwargs = kwargs.copy()
    server_kwargs.update(connection_auth)

    if action == "create":
        server = await hub.exec.azurerm.postgresql.server.create(
            ctx=ctx,
            name=name,
            resource_group=resource_group,
            location=location,
            sku=sku,
            version=version,
            ssl_enforcement=ssl_enforcement,
            storage_profile=storage_profile,
            login=login,
            login_password=login_password,
            create_mode=create_mode,
            tags=tags,
            **server_kwargs,
        )
    else:
        server = await hub.exec.azurerm.postgresql.server.update(
            ctx=ctx,
            name=name,
            resource_group=resource_group,
            sku=sku,
            version=version,
            ssl_enforcement=ssl_enforcement,
            storage_profile=storage_profile,
            login_password=login_password,
            tags=tags,
            **server_kwargs,
        )

    if "error" not in server:
        ret["result"] = True
        ret["comment"] = f"Server {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} Server {1}! ({2})".format(
        action, name, server.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #2
0
async def present(
    hub, ctx, name, resource_group, tags=None, connection_auth=None, **kwargs,
):
    """
    .. versionadded:: 4.0.0

    Ensure the specified user assigned identity exists.

    :param name: The name of the user assigned identity..

    :param resource_group: The resource group assigned to the user assigned identity.

    :param tags: A dictionary of strings can be passed as tag metadata to the user assigned identity object.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure user assigned identity exists:
            azurerm.managed_service_identity.user_assigned_identity.present:
                - name: test_identity
                - resource_group: test_group
                - tags:
                    contact_name: Elmer Fudd Gantry

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret[
                "comment"
            ] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    identity = await hub.exec.azurerm.managed_service_identity.user_assigned_identity.get(
        ctx, name, resource_group, azurerm_log_level="info", **connection_auth
    )

    if "error" not in identity:
        action = "update"

        # tag changes
        tag_changes = differ.deep_diff(identity.get("tags", {}), tags or {})
        if tag_changes:
            ret["changes"]["tags"] = tag_changes

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "User assigned identity {0} is already present.".format(
                name
            )
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "User assigned identity {0} would be updated.".format(name)
            return ret

    if ctx["test"]:
        ret["comment"] = "User assigned identity {0} would be created.".format(name)
        ret["result"] = None
        return ret

    identity_kwargs = kwargs.copy()
    identity_kwargs.update(connection_auth)

    identity = await hub.exec.azurerm.managed_service_identity.user_assigned_identity.create_or_update(
        ctx=ctx, name=name, resource_group=resource_group, tags=tags, **identity_kwargs,
    )

    if action == "create":
        ret["changes"] = {"old": {}, "new": identity}

    if "error" not in identity:
        ret["result"] = True
        ret["comment"] = f"User assigned identity {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} user assigned identity {1}! ({2})".format(
        action, name, identity.get("error")
    )
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #3
0
async def present(
    hub,
    ctx,
    name,
    resource_group,
    location,
    tenant_id,
    sku,
    access_policies=None,
    vault_uri=None,
    create_mode=None,
    enabled_for_deployment=None,
    enabled_for_disk_encryption=None,
    enabled_for_template_deployment=None,
    enable_soft_delete=None,
    soft_delete_retention=None,
    enable_purge_protection=None,
    enable_rbac_authorization=None,
    network_acls=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 2.0.0

    .. versionchanged:: 4.0.0

    Ensure a specified keyvault exists.

    :param name: The name of the vault.

    :param resource_group: The name of the resource group to which the vault belongs.

    :param location: The supported Azure location where the key vault should be created.

    :param tenant_id: The Azure Active Direction tenant ID that should be used for authenticating requests to
        the key vault.

    :param sku: The SKU name to specify whether the key vault is a standard vault or a premium vault. Possible
        values include: 'standard' and 'premium'.

    :param access_policies: A list of 0 to 16 dictionaries that represent AccessPolicyEntry objects. The
        AccessPolicyEntry objects represent identities that have access to the key vault. All identities in the
        list must use the same tenant ID as the key vault's tenant ID. When createMode is set to "recover", access
        policies are not required. Otherwise, access policies are required. Valid parameters are:

        - ``tenant_id``: (Required) The Azure Active Directory tenant ID that should be used for authenticating
          requests to the key vault.
        - ``object_id``: (Required) The object ID of a user, service principal, or security group in the Azure Active
          Directory tenant for the vault. The object ID must be unique for the list of access policies.
        - ``application_id``: (Optional) Application ID of the client making request on behalf of a principal.
        - ``permissions``: (Required) A dictionary representing permissions the identity has for keys, secrets, and
          certifications. Valid parameters include:

          - ``keys``: A list that represents permissions to keys. Possible values include: 'backup', 'create',
            'decrypt', 'delete', 'encrypt', 'get', 'import_enum', 'list', 'purge', 'recover', 'restore', 'sign',
            'unwrap_key', 'update', 'verify', and 'wrap_key'.
          - ``secrets``: A list that represents permissions to secrets. Possible values include: 'backup', 'delete',
            'get', 'list', 'purge', 'recover', 'restore', and 'set'.
          - ``certificates``: A list that represents permissions to certificates. Possible values include: 'create',
            'delete', 'deleteissuers', 'get', 'getissuers', 'import_enum', 'list', 'listissuers', 'managecontacts',
            'manageissuers', 'purge', 'recover', 'setissuers', and 'update'.
          - ``storage``: A list that represents permissions to storage accounts. Possible values include: 'backup',
            'delete', 'deletesas', 'get', 'getsas', 'list', 'listsas', 'purge', 'recover', 'regeneratekey',
            'restore', 'set', 'setsas', and 'update'.

    :param vault_uri: The URI of the vault for performing operations on keys and secrets.

    :param create_mode: The vault's create mode to indicate whether the vault needs to be recovered or not.
        Possible values include: 'recover' and 'default'.

    :param enabled_for_deployment: A boolean value specifying whether Azure Virtual Machines are permitted to
        retrieve certificates stored as secrets from the key vault.

    :param enabled_for_disk_encryption: A boolean value specifying whether Azure Disk Encrpytion is permitted
        to retrieve secrets from the vault and unwrap keys.

    :param enabled_for_template_deployment: A boolean value specifying whether Azure Resource Manager is
        permitted to retrieve secrets from the key vault.

    :param create_mode: The vault's create mode to indicate whether the vault needs to be recovered or not.
        Possible values include: 'recover' and 'default'.

    :param enable_soft_delete: A boolean value that specifies whether the 'soft delete' functionality is
        enabled for this key vault. If it's not set to any value (True or False) when creating new key vault, it will
        be set to True by default. Once set to True, it cannot be reverted to False.

    :param soft_delete_retention: The soft delete data retention period in days. It accepts values between
        7-90, inclusive. Default value is 90.

    :param enable_purge_protection: A boolean value specifying whether protection against purge is enabled for this
        vault. Setting this property to True activates protection against purge for this vault and its content - only
        the Key Vault service may initiate a hard, irrecoverable deletion. Enabling this functionality is irreversible,
        that is, the property does not accept False as its value. This is only effective if soft delete has been
        enabled via the ``enable_soft_delete`` parameter.

    :param enable_rbac_authorization: A boolean value that controls how data actions are authorized. When set to True,
        the key vault will use Role Based Access Control (RBAC) for authorization of data actions, and the access
        policies specified in vault properties will be ignored (warning: this is a preview feature). When set as
        False, the key vault will use the access policies specified in vault properties, and any policy stored on Azure
        Resource Manager will be ignored. Note that management actions are always authorized with RBAC. Defaults
        to False.

    :param network_acls: A dictionary representing a NetworkRuleSet. Rules governing the accessibility of
        the key vault from specific network locations.

    :param tags: The tags that will be assigned to the key vault.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure key vault exists:
            azurerm.keyvault.vault.present:
                - name: my_vault
                - resource_group: my_rg
                - location: my_location
                - tenant_id: my_tenant
                - sku: my_sku
                - access_policies:
                  - tenant_id: my_tenant
                    object_id: my_object
                    permissions:
                      keys:
                        - perm1
                        - perm2
                        - perm3
                      secrets:
                        - perm1
                        - perm2
                        - perm3
                      certificates:
                        - perm1
                        - perm2
                        - perm3
                - tags:
                    contact_name: Elmer Fudd Gantry

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    vault = await hub.exec.azurerm.keyvault.vault.get(ctx,
                                                      name,
                                                      resource_group,
                                                      azurerm_log_level="info",
                                                      **connection_auth)

    if "error" not in vault:
        action = "update"

        ret["changes"]["properties"] = {}

        tag_changes = differ.deep_diff(vault.get("tags", {}), tags or {})
        if tag_changes:
            ret["changes"]["tags"] = tag_changes

        # Checks for changes in the account_policies parameter
        if len(access_policies or []) == len(
                vault.get("properties").get("access_policies", [])):
            new_policies_sorted = sorted(access_policies or [],
                                         key=itemgetter("object_id"))
            old_policies_sorted = sorted(
                vault.get("properties").get("access_policies", []),
                key=itemgetter("object_id"),
            )
            changed = False

            for index, new_policy in enumerate(new_policies_sorted):
                old_policy = old_policies_sorted[index]

                # Checks for changes with the tenant_id key
                if old_policy.get("tenant_id") != new_policy.get("tenant_id"):
                    changed = True
                    break

                # Checks for changes with the object_id key
                if old_policy.get("object_id") != new_policy.get("object_id"):
                    changed = True
                    break

                # Checks for changes with the application_id key
                if old_policy.get("application_id") != new_policy.get(
                        "application_id"):
                    changed = True
                    break

                # Checks for changes within the permissions key
                if new_policy["permissions"].get("keys") is not None:
                    new_keys = sorted(new_policy["permissions"].get("keys"))
                    old_keys = sorted(old_policy["permissions"].get(
                        "keys", []))
                    if new_keys != old_keys:
                        changed = True
                        break

                # Checks for changes within the secrets key
                if new_policy["permissions"].get("secrets") is not None:
                    new_secrets = sorted(
                        new_policy["permissions"].get("secrets"))
                    old_secrets = sorted(old_policy["permissions"].get(
                        "secrets", []))
                    if new_secrets != old_secrets:
                        changed = True
                        break

                # Checks for changes within the certificates key
                if new_policy["permissions"].get("certificates") is not None:
                    new_certificates = sorted(
                        new_policy["permissions"].get("certificates"))
                    old_certificates = sorted(old_policy["permissions"].get(
                        "certificates", []))
                    if new_certificates != old_certificates:
                        changed = True
                        break

            if changed:
                ret["changes"]["properties"]["access_policies"] = {
                    "old": vault.get("properties").get("access_policies", []),
                    "new": access_policies,
                }

        else:
            ret["changes"]["properties"]["access_policies"] = {
                "old": vault.get("properties").get("access_policies", []),
                "new": access_policies,
            }

        if sku != vault.get("properties").get("sku").get("name"):
            ret["changes"]["properties"]["sku"] = {
                "old": vault.get("properties").get("sku"),
                "new": {
                    "name": sku
                },
            }

        if enabled_for_deployment is not None:
            if enabled_for_deployment != vault.get("properties").get(
                    "enabled_for_deployment"):
                ret["changes"]["properties"]["enabled_for_deployment"] = {
                    "old":
                    vault.get("properties").get("enabled_for_deployment"),
                    "new": enabled_for_deployment,
                }

        if enabled_for_disk_encryption is not None:
            if enabled_for_disk_encryption != vault.get("properties").get(
                    "enabled_for_disk_encryption"):
                ret["changes"]["properties"]["enabled_for_disk_encryption"] = {
                    "old":
                    vault.get("properties").get("enabled_for_disk_encryption"),
                    "new":
                    enabled_for_disk_encryption,
                }

        if enabled_for_template_deployment is not None:
            if enabled_for_template_deployment != vault.get("properties").get(
                    "enabled_for_template_deployment"):
                ret["changes"]["properties"][
                    "enabled_for_template_deployment"] = {
                        "old":
                        vault.get("properties").get(
                            "enabled_for_template_deployment"),
                        "new":
                        enabled_for_template_deployment,
                    }

        if enable_soft_delete is not None:
            if enable_soft_delete != vault.get("properties").get(
                    "enable_soft_delete"):
                ret["changes"]["properties"]["enable_soft_delete"] = {
                    "old": vault.get("properties").get("enable_soft_delete"),
                    "new": enable_soft_delete,
                }

        if enable_purge_protection is not None:
            if enable_purge_protection != vault.get("properties").get(
                    "enable_purge_protection"):
                ret["changes"]["properties"]["enable_purge_protection"] = {
                    "old":
                    vault.get("properties").get("enable_purge_protection"),
                    "new": enable_purge_protection,
                }

        if enable_rbac_authorization is not None:
            if enable_rbac_authorization != vault.get("properties").get(
                    "enable_rbac_authorization"):
                ret["changes"]["properties"]["enable_rbac_authorization"] = {
                    "old":
                    vault.get("properties").get("enable_rbac_authorization"),
                    "new": enable_rbac_authorization,
                }

        if network_acls:
            acls_changes = differ.deep_diff(
                vault.get("properties").get("network_acls", {}), network_acls
                or {})
            if acls_changes:
                ret["changes"]["properties"]["network_acls"] = acls_changes

        if not ret["changes"]["properties"]:
            del ret["changes"]["properties"]

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Key Vault {0} is already present.".format(name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Key Vault {0} would be updated.".format(name)
            return ret

    if ctx["test"]:
        ret["comment"] = "Key vault {0} would be created.".format(name)
        ret["result"] = None
        return ret

    vault_kwargs = kwargs.copy()
    vault_kwargs.update(connection_auth)

    vault = await hub.exec.azurerm.keyvault.vault.create_or_update(
        ctx=ctx,
        name=name,
        resource_group=resource_group,
        location=location,
        tenant_id=tenant_id,
        sku=sku,
        access_policies=access_policies,
        vault_uri=vault_uri,
        create_mode=create_mode,
        enable_soft_delete=enable_soft_delete,
        enable_purge_protection=enable_purge_protection,
        soft_delete_retention=soft_delete_retention,
        enabled_for_deployment=enabled_for_deployment,
        enabled_for_disk_encryption=enabled_for_disk_encryption,
        enabled_for_template_deployment=enabled_for_template_deployment,
        enable_rbac_authorization=enable_rbac_authorization,
        network_acls=network_acls,
        tags=tags,
        **vault_kwargs,
    )

    if action == "create":
        ret["changes"] = {"old": {}, "new": vault}

    if "error" not in vault:
        ret["result"] = True
        ret["comment"] = f"Key Vault {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} Key Vault {1}! ({2})".format(
        action, name, vault.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
async def present(
    hub,
    ctx,
    name,
    resource_group,
    location,
    sku,
    redis_configuration=None,
    enable_non_ssl_port=False,
    tenant_settings=None,
    shard_count=None,
    minimum_tls_version=None,
    subnet_id=None,
    static_ip=None,
    zones=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 2.0.0

    Ensure a redis cache exists in the resource group.

    :param name: The name of the Redis cache.

    :param resource_group: The name of the resource group.

    :param location: The geo-location where the resource lives.

    :param sku: A dictionary representing the SKU of the Redis cache to deploy. Required parameters include:
        - ``name``: The type of Redis cache to deploy. Possible values include: 'Basic', 'Standard', and 'Premium'.
        - ``family``: The SKU family to use. Possible values include 'C' for Basic/Standard and 'P' for Premium.
        - ``capacity``: The size of the Redis cache to deploy. Possible values include 0, 1, 2, 3, 4, 5, and 6 for the
                        C (Basic/Standard) family and 1, 2, 3, and 4 for the P (Premium) family.

    :param redis_configuration: A dictionary of string key-value pairs that represent all Redis Settings. Some possible
        keys include: rdb-backup-enabled, rdb-storage-connection-string, rdb-backup-frequency, maxmemory-delta,
        maxmemory-policy, notify-keyspace-events, maxmemory-samples, slowlog-log-slower-than, slowlog-max-len,
        list-max-ziplist-entries, list-max-ziplist-value, hash-max-ziplist-entries, hash-max-ziplist-value,
        set-max-intset-entries, zset-max-ziplist-entries, zset-max-ziplist-value, and more.

    :param enable_non_ssl_port: Specifies whether the non-ssl Redis server port (6379) is enabled. Defaults to False.

    :param tenant_settings: A dictionary of tenant settings.

    :param shard_count: The number of shards to be created on a Premium Cluster Cache.

    :param minimum_tls_version: The specified TLS version (or higher) that clients are required to use. Possible values
        include: '1.0', '1.1', and '1.2'.

    :param subnet_id: The full resource ID of a subnet in a virtual network to deploy the Redis cache in. Example
        format: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/Microsoft.{Network|ClassicNetwork}/VirtualNetworks/vnet1/subnets/subnet1

    :param static_ip: Static IP address. Required when deploying a Redis cache inside an existing Azure Virtual Network.

    :param zones: A list of availability zones denoting where the resource needs to come from.

    :param tags: A dictionary of strings can be passed as tag metadata to the storage account object.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure redis cache exists:
            azurerm.redis.operations.present:
                - name: my_account
                - resource_group: my_rg
                - sku:
                    name: 'Premium'
                    family: 'P'
                    capacity: 3
                - location: 'eastus'
                - tags:
                    contact_name: Elmer Fudd Gantry
                - connection_auth: {{ profile }}

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    cache = await hub.exec.azurerm.redis.operations.get(
        ctx, name, resource_group, azurerm_log_level="info", **connection_auth)

    if "error" not in cache:
        action = "update"

        if tags:
            tag_changes = differ.deep_diff(cache.get("tags", {}), tags)
            if tag_changes:
                ret["changes"]["tags"] = tag_changes

        sku_changes = differ.deep_diff(cache.get("sku"), sku)
        if sku_changes:
            ret["changes"]["sku"] = sku_changes

        if tenant_settings:
            tenant_changes = differ.deep_diff(cache.get("tenant_settings", {}),
                                              tenant_settings)
            if tenant_changes:
                ret["changes"]["tenant_settings"] = tenant_changes

        if redis_configuration:
            config_changes = differ.deep_diff(
                cache.get("redis_configuration", {}), redis_configuration)
            if config_changes:
                ret["changes"]["redis_configuration"] = config_changes

        if enable_non_ssl_port is not None:
            if enable_non_ssl_port != cache.get("enable_non_ssl_port"):
                ret["changes"]["enable_non_ssl_port"] = {
                    "old": cache.get("enable_non_ssl_port"),
                    "new": enable_non_ssl_port,
                }
        if shard_count is not None:
            if shard_count != cache.get("shard_count", 0):
                ret["changes"]["shard_count"] = {
                    "old": cache.get("shard_count"),
                    "new": shard_count,
                }

        if minimum_tls_version:
            if minimum_tls_version != cache.get("minimum_tls_version"):
                ret["changes"]["minimum_tls_version"] = {
                    "old": cache.get("minimum_tls_version"),
                    "new": minimum_tls_version,
                }

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Redis cache {0} is already present.".format(name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Redis cache {0} would be updated.".format(name)
            return ret

    else:
        ret["changes"] = {
            "old": {},
            "new": {
                "name": name,
                "resource_group": resource_group,
                "sku": sku,
                "location": location,
            },
        }

        if tags:
            ret["changes"]["new"]["tags"] = tags
        if redis_configuration:
            ret["changes"]["new"]["redis_configuration"] = redis_configuration
        if enable_non_ssl_port is not None:
            ret["changes"]["new"]["enable_non_ssl_port"] = enable_non_ssl_port
        if tenant_settings:
            ret["changes"]["new"]["tenant_settings"] = tenant_settings
        if shard_count is not None:
            ret["changes"]["new"]["shard_count"] = shard_count
        if minimum_tls_version:
            ret["changes"]["new"]["minimum_tls_version"] = minimum_tls_version
        if subnet_id:
            ret["changes"]["new"]["subnet_id"] = subnet_id
        if static_ip:
            ret["changes"]["new"]["static_ip"] = static_ip
        if zones:
            ret["changes"]["new"]["zones"] = zones

    if ctx["test"]:
        ret["comment"] = "Redis cache {0} would be created.".format(name)
        ret["result"] = None
        return ret

    cache_kwargs = kwargs.copy()
    cache_kwargs.update(connection_auth)

    if action == "create":
        cache = await hub.exec.azurerm.redis.operations.create(
            ctx=ctx,
            name=name,
            resource_group=resource_group,
            sku=sku,
            location=location,
            redis_configuration=redis_configuration,
            enable_non_ssl_port=enable_non_ssl_port,
            tenant_settings=tenant_settings,
            shard_count=shard_count,
            minimum_tls_version=minimum_tls_version,
            subnet_id=subnet_id,
            static_ip=static_ip,
            zones=zones,
            tags=tags,
            **cache_kwargs,
        )
    else:
        cache = await hub.exec.azurerm.redis.operations.update(
            ctx=ctx,
            name=name,
            resource_group=resource_group,
            sku=sku,
            redis_configuration=redis_configuration,
            enable_non_ssl_port=enable_non_ssl_port,
            tenant_settings=tenant_settings,
            shard_count=shard_count,
            minimum_tls_version=minimum_tls_version,
            tags=tags,
            **cache_kwargs,
        )

    if "error" not in cache:
        ret["result"] = True
        ret["comment"] = f"Redis cache {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} Redis cache {1}! ({2})".format(
        action, name, cache.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #5
0
async def present(
    hub,
    ctx,
    name,
    resource_group,
    sku,
    kind,
    location,
    custom_domain=None,
    encryption=None,
    network_rule_set=None,
    access_tier=None,
    https_traffic_only=None,
    is_hns_enabled=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 2.0.0

    Ensure a storage account exists in the resource group.

    :param name: The name of the storage account being created. Storage account names must be between 3 and 24
        characters in length and use numbers and lower-case letters only.

    :param resource_group: The name of the resource group that the storage account belongs to.

    :param sku: The name of the storage account SKU. Possible values include: 'Standard_LRS', 'Standard_GRS',
        'Standard_RAGRS', 'Standard_ZRS', 'Premium_LRS', 'Premium_ZRS', 'Standard_GZRS', and 'Standard_RAGZRS'.

    :param kind: Indicates the type of storage account. Possible values include: 'Storage', 'StorageV2', 'BlobStorage'.

    :param location: Gets or sets the location of the resource. This will be one of the supported and registered Azure
        Geo Regions (e.g. West US, East US, Southeast Asia, etc.). The geo region of a resource cannot be changed once
        it is created, but if an identical geo region is specified on update, the request will succeed.

    :param custom_domain: User domain assigned to the storage account. Valid parameters are:
        - ``name``: Required. Gets or sets the custom domain name assigned to the storage account. Name is the CNAME
                    source. To clear the existing custom domain, use an empty string for this property.
        - ``use_sub_domain_name``: Indicates whether indirect CName validation is enabled. Default value is false.
                                   This should only be set on updates.

    :param encryption: Provides the encryption settings on the account. If left unspecified the account encryption
        settings will remain the same. The default setting is unencrypted.

    :param network_rule_set: A dictionary representing a NetworkRuleSet object.

    :param access_tier: The access tier is used for billing. Required for when the kind is set to 'BlobStorage'.
        Possible values include: 'Hot' and 'Cool'.

    :param https_traffic_only: Allows https traffic only to storage service if set to True. The default value
        is False.

    :param is_hns_enabled: Account HierarchicalNamespace enabled if set to True. The default value is False.

    :param tags: A dictionary of strings can be passed as tag metadata to the storage account object.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure storage account exists:
            azurerm.storage.account.present:
                - name: my_account
                - resource_group: my_rg
                - sku: 'Standard_LRS'
                - kind: 'Storage'
                - location: 'eastus'
                - tags:
                    contact_name: Elmer Fudd Gantry
                - connection_auth: {{ profile }}

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    account = await hub.exec.azurerm.storage.account.get_properties(
        ctx, name, resource_group, **connection_auth)

    if "error" not in account:
        action = "update"
        tag_changes = differ.deep_diff(account.get("tags", {}), tags or {})
        if tag_changes:
            ret["changes"]["tags"] = tag_changes

        if sku != account.get("sku").get("name"):
            ret["changes"]["sku"] = {
                "old": account.get("sku").get("name"),
                "new": sku
            }

        if kind != account.get("kind"):
            ret["changes"]["kind"] = {"old": account.get("kind"), "new": kind}

        if https_traffic_only is not None:
            if https_traffic_only != account.get("enable_https_traffic_only"):
                ret["changes"]["enable_https_traffic_only"] = {
                    "old": account.get("enable_https_traffic_only"),
                    "new": https_traffic_only,
                }

        if is_hns_enabled is not None:
            if is_hns_enabled != account.get("is_hns_enabled"):
                ret["changes"]["is_hns_enabled"] = {
                    "old": account.get("is_hns_enabled"),
                    "new": is_hns_enabled,
                }

        if network_rule_set:
            rule_set_changes = differ.deep_diff(
                account.get("network_rule_set", {}), network_rule_set or {})
            if rule_set_changes:
                ret["changes"]["network_rule_set"] = rule_set_changes

        if encryption:
            encryption_changes = differ.deep_diff(
                account.get("encryption", {}), encryption or {})
            if encryption_changes:
                ret["changes"]["encryption"] = encryption_changes

        # The Custom Domain can only be added on once, so if it already exists then this cannot be changed
        if custom_domain:
            domain_changes = differ.deep_diff(account.get("custom_domain", {}),
                                              custom_domain or {})
            if domain_changes:
                ret["changes"]["custom_domain"] = domain_changes

        if access_tier:
            if access_tier != account.get("access_tier"):
                ret["changes"]["access_tier"] = {
                    "old": account.get("access_tier"),
                    "new": access_tier,
                }

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Storage account {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Storage account {0} would be updated.".format(
                name)
            return ret

    else:
        ret["changes"] = {
            "old": {},
            "new": {
                "name": name,
                "resource_group": resource_group,
                "sku": sku,
                "kind": kind,
                "location": location,
            },
        }

        if tags:
            ret["changes"]["new"]["tags"] = tags
        if access_tier:
            ret["changes"]["new"]["access_tier"] = access_tier
        if custom_domain:
            ret["changes"]["new"]["custom_domain"] = custom_domain
        if encryption:
            ret["changes"]["new"]["encryption"] = encryption
        if network_rule_set:
            ret["changes"]["new"]["network_rule_set"] = network_rule_set
        if https_traffic_only is not None:
            ret["changes"]["new"][
                "enable_https_traffic_only"] = https_traffic_only
        if is_hns_enabled is not None:
            ret["changes"]["new"]["is_hns_enabled"] = is_hns_enabled

    if ctx["test"]:
        ret["comment"] = "Storage account {0} would be created.".format(name)
        ret["result"] = None
        return ret

    account_kwargs = kwargs.copy()
    account_kwargs.update(connection_auth)

    account = await hub.exec.azurerm.storage.account.create(
        ctx=ctx,
        name=name,
        resource_group=resource_group,
        tags=tags,
        sku=sku,
        kind=kind,
        location=location,
        custom_domain=custom_domain,
        encryption=encryption,
        network_rule_set=network_rule_set,
        access_tier=access_tier,
        https_traffic_only=https_traffic_only,
        is_hns_enabled=is_hns_enabled,
        **account_kwargs,
    )

    if "error" not in account:
        ret["result"] = True
        ret["comment"] = f"Storage account {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} storage acccount {1}! ({2})".format(
        action, name, account.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #6
0
async def assignment_present(
    hub,
    ctx,
    name,
    scope,
    definition_name,
    display_name=None,
    description=None,
    parameters=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 1.0.0

    .. versionchanged:: 2.3.2

    Ensure a security policy assignment exists.

    :param name:
        Name of the policy assignment.

    :param scope:
        The scope of the policy assignment.

    :param definition_name:
        The name of the policy definition to assign.

    :param display_name:
        The display name of the policy assignment.

    :param description:
        The policy assignment description.

    :param parameters:
        Required dictionary if a parameter is used in the policy rule. Note that parameters will require a "value" key
        underneath the actual parameter name before specifying the values being passed. See the example for details.

    :param connection_auth:
        A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Restrict Allowed Locations :
            azurerm.resource.policy.assignment_present :
                - name: AllowedLocations
                - scope: /subscriptions/bc75htn-a0fhsi-349b-56gh-4fghti-f84852
                - definition_name: e56962a6-4747-49cd-b67b-bf8b01975c4c
                - display_name: Allowed Locations
                - description: This policy enables restriction of locations you can specify when deploying resources
                - parameters:
                      listOfAllowedLocations:
                          value:
                              - centralus
                              - eastus
                              - eastus2
                              - northcentralus
                              - southcentralus
                              - westcentralus
                              - westus
                              - westus2

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    policy = await hub.exec.azurerm.resource.policy.assignment_get(
        ctx, name, scope, azurerm_log_level="info", **connection_auth)

    if "error" not in policy:
        action = "update"
        if scope.lower() != policy["scope"].lower():
            ret["changes"]["scope"] = {"old": policy["scope"], "new": scope}

        pa_name = policy["policy_definition_id"].split("/")[-1]
        if definition_name.lower() != pa_name.lower():
            ret["changes"]["definition_name"] = {
                "old": pa_name,
                "new": definition_name
            }

        if (display_name or "").lower() != policy.get("display_name",
                                                      "").lower():
            ret["changes"]["display_name"] = {
                "old": policy.get("display_name"),
                "new": display_name,
            }

        if (description or "").lower() != policy.get("description",
                                                     "").lower():
            ret["changes"]["description"] = {
                "old": policy.get("description"),
                "new": description,
            }

        param_changes = differ.deep_diff(policy.get("parameters", {}),
                                         parameters or {})
        if param_changes:
            ret["changes"]["parameters"] = param_changes

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Policy assignment {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["comment"] = "Policy assignment {0} would be updated.".format(
                name)
            ret["result"] = None
            return ret

    else:
        ret["changes"] = {
            "old": {},
            "new": {
                "name": name,
                "scope": scope,
                "definition_name": definition_name,
                "display_name": display_name,
                "description": description,
                "parameters": parameters,
            },
        }

    if ctx["test"]:
        ret["comment"] = "Policy assignment {0} would be created.".format(name)
        ret["result"] = None
        return ret

    if isinstance(parameters, dict):
        parameters = json.loads(json.dumps(parameters))

    policy_kwargs = kwargs.copy()
    policy_kwargs.update(connection_auth)
    policy = await hub.exec.azurerm.resource.policy.assignment_create(
        ctx=ctx,
        name=name,
        scope=scope,
        definition_name=definition_name,
        display_name=display_name,
        description=description,
        parameters=parameters,
        **policy_kwargs,
    )

    if "error" not in policy:
        ret["result"] = True
        ret["comment"] = f"Policy assignment {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} policy assignment {1}! ({2})".format(
        action, name, policy.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #7
0
async def present(
    hub,
    ctx,
    name,
    resource_group,
    containers,
    os_type,
    restart_policy="OnFailure",
    identity=None,
    image_registry_credentials=None,
    ip_address=None,
    volumes=None,
    diagnostics=None,
    network_profile=None,
    dns_config=None,
    sku=None,
    encryption_properties=None,
    init_containers=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 3.0.0

    .. versionchanged:: 4.0.0

    Ensure a container instance group exists.

    :param name: The name of the container group.

    :param resource_group: The name of the resource group to which the container group belongs.

    :param containers: A list of the containers within the container group. The following are possible parameters for
        the containers:

        - **name**: Required. The user-provided name of the container instance.
        - **image**: Required. The name of the image used to create the container instance.
        - **resources**:

          - **requests**:

            - **memory_in_gb**: Required. The memory request in GB of this container instance.
            - **cpu**: Required. The CPU request of this container instance.
            - **gpu**: The GPU request of this container instance.

          - **limits**:

            - **memory_in_gb**: The memory limit in GB of this container instance.
            - **cpu**: The CPU limit of this container instance.
            - **gpu**: The GPU limit of this container instance.

        - **command**: A list of commands to execute within the container instance in exec form.
        - **ports**: A list of the dictionaries of exposed ports on the container instance
          (i.e., ``{"protocol": "TCP", "port": 80}``).
        - **environment_variables**: A list of environment variables to set in the container instance.

          - **name**: Required if environment_variables is used. The name of the environment variable.
          - **value**: The value of the environment variable.
          - **secure_value**: The value of the secure environment variable.

        - **volume_mounts**: A list of volume mounts available to the container instance.

          - **name**: Required if volume_mounts is used. The name of the volume mount.
          - **mount_path**: Required if volume_mounts is used. The path within the container where the volume should
            be mounted. Must not contain colon (:).
          - **read_only**: Boolean flag indicating whether the volume mount is read-only.

        - **liveness_probe**:

          - **exec_property**:

            - **command**: The commands to execute within the container.

          - **http_get**:

            - **path**: The path to probe.
            - **port**: Required if http_get is used. The port number to probe.
            - **scheme**: The scheme. Possible values include: 'http', 'https'.

          - **initial_delay_seconds**: The initial delay seconds.
          - **period_seconds**: The period seconds.
          - **failure_threshold**: The failure threshold.
          - **success_threshold**: The success threshold.
          - **timeout_seconds**: The timeout seconds.

        - **readiness_probe**:

          - **exec_property**:

            - **command**: The commands to execute within the container.

          - **http_get**:

            - **path**: The path to probe.
            - **port**: Required if http_get is used. The port number to probe.
            - **scheme**: The scheme. Possible values include: 'http', 'https'

          - **initial_delay_seconds**: The initial delay seconds.
          - **period_seconds**: The period seconds.
          - **failure_threshold**: The failure threshold.
          - **success_threshold**: The success threshold.
          - **timeout_seconds**: The timeout seconds.

    :param os_type: The operating system type required by the containers in the container group. Possible values
        include: 'Windows', 'Linux'.

    :param restart_policy: Restart policy for all containers within the container group. Possible values are:

        - ``Always``: Always restart.
        - ``OnFailure``: Restart on failure.
        - ``Never``: Never restart.

    :param identity: A dictionary defining a ContainerGroupIdentity object which represents the identity for the
        container group.

    :param image_registry_credentials: A list of dictionaries defining ImageRegistryCredential objects for the image
        registry credentials.

    :param ip_address: A dictionary defining an IpAddress object which represents the IP address for the container
        group. Possible keys are:

        - ``ports``: The list of ports exposed on the container group. Required if ip_address is used.
        - ``type``: Specifies if the IP is exposed to the public internet or private VNET. Required if ip_address is
          used. Possible values include: 'Public', 'Private'.
        - ``ip``: The IP exposed to the public internet.
        - ``dns_name_label``: The Dns name label for the IP.

    :param volumes: The list of dictionaries representing Volume objects that can be mounted by containers in this
        container group.

    :param diagnostics: A dictionary defining a ContainerGroupDiagnostics object which represents the diagnostic
        information for the container group.

    :param network_profile: A dictionary defining a ContainerGroupNetworkProfile object which represents the network
        profile information for the container group.

    :param dns_config: A dictionary defining a DnsConfiguration object which represents the DNS config information for
        the container group.

    :param sku: The SKU for a container group. Possible values include: 'Standard', 'Dedicated'.

    :param encryption_properties: A dictionary defining an EncryptionProperties object which represents the encryption
        properties for the container group.

    :param init_containers: A list of dictionaries defining InitContainerDefinition objects which represent the init
        containers for the container group.

    :param tags: A dictionary of strings can be passed as tag metadata to the object.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure container instance group exists:
            azurerm.containerinstance.group.present:
                - name: containergroup
                - resource_group: testgroup
                - containers:
                    - name: mycoolwebcontainer
                      image: "nginx:latest"
                      ports:
                        - protocol: TCP
                          port: 80
                      resources:
                          requests:
                              memory_in_gb: 1
                              cpu: 1
                      volume_mounts:
                        - name: testwebsite
                          mount_path: /usr/share/nginx
                          read_only: True
                - os_type: Linux
                - restart_policy: OnFailure
                - ip_address:
                    ports:
                      - protocol: TCP
                        port: 80
                    type: Public
                    dns_name_label: supercoolcontainergroup
                - volumes:
                    - name: testwebsite
                      git_repo:
                        directory: html
                        repository: "https://github.com/WooxSolo/test-website"
                - tags:
                    how_awesome: very
                    contact_name: Elmer Fudd Gantry

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    # get existing container instance group if present
    acig = await hub.exec.azurerm.containerinstance.group.get(
        ctx, name, resource_group, azurerm_log_level="info", **connection_auth)

    if "error" not in acig:
        action = "update"

        # containers changes
        comp = await hub.exec.azurerm.utils.compare_list_of_dicts(
            acig["containers"], containers)
        if comp.get("changes"):
            ret["changes"]["containers"] = comp["changes"]

        # os_type changes
        if os_type.upper() != acig["os_type"].upper():
            ret["changes"]["os_type"] = {
                "old": acig["os_type"],
                "new": os_type
            }

        # restart_policy changes
        if restart_policy.upper() != acig["restart_policy"].upper():
            ret["changes"]["restart_policy"] = {
                "old": acig["restart_policy"],
                "new": restart_policy,
            }

        # identity changes
        if identity:
            id_diff = differ.deep_diff(acig.get("identity", {}), identity)
            if id_diff:
                ret["changes"]["identity"] = id_diff

        # image_registry_credentials changes
        if image_registry_credentials:
            comp = await hub.exec.azurerm.utils.compare_list_of_dicts(
                acig.get("image_registry_credentials", []),
                image_registry_credentials,
                key_name="server",
            )
            if comp.get("changes"):
                ret["changes"]["image_registry_credentials"] = comp["changes"]

        # ip_address changes
        if ip_address:
            old_ip = acig.get("ip_address", {}).copy()
            # remove keys from the diff that can't be set
            for key in ["fqdn", "ip"]:
                if key in old_ip:
                    old_ip.pop(key)
            ip_diff = differ.deep_diff(old_ip, ip_address)
            if ip_diff:
                ret["changes"]["ip_address"] = ip_diff

        # volumes changes
        if volumes:
            comp = await hub.exec.azurerm.utils.compare_list_of_dicts(
                acig.get("volumes", []), volumes)
            if comp.get("changes"):
                ret["changes"]["volumes"] = comp["changes"]

        # diagnostics changes
        if diagnostics:
            diag_diff = differ.deep_diff(acig.get("diagnostics", {}),
                                         diagnostics)
            if diag_diff:
                ret["changes"]["diagnostics"] = diag_diff

        # network_profile changes
        if network_profile:
            net_diff = differ.deep_diff(acig.get("network_profile", {}),
                                        network_profile)
            if net_diff:
                ret["changes"]["network_profile"] = net_diff

        # dns_config changes
        if dns_config:
            dns_diff = differ.deep_diff(acig.get("dns_config", {}), dns_config)
            if dns_diff:
                ret["changes"]["dns_config"] = dns_diff

        # sku changes
        if sku and sku.upper() != acig["sku"].upper():
            ret["changes"]["sku"] = {
                "old": acig["sku"],
                "new": sku,
            }

        # encryption_properties changes
        if encryption_properties:
            enc_diff = differ.deep_diff(acig.get("encryption_properties", {}),
                                        encryption_properties)
            if enc_diff:
                ret["changes"]["encryption_properties"] = enc_diff

        # init_containers changes
        if init_containers:
            comp = await hub.exec.azurerm.utils.compare_list_of_dicts(
                acig["init_containers"], init_containers)
            if comp.get("changes"):
                ret["changes"]["init_containers"] = comp["changes"]

        # tag changes
        tag_diff = differ.deep_diff(acig.get("tags", {}), tags or {})
        if tag_diff:
            ret["changes"]["tags"] = tag_diff

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Container instance group {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["comment"] = "Container instance group {0} would be updated.".format(
                name)
            ret["result"] = None
            return ret

    elif ctx["test"]:
        ret["comment"] = "Container instance group {0} would be created.".format(
            name)
        ret["result"] = None
        return ret

    acig_kwargs = kwargs.copy()
    acig_kwargs.update(connection_auth)

    if action == "create" or len(ret["changes"]) > 1 or not tag_diff:
        acig = await hub.exec.azurerm.containerinstance.group.create_or_update(
            ctx,
            name,
            resource_group,
            containers=containers,
            os_type=os_type,
            restart_policy=restart_policy,
            identity=identity,
            image_registry_credentials=image_registry_credentials,
            ip_address=ip_address,
            volumes=volumes,
            diagnostics=diagnostics,
            network_profile=network_profile,
            dns_config=dns_config,
            sku=sku,
            encryption_properties=encryption_properties,
            init_containers=init_containers,
            tags=tags,
            **acig_kwargs,
        )

    # no idea why create_or_update doesn't work for tags
    if action == "update" and tag_diff:
        acig = await hub.exec.azurerm.containerinstance.group.update(
            ctx,
            name,
            resource_group,
            tags=tags,
            **acig_kwargs,
        )

    if action == "create":
        ret["changes"] = {"old": {}, "new": acig}

    if "error" not in acig:
        ret["result"] = True
        ret["comment"] = f"Container instance group {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} container instance group {1}! ({2})".format(
        action, name, acig.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #8
0
async def present(
    hub,
    ctx,
    name,
    value,
    vault_url,
    content_type=None,
    enabled=None,
    expires_on=None,
    not_before=None,
    tags=None,
    version=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 2.4.0

    Ensure the specified secret exists within the given key vault. Requires secrets/set permission. Secret properties
    can be specified as keyword arguments.

    :param name: The name of the secret. Secret names can only contain alphanumeric characters and dashes.

    :param value: The value of the secret.

    :param vault_url: The URL of the vault that the client will access.

    :param content_type: An arbitrary string indicating the type of the secret.

    :param enabled: Whether the secret is enabled for use.

    :param expires_on: When the secret will expire, in UTC. This parameter must be a string representation of a Datetime
        object in ISO-8601 format.

    :param not_before: The time before which the secret cannot be used, in UTC. This parameter must be a string
        representation of a Datetime object in ISO-8601 format.

    :param tags: A dictionary of strings can be passed as tag metadata to the secret.

    :param version: By default, a new version of the secret will not be created if the name is already in use UNLESS
        the value of the secret is changed. Secret properties will be updated on the latest version unless otherwise
        specified with this parameter. Set ``version`` to ``new`` in order to forse the creation of a new secret
        version.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure secret exists:
            azurerm.keyvault.secret.present:
                - name: secretname
                - value: supersecret
                - content_type: "text/plain"
                - vault_url: "https://myvault.vault.azure.net/"
                - tags:
                    contact_name: Elmer Fudd Gantry

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    secret = await hub.exec.azurerm.keyvault.secret.get_secret(
        ctx=ctx,
        name=name,
        vault_url=vault_url,
        azurerm_log_level="info",
        **connection_auth,
    )

    if "error" not in secret:
        action = "update"
        if value != secret.get("value"):
            ret["changes"]["value"] = {
                "old": "REDACTED_OLD_VALUE",
                "new": "REDACTED_NEW_VALUE",
            }

        if tags:
            tag_changes = differ.deep_diff(
                secret.get("properties", {}).get("tags", {}) or {}, tags or {})
            if tag_changes:
                ret["changes"]["tags"] = tag_changes

        if content_type:
            if (content_type.lower() !=
                (secret.get("properties", {}).get("content_type", "")
                 or "").lower()):
                ret["changes"]["content_type"] = {
                    "old": secret.get("properties", {}).get("content_type"),
                    "new": content_type,
                }

        if enabled is not None:
            if enabled != secret.get("properties", {}).get("enabled"):
                ret["changes"]["enabled"] = {
                    "old": secret.get("properties", {}).get("enabled"),
                    "new": enabled,
                }

        if expires_on:
            if expires_on != secret.get("properties", {}).get("expires_on"):
                ret["changes"]["expires_on"] = {
                    "old": secret.get("properties", {}).get("expires_on"),
                    "new": expires_on,
                }

        if not_before:
            if not_before != secret.get("properties", {}).get("not_before"):
                ret["changes"]["not_before"] = {
                    "old": secret.get("properties", {}).get("not_before"),
                    "new": not_before,
                }

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Secret {0} is already present.".format(name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Secret {0} would be updated.".format(name)
            return ret

    else:
        ret["changes"] = {
            "name": {
                "new": name
            },
            "value": {
                "new": "REDACTED_VALUE"
            },
        }

        if tags:
            ret["changes"]["tags"] = {"new": tags}
        if content_type:
            ret["changes"]["content_type"] = {"new": content_type}
        if enabled is not None:
            ret["changes"]["enabled"] = {"new": enabled}
        if expires_on:
            ret["changes"]["expires_on"] = {"new": expires_on}
        if not_before:
            ret["changes"]["not_before"] = {"new": not_before}

    if ctx["test"]:
        ret["comment"] = "Secret {0} would be created.".format(name)
        ret["result"] = None
        return ret

    secret_kwargs = kwargs.copy()
    secret_kwargs.update(connection_auth)
    secret = {}

    if ret["changes"].get("value"):
        secret = await hub.exec.azurerm.keyvault.secret.set_secret(
            ctx=ctx,
            name=name,
            value=value,
            vault_url=vault_url,
            **secret_kwargs)

    if [key for key in ret["changes"] if key not in ["name", "value"]]:
        secret = await hub.exec.azurerm.keyvault.secret.update_secret_properties(
            ctx=ctx,
            name=name,
            vault_url=vault_url,
            version=version,
            content_type=content_type,
            enabled=enabled,
            expires_on=expires_on,
            not_before=not_before,
            tags=tags,
            **secret_kwargs,
        )

    if "error" not in secret:
        ret["result"] = True
        ret["comment"] = f"Secret {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} Secret {1}! ({2})".format(
        action, name, secret.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
async def present(
    hub,
    ctx,
    name,
    resource_group,
    gateway_ip_address,
    bgp_settings=None,
    address_prefixes=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 1.0.0

    Ensure a location network gateway exists.

    :param name:
        Name of the local network gateway.

    :param resource_group:
        The resource group assigned to the local network gateway.

    :param gateway_ip_address:
        IP address of local network gateway.

    :param bgp_settings:
        A dictionary representing a valid BgpSettings object, which stores the local network gateway's BGP speaker
        settings. Valid parameters include:
          - ``asn``: The BGP speaker's Autonomous System Number. This is an integer value.
          - ``bgp_peering_address``: The BGP peering address and BGP identifier of this BGP speaker.
                                     This is a string value.
          - ``peer_weight``: The weight added to routes learned from this BGP speaker. This is an integer value.

    :param address_prefixes:
        A list of CIDR blocks which can be used by subnets within the virtual network.
        Represents the local network site address space.

    :param tags:
        A dictionary of strings can be passed as tag metadata to the local network gateway object.

    :param connection_auth:
        A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure local network gateway exists:
            azurerm.network.local_network_gateway.present:
                - name: gateway1
                - resource_group: rg-module-testing
                - gateway_ip_address: 192.168.0.1
                - bgp_settings:
                    asn: 65515
                    bgp_peering_address: 10.2.2.2
                    peer_weight: 0
                - address_prefixes:
                    - '10.0.0.0/8'
                    - '192.168.0.0/16'
                - tags:
                    contact_name: Elmer Fudd Gantry
                - connection_auth: {{ profile }}
    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    gateway = await hub.exec.azurerm.network.local_network_gateway.get(
        ctx, name, resource_group, azurerm_log_level="info", **connection_auth)

    if "error" not in gateway:
        action = "update"
        tag_changes = differ.deep_diff(gateway.get("tags", {}), tags or {})
        if tag_changes:
            ret["changes"]["tags"] = tag_changes

        if gateway_ip_address != gateway.get("gateway_ip_address"):
            ret["changes"]["gateway_ip_address"] = {
                "old": gateway.get("gateway_ip_address"),
                "new": gateway_ip_address,
            }

        if bgp_settings:
            if not isinstance(bgp_settings, dict):
                ret["comment"] = "BGP settings must be provided as a dictionary!"
                return ret

            for key in bgp_settings:
                if bgp_settings[key] != gateway.get("bgp_settings",
                                                    {}).get(key):
                    ret["changes"]["bgp_settings"] = {
                        "old": gateway.get("bgp_settings"),
                        "new": bgp_settings,
                    }
                    break

        addr_changes = set(address_prefixes or []).symmetric_difference(
            set(
                gateway.get("local_network_address_space",
                            {}).get("address_prefixes", [])))
        if addr_changes:
            ret["changes"]["local_network_address_space"] = {
                "address_prefixes": {
                    "old":
                    gateway.get("local_network_address_space",
                                {}).get("address_prefixes", []),
                    "new":
                    address_prefixes,
                }
            }

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Local network gateway {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Local network gateway {0} would be updated.".format(
                name)
            return ret

    else:
        ret["changes"] = {
            "old": {},
            "new": {
                "name": name,
                "resource_group": resource_group,
                "gateway_ip_address": gateway_ip_address,
                "tags": tags,
            },
        }

        if bgp_settings:
            ret["changes"]["new"]["bgp_settings"] = bgp_settings
        if address_prefixes:
            ret["changes"]["new"]["local_network_address_space"] = {
                "address_prefixes": address_prefixes
            }

    if ctx["test"]:
        ret["comment"] = "Local network gateway {0} would be created.".format(
            name)
        ret["result"] = None
        return ret

    gateway_kwargs = kwargs.copy()
    gateway_kwargs.update(connection_auth)

    gateway = await hub.exec.azurerm.network.local_network_gateway.create_or_update(
        ctx=ctx,
        name=name,
        resource_group=resource_group,
        gateway_ip_address=gateway_ip_address,
        local_network_address_space={"address_prefixes": address_prefixes},
        bgp_settings=bgp_settings,
        tags=tags,
        **gateway_kwargs,
    )

    if "error" not in gateway:
        ret["result"] = True
        ret["comment"] = f"Local network gateway {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} local network gateway {1}! ({2})".format(
        action, name, gateway.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #10
0
async def present(
    hub,
    ctx,
    name,
    address_prefixes,
    resource_group,
    dns_servers=None,
    # enable_vm_protection=False, # Not usable until next version bump
    enable_ddos_protection=False,
    ddos_protection_plan=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 1.0.0

    .. versionchanged:: 4.0.0, 5.0.0

    Ensure a virtual network exists.

    :param name:
        Name of the virtual network.

    :param resource_group:
        The resource group assigned to the virtual network.

    :param address_prefixes:
        A list of CIDR blocks which can be used by subnets within the virtual network.

    :param dns_servers:
        A list of DNS server addresses.

    :param enable_ddos_protection:
        A boolean value indicating whether a DDoS protection is enabled for all the protected resources in
        the virtual network. It requires a DDoS protection plan associated with the resource. Default to False.

    :param ddos_protection_plan:
        The resource ID of the DDoS protection plan associated with the virtual network. This parameter is required
        when the ``enable_ddos_protection`` parameter is set to True.

    :param tags:
        A dictionary of strings can be passed as tag metadata to the virtual network object.

    :param connection_auth:
        A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure virtual network exists:
            azurerm.network.virtual_network.present:
                - name: vnet1
                - resource_group: group1
                - address_prefixes:
                    - '10.0.0.0/8'
                    - '192.168.0.0/16'
                - dns_servers:
                    - '8.8.8.8'
                - tags:
                    contact_name: Elmer Fudd Gantry

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    if enable_ddos_protection and not ddos_protection_plan:
        log.error(
            "The resource ID of the DDOS Protection Plan must be specified if DDOS protection is going to be enabled."
        )
        ret["comment"] = "The resource ID of the DDOS Protection Plan must be specified if DDOS protection is going to be enabled."
        return ret

    if ddos_protection_plan:
        if not is_valid_resource_id(ddos_protection_plan):
            log.error(
                "The specified resource ID of the DDOS Protection Plan is invalid."
            )
            ret["comment"] = "The specified resource ID of the DDOS Protection Plan is invalid."
            return ret
        ddos_protection_plan = {"id": ddos_protection_plan}

    vnet = await hub.exec.azurerm.network.virtual_network.get(
        ctx, name, resource_group, azurerm_log_level="info", **connection_auth)

    if "error" not in vnet:
        action = "update"

        tag_changes = differ.deep_diff(vnet.get("tags", {}), tags or {})
        if tag_changes:
            ret["changes"]["tags"] = tag_changes

        dns_changes = set(dns_servers or []).symmetric_difference(
            set(vnet.get("dhcp_options", {}).get("dns_servers", [])))
        if dns_changes:
            ret["changes"]["dns_servers"] = {
                "old": vnet.get("dhcp_options", {}).get("dns_servers", []),
                "new": dns_servers,
            }

        addr_changes = set(address_prefixes or []).symmetric_difference(
            set(vnet.get("address_space", {}).get("address_prefixes", [])))
        if addr_changes:
            ret["changes"]["address_space"] = {
                "address_prefixes": {
                    "old":
                    vnet.get("address_space", {}).get("address_prefixes", []),
                    "new":
                    address_prefixes,
                }
            }

        if enable_ddos_protection is not None:
            if enable_ddos_protection != vnet.get("enable_ddos_protection"):
                ret["changes"]["enable_ddos_protection"] = {
                    "old": vnet.get("enable_ddos_protection"),
                    "new": enable_ddos_protection,
                }

        if ddos_protection_plan:
            if ddos_protection_plan.get("id") != vnet.get(
                    "ddos_protection_plan", {}).get("id"):
                ret["changes"]["ddos_protection_plan"] = {
                    "old": vnet.get("ddos_protection_plan"),
                    "new": ddos_protection_plan,
                }

        # Functionality not usable until next version bump
        # if enable_vm_protection is not None:
        #    if enable_vm_protection != vnet.get("enable_vm_protection"):
        #        ret["changes"]["enable_vm_protection"] = {
        #            "old": vnet.get("enable_vm_protection"),
        #            "new": enable_vm_protection,
        #        }

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Virtual network {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Virtual network {0} would be updated.".format(
                name)
            return ret

    if ctx["test"]:
        ret["comment"] = "Virtual network {0} would be created.".format(name)
        ret["result"] = None
        return ret

    vnet_kwargs = kwargs.copy()
    vnet_kwargs.update(connection_auth)

    vnet = await hub.exec.azurerm.network.virtual_network.create_or_update(
        ctx=ctx,
        name=name,
        resource_group=resource_group,
        address_prefixes=address_prefixes,
        dns_servers=dns_servers,
        enable_ddos_protection=enable_ddos_protection,
        # enable_vm_protection=enable_vm_protection, # Not usable until next version bump
        ddos_protection_plan=ddos_protection_plan,
        tags=tags,
        **vnet_kwargs,
    )

    if action == "create":
        ret["changes"] = {"old": {}, "new": vnet}

    if "error" not in vnet:
        ret["result"] = True
        ret["comment"] = f"Virtual network {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} virtual network {1}! ({2})".format(
        action, name, vnet.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #11
0
async def present(
    hub,
    ctx,
    name,
    resource_group,
    etag=None,
    if_match=None,
    if_none_match=None,
    registration_virtual_networks=None,
    resolution_virtual_networks=None,
    tags=None,
    zone_type="Public",
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 1.0.0

    Ensure a DNS zone exists.

    :param name:
        Name of the DNS zone (without a terminating dot).

    :param resource_group:
        The resource group assigned to the DNS zone.

    :param etag:
        The etag of the zone. `Etags <https://docs.microsoft.com/en-us/azure/dns/dns-zones-records#etags>`_ are used
        to handle concurrent changes to the same resource safely.

    :param if_match:
        The etag of the DNS zone. Omit this value to always overwrite the current zone. Specify the last-seen etag
        value to prevent accidentally overwritting any concurrent changes.

    :param if_none_match:
        Set to '*' to allow a new DNS zone to be created, but to prevent updating an existing zone. Other values will
        be ignored.

    :param registration_virtual_networks:
        A list of references to virtual networks that register hostnames in this DNS zone. This is only when zone_type
        is Private. (requires `azure-mgmt-dns <https://pypi.python.org/pypi/azure-mgmt-dns>`_ >= 2.0.0rc1)

    :param resolution_virtual_networks:
        A list of references to virtual networks that resolve records in this DNS zone. This is only when zone_type is
        Private. (requires `azure-mgmt-dns <https://pypi.python.org/pypi/azure-mgmt-dns>`_ >= 2.0.0rc1)

    :param tags:
        A dictionary of strings can be passed as tag metadata to the DNS zone object.

    :param zone_type:
        The type of this DNS zone (Public or Private). Possible values include: 'Public', 'Private'. Default value: 'Public'
         (requires `azure-mgmt-dns <https://pypi.python.org/pypi/azure-mgmt-dns>`_ >= 2.0.0rc1)

    :param connection_auth:
        A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure DNS zone exists:
            azurerm.dns.zone.present:
                - name: contoso.com
                - resource_group: my_rg
                - zone_type: Private
                - registration_virtual_networks:
                  - /subscriptions/{{ sub }}/resourceGroups/my_rg/providers/Microsoft.Network/virtualNetworks/test_vnet
                - tags:
                    how_awesome: very
                    contact_name: Elmer Fudd Gantry
                - connection_auth: {{ profile }}

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    zone = await hub.exec.azurerm.dns.zone.get(ctx,
                                               name,
                                               resource_group,
                                               azurerm_log_level="info",
                                               **connection_auth)

    if "error" not in zone:
        action = "update"
        tag_changes = differ.deep_diff(zone.get("tags", {}), tags or {})
        if tag_changes:
            ret["changes"]["tags"] = tag_changes

        # The zone_type parameter is only accessible in azure-mgmt-dns >=2.0.0rc1
        if zone.get("zone_type"):
            if zone.get("zone_type").lower() != zone_type.lower():
                ret["changes"]["zone_type"] = {
                    "old": zone["zone_type"],
                    "new": zone_type,
                }

            if zone_type.lower() == "private":
                # The registration_virtual_networks parameter is only accessible in azure-mgmt-dns >=2.0.0rc1
                if registration_virtual_networks and not isinstance(
                        registration_virtual_networks, list):
                    ret["comment"] = "registration_virtual_networks must be supplied as a list of VNET ID paths!"
                    return ret
                reg_vnets = zone.get("registration_virtual_networks", [])
                remote_reg_vnets = sorted(
                    [vnet["id"].lower() for vnet in reg_vnets if "id" in vnet])
                local_reg_vnets = sorted([
                    vnet.lower()
                    for vnet in registration_virtual_networks or []
                ])
                if local_reg_vnets != remote_reg_vnets:
                    ret["changes"]["registration_virtual_networks"] = {
                        "old": remote_reg_vnets,
                        "new": local_reg_vnets,
                    }

                # The resolution_virtual_networks parameter is only accessible in azure-mgmt-dns >=2.0.0rc1
                if resolution_virtual_networks and not isinstance(
                        resolution_virtual_networks, list):
                    ret["comment"] = "resolution_virtual_networks must be supplied as a list of VNET ID paths!"
                    return ret
                res_vnets = zone.get("resolution_virtual_networks", [])
                remote_res_vnets = sorted(
                    [vnet["id"].lower() for vnet in res_vnets if "id" in vnet])
                local_res_vnets = sorted([
                    vnet.lower() for vnet in resolution_virtual_networks or []
                ])
                if local_res_vnets != remote_res_vnets:
                    ret["changes"]["resolution_virtual_networks"] = {
                        "old": remote_res_vnets,
                        "new": local_res_vnets,
                    }

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "DNS zone {0} is already present.".format(name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "DNS zone {0} would be updated.".format(name)
            return ret

    else:
        ret["changes"] = {
            "old": {},
            "new": {
                "name": name,
                "resource_group": resource_group,
                "etag": etag,
                "registration_virtual_networks": registration_virtual_networks,
                "resolution_virtual_networks": resolution_virtual_networks,
                "tags": tags,
                "zone_type": zone_type,
            },
        }

    if ctx["test"]:
        ret["comment"] = "DNS zone {0} would be created.".format(name)
        ret["result"] = None
        return ret

    zone_kwargs = kwargs.copy()
    zone_kwargs.update(connection_auth)

    zone = await hub.exec.azurerm.dns.zone.create_or_update(
        ctx=ctx,
        name=name,
        resource_group=resource_group,
        etag=etag,
        if_match=if_match,
        if_none_match=if_none_match,
        registration_virtual_networks=registration_virtual_networks,
        resolution_virtual_networks=resolution_virtual_networks,
        tags=tags,
        zone_type=zone_type,
        **zone_kwargs,
    )

    if "error" not in zone:
        ret["result"] = True
        ret["comment"] = f"DNS zone {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} DNS zone {1}! ({2})".format(
        action, name, zone.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #12
0
async def present(
    hub,
    ctx,
    name,
    registry_name,
    resource_group,
    service_uri,
    actions,
    custom_headers=None,
    status="enabled",
    scope=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 3.0.0

    .. versionchanged:: 4.0.0

    Ensure a container registry webhook exists.

    :param name: The name of the webhook.

    :param registry_name: The name of the container registry.

    :param resource_group: The name of the resource group to which the container registry belongs.

    :param service_uri: The service URI for the webhook to post notifications.

    :param actions: The list of actions that trigger the webhook to post notifications. Possible values include
        'chart_delete', 'chart_push', 'delete', 'push', and 'quarantine'.

    :param custom_headers: A dictionary of custom headers that will be added to the webhook notifications.

    :param status: The status of the webhook at the time the operation was called. Possible values are 'enabled' and
        'disabled'.

    :param scope: The scope of repositories where the event can be triggered. For example, ``foo:>>*<<`` means events
        for all tags under repository ``foo``. ``foo:bar`` means events for ``foo:bar`` only. ``foo`` is equivalent to
        ``foo:latest``. Empty means all events.

    :param tags: A dictionary of strings can be passed as tag metadata to the object.

    Example usage:

    .. code-block:: yaml

        Ensure container registry webhook exists:
            azurerm.containerregistry.webhook.present:
                - name: testhook
                - registry_name: testrepo
                - resource_group: testgroup
                - service_uri: http://idem.eitr.tech/webhook
                - actions:
                    - push
                - status: enabled
                - customer_headers:
                    X-Custom-Header: idem
                - tags:
                    how_awesome: very
                    contact_name: Elmer Fudd Gantry

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret[
                "comment"
            ] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    # get existing container registry webhook if present
    hook = await hub.exec.azurerm.containerregistry.webhook.get(
        ctx,
        name,
        registry_name,
        resource_group,
        callback_config=True,
        azurerm_log_level="info",
        **connection_auth,
    )

    if "error" not in hook:
        action = "update"

        # sku changes
        if service_uri.upper() != hook["service_uri"].upper():
            ret["changes"]["service_uri"] = {
                "old": hook["service_uri"],
                "new": service_uri,
            }

        # actions changes
        old_act = sorted([act.lower() for act in hook["actions"]])
        actions = sorted([act.lower() for act in actions])
        if old_act != actions:
            ret["changes"]["actions"] = {
                "old": old_act,
                "new": actions,
            }

        # custom_headers changes
        head_diff = differ.deep_diff(
            hook.get("custom_headers", {}), custom_headers or {}
        )
        if head_diff:
            ret["changes"]["tags"] = head_diff

        # status changes
        if status.upper() != hook["status"].upper():
            ret["changes"]["status"] = {"old": hook["status"], "new": status}

        # scope changes
        if scope:
            if scope.upper() != hook["scope"].upper():
                ret["changes"]["scope"] = {"old": hook["scope"], "new": scope}

        # tag changes
        tag_diff = differ.deep_diff(hook.get("tags", {}), tags or {})
        if tag_diff:
            ret["changes"]["tags"] = tag_diff

        if not ret["changes"]:
            ret["result"] = True
            ret[
                "comment"
            ] = "Container registry webhook {0} is already present.".format(name)
            return ret

        if ctx["test"]:
            ret["comment"] = "Container registry webhook {0} would be updated.".format(
                name
            )
            ret["result"] = None
            return ret

    elif ctx["test"]:
        ret["comment"] = "Container registry webhook {0} would be created.".format(name)
        ret["result"] = None
        return ret

    hook_kwargs = kwargs.copy()
    hook_kwargs.update(connection_auth)

    hook = await hub.exec.azurerm.containerregistry.webhook.create_or_update(
        ctx=ctx,
        name=name,
        registry_name=registry_name,
        resource_group=resource_group,
        service_uri=service_uri,
        actions=actions,
        custom_headers=custom_headers,
        status=status,
        scope=scope,
        tags=tags,
        **hook_kwargs,
    )

    if action == "create":
        ret["changes"] = {"old": {}, "new": hook}

    if "error" not in hook:
        ret["result"] = True
        ret["comment"] = f"Container registry webhook {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} container registry webhook {1}! ({2})".format(
        action, name, hook.get("error")
    )
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #13
0
async def present(
    hub,
    ctx,
    name,
    resource_group,
    platform_fault_domain_count,
    zone=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 4.0.0

    Ensures the dedicated host group exists.

    :param name: The name of the dedicated host group.

    :param resource_group: The name of the resource group name assigned to the dedicated host group.

    :param platform_fault_domain_count: The number of fault domains that the host group can span. This value cannot be
        changed after creation. Must be an integer between 1 and 5.

    :param zone: The Availability Zone to use for this host group. The zone can only be assigned during creation. If
        not provided, the group supports all zones in the region. If provided, enforces each host in the group to be
        in the same zone.

    :param tags: A dictionary of strings can be passed as tag metadata to the dedicate host group resource object.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure dedicated host group exists:
            azurerm.compute.dedicate_host_group.present:
                - name: test_host_group
                - resource_group: test_group
                - platform_fault_domain_count: 1
                - tags:
                    contact_name: Elmer Fudd Gantry

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    host_group = await hub.exec.azurerm.compute.dedicated_host_group.get(
        ctx, name, resource_group, azurerm_log_level="info", **connection_auth)

    if "error" not in host_group:
        action = "update"

        tag_changes = differ.deep_diff(host_group.get("tags", {}), tags or {})
        if tag_changes:
            ret["changes"]["tags"] = tag_changes

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Dedicated host group {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Dedicated host group {0} would be updated.".format(
                name)
            return ret

    if ctx["test"]:
        ret["comment"] = "Dedicated host group {0} would be created.".format(
            name)
        ret["result"] = None
        return ret

    group_kwargs = kwargs.copy()
    group_kwargs.update(connection_auth)

    host_group = await hub.exec.azurerm.compute.dedicated_host_group.create_or_update(
        ctx=ctx,
        name=name,
        resource_group=resource_group,
        platform_fault_domain_count=platform_fault_domain_count,
        zone=zone,
        tags=tags,
        **group_kwargs,
    )

    if action == "create":
        ret["changes"] = {"old": {}, "new": host_group}

    if "error" not in host_group:
        ret["result"] = True
        ret["comment"] = f"Dedicated host group {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} dedicated host group {1}! ({2})".format(
        action, name, host_group.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #14
0
async def present(
    hub,
    ctx,
    name,
    resource_group,
    sku=None,
    public_ip_allocation_method=None,
    public_ip_address_version=None,
    idle_timeout_in_minutes=None,
    dns_settings=None,
    ddos_settings=None,
    ip_address=None,
    public_ip_prefix=None,
    zones=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 1.0.0

    .. versionchanged:: 4.0.0

    Ensure a public IP address exists.

    :param name: The name of the public IP address.

    :param resource_group: The resource group assigned of the public IP address.

    :param sku: The SKU of public IP address. Possible values include: 'Basic', 'Standard'.

    :param public_ip_allocation_method: The public IP allocation method. Possible values are 'Static'
        and 'Dynamic'.

    :param public_ip_address_version: The public IP address version. Possible values are 'IPv4' and 'IPv6'.

    :param idle_timeout_in_minutes: An integer representing the idle timeout of the public IP address.

    :param dns_settings: A dictionary representing a valid PublicIPAddressDnsSettings object. Parameters
        include the following:

        - ``domain_name_label``: (Required) The domain name label. The concatenation of the domain name label and the
          regionalize DNS zone make up the fully qualified domain name associated with the public IP address. If a
          domain name DNS zone make up the fully qualified domain name associated with the public IP address. If a
          domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure
          DNS system.
        - ``reverse_fqdn``: A user-visible, fully qualified domain name that resolves to this public IP address. If the
          reverse FQDN is specified, then a PTR DNS record is created pointing from the IP address in the in-addr.arpa
          domain to the reverse FQDN.

    :param ddos_settings: A dictionary representing an DdosSettings object. That DdosSettings object serves
        as the DDoS protection custom policy associated with the public IP address.

    :param public_ip_prefix: The Resource ID of the Public IP Prefix that this Public IP Address should be
        allocated from.

    :param zones: A list of availability zones denoting the IP allocated for the resource needs to come from.

    :param tags: A dictionary of strings can be passed as tag metadata to the public IP address object.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure public IP exists:
            azurerm.network.public_ip_address.present:
                - name: pub_ip1
                - resource_group: group1
                - dns_settings:
                    domain_name_label: decisionlab-ext-test-label
                - sku: basic
                - public_ip_allocation_method: static
                - public_ip_address_version: ipv4
                - idle_timeout_in_minutes: 4
                - tags:
                    contact_name: Elmer Fudd Gantry

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    if sku:
        sku = {"name": sku.capitalize()}

    if public_ip_prefix:
        if is_valid_resource_id(public_ip_prefix):
            public_ip_prefix = {"id": public_ip_prefix}
        else:
            log.error(
                "The specified resource ID of the Public IP Prefix is invalid."
            )
            ret["comment"] = "The specified resource ID of the Public IP Prefix is invalid."
            return ret

    pub_ip = await hub.exec.azurerm.network.public_ip_address.get(
        ctx, name, resource_group, azurerm_log_level="info", **connection_auth)

    if "error" not in pub_ip:
        action = "update"

        # tag changes
        tag_changes = differ.deep_diff(pub_ip.get("tags", {}), tags or {})
        if tag_changes:
            ret["changes"]["tags"] = tag_changes

        # dns_settings changes
        if dns_settings:
            if not isinstance(dns_settings, dict):
                ret["comment"] = "DNS settings must be provided as a dictionary!"
                return ret

            for key in dns_settings:
                if dns_settings[key] != pub_ip.get("dns_settings",
                                                   {}).get(key):
                    ret["changes"]["dns_settings"] = {
                        "old": pub_ip.get("dns_settings"),
                        "new": dns_settings,
                    }
                    break

        # ddos_settings changes
        ddos_changes = differ.deep_diff(pub_ip.get("ddos_settings", {}),
                                        ddos_settings or {})
        if ddos_changes:
            ret["changes"]["ddos_settings"] = ddos_changes

        # sku changes
        if sku and sku != pub_ip.get("sku", {}):
            ret["changes"]["sku"] = {
                "old": pub_ip.get("sku"),
                "new": sku,
            }

        # public_ip_allocation_method changes
        if public_ip_allocation_method:
            if public_ip_allocation_method.capitalize() != pub_ip.get(
                    "public_ip_allocation_method"):
                ret["changes"]["public_ip_allocation_method"] = {
                    "old": pub_ip.get("public_ip_allocation_method"),
                    "new": public_ip_allocation_method,
                }

        # public_ip_address_version changes
        if public_ip_address_version:
            if (public_ip_address_version.lower() != pub_ip.get(
                    "public_ip_address_version", "").lower()):
                ret["changes"]["public_ip_address_version"] = {
                    "old": pub_ip.get("public_ip_address_version"),
                    "new": public_ip_address_version,
                }

        # idle_timeout_in_minutes changes
        if idle_timeout_in_minutes and (int(idle_timeout_in_minutes) !=
                                        pub_ip.get("idle_timeout_in_minutes")):
            ret["changes"]["idle_timeout_in_minutes"] = {
                "old": pub_ip.get("idle_timeout_in_minutes"),
                "new": idle_timeout_in_minutes,
            }

        # public_ip_prefix changes
        if public_ip_prefix:
            if public_ip_prefix != pub_ip.get("public_ip_prefix"):
                ret["changes"]["public_ip_prefix"] = {
                    "old": pub_ip.get("public_ip_prefix"),
                    "new": public_ip_prefix,
                }

        # zones changes
        if zones is not None:
            if zones.sort() != pub_ip.get("zones").sort():
                ret["changes"]["zones"] = {
                    "old": pub_ip.get("zones"),
                    "new": zones
                }

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Public IP address {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Public IP address {0} would be updated.".format(
                name)
            return ret

    if ctx["test"]:
        ret["comment"] = "Public IP address {0} would be created.".format(name)
        ret["result"] = None
        return ret

    pub_ip_kwargs = kwargs.copy()
    pub_ip_kwargs.update(connection_auth)

    if action == "create" or len(ret["changes"]) > 1 or not tag_changes:
        pub_ip = await hub.exec.azurerm.network.public_ip_address.create_or_update(
            ctx=ctx,
            name=name,
            resource_group=resource_group,
            sku=sku,
            tags=tags,
            dns_settings=dns_settings,
            ddos_settings=ddos_settings,
            public_ip_allocation_method=public_ip_allocation_method,
            public_ip_address_version=public_ip_address_version,
            idle_timeout_in_minutes=idle_timeout_in_minutes,
            public_ip_prefix=public_ip_prefix,
            zones=zones,
            **pub_ip_kwargs,
        )

    # no idea why create_or_update doesn't work for tags
    if action == "update" and tag_changes:
        pub_ip = await hub.exec.azurerm.network.public_ip_address.update_tags(
            ctx,
            name=name,
            resource_group=resource_group,
            tags=tags,
            **pub_ip_kwargs,
        )

    if action == "create":
        ret["changes"] = {"old": {}, "new": pub_ip}

    if "error" not in pub_ip:
        ret["result"] = True
        ret["comment"] = f"Public IP address {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} public IP address {1}! ({2})".format(
        action, name, pub_ip.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #15
0
async def present(hub,
                  ctx,
                  name,
                  location,
                  managed_by=None,
                  tags=None,
                  connection_auth=None,
                  **kwargs):
    """
    .. versionadded:: 1.0.0

    .. versionchanged:: 4.0.0

    Ensure a resource group exists.

    :param name:
        Name of the resource group.

    :param location:
        The Azure location in which to create the resource group. This value cannot be updated once
        the resource group is created.

    :param managed_by:
        The ID of the resource that manages this resource group. This value cannot be updated once the
        resource group is created.

    :param tags:
        A dictionary of strings can be passed as tag metadata to the resource group object.

    :param connection_auth:
        A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure resource group exists:
            azurerm.resource.group.present:
                - name: group1
                - location: eastus
                - tags:
                    contact_name: Elmer Fudd Gantry

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    group = await hub.exec.azurerm.resource.group.get(ctx,
                                                      name,
                                                      azurerm_log_level="info",
                                                      **connection_auth)

    if "error" not in group:
        action = "update"

        # tag changes
        tag_changes = differ.deep_diff(group.get("tags", {}), tags or {})
        if tag_changes:
            ret["changes"]["tags"] = tag_changes

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Resource group {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["comment"] = "Resource group {0} tags would be updated.".format(
                name)
            ret["result"] = None
            ret["changes"] = {"old": group.get("tags", {}), "new": tags}
            return ret

    elif ctx["test"]:
        ret["comment"] = "Resource group {0} would be created.".format(name)
        ret["result"] = None
        return ret

    group_kwargs = kwargs.copy()
    group_kwargs.update(connection_auth)

    group = await hub.exec.azurerm.resource.group.create_or_update(
        ctx, name, location, managed_by=managed_by, tags=tags, **group_kwargs)

    if action == "create":
        ret["changes"] = {"old": {}, "new": group}

    if "error" not in group:
        ret["result"] = True
        ret["comment"] = f"Resource group {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} resource group {1}! ({2})".format(
        action, name, group.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #16
0
async def present(
    hub,
    ctx,
    name,
    key_type,
    vault_url,
    key_ops=None,
    enabled=None,
    expires_on=None,
    not_before=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 2.0.0

    Ensure the specified key exists within the given key vault. Requires keys/create permission. Key properties can be
        specified as keyword arguments.

    :param name: The name of the new key. Key names can only contain alphanumeric characters and dashes.

    :param key_type: The type of key to create. Possible values include: 'ec', 'ec_hsm', 'oct', 'rsa', 'rsa_hsm'.

    :param vault_url: The URL of the vault that the client will access.

    :param key_ops: A list of permitted key operations. Possible values include: 'decrypt', 'encrypt', 'sign',
        'unwrap_key', 'verify', 'wrap_key'.

    :param enabled: Whether the key is enabled for use.

    :param expires_on: When the key will expire, in UTC. This parameter must be a string representation of a Datetime
        object in ISO-8601 format.

    :param not_before: The time before which the key can not be used, in UTC. This parameter must be a string
        representation of a Datetime object in ISO-8601 format.

    :param tags: A dictionary of strings can be passed as tag metadata to the key.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure key exists:
            azurerm.keyvault.key.present:
                - name: my_key
                - key_type: my_type
                - vault_url: my_vault
                - tags:
                    contact_name: Elmer Fudd Gantry
                - connection_auth: {{ profile }}

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret[
                "comment"
            ] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    key = await hub.exec.azurerm.keyvault.key.get_key(
        ctx=ctx,
        name=name,
        vault_url=vault_url,
        azurerm_log_level="info",
        **connection_auth,
    )

    if key_type != "oct":
        key_type = key_type.upper().replace("_", "-")

    if "error" not in key:
        action = "update"
        if tags:
            tag_changes = differ.deep_diff(
                key.get("properties", {}).get("tags", {}) or {}, tags or {}
            )
            if tag_changes:
                ret["changes"]["tags"] = tag_changes

        if isinstance(key_ops, list):
            if sorted(key_ops) != sorted(key.get("key_operations", [])):
                ret["changes"]["key_operations"] = {
                    "old": key.get("key_operations"),
                    "new": key_ops,
                }

        if enabled is not None:
            if enabled != key.get("properties", {}).get("enabled"):
                ret["changes"]["enabled"] = {
                    "old": key.get("properties", {}).get("enabled"),
                    "new": enabled,
                }

        if expires_on:
            if expires_on != key.get("properties", {}).get("expires_on"):
                ret["changes"]["expires_on"] = {
                    "old": key.get("properties", {}).get("expires_on"),
                    "new": expires_on,
                }

        if not_before:
            if not_before != key.get("properties", {}).get("not_before"):
                ret["changes"]["not_before"] = {
                    "old": key.get("properties", {}).get("not_before"),
                    "new": not_before,
                }

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Key {0} is already present.".format(name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Key {0} would be updated.".format(name)
            return ret

    else:
        ret["changes"] = {"old": {}, "new": {"name": name, "key_type": key_type}}

        if tags:
            ret["changes"]["new"]["tags"] = tags
        if key_ops is not None:
            ret["changes"]["new"]["key_operations"] = key_ops
        if enabled is not None:
            ret["changes"]["new"]["enabled"] = enabled
        if expires_on:
            ret["changes"]["new"]["expires_on"] = expires_on
        if not_before:
            ret["changes"]["new"]["not_before"] = not_before

    if ctx["test"]:
        ret["comment"] = "Key {0} would be created.".format(name)
        ret["result"] = None
        return ret

    key_kwargs = kwargs.copy()
    key_kwargs.update(connection_auth)

    key = await hub.exec.azurerm.keyvault.key.create_key(
        ctx=ctx,
        name=name,
        vault_url=vault_url,
        key_type=key_type,
        tags=tags,
        key_ops=key_ops,
        enabled=enabled,
        not_before=not_before,
        expires_on=expires_on,
        **key_kwargs,
    )

    if "error" not in key:
        ret["result"] = True
        ret["comment"] = f"Key {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} Key {1}! ({2})".format(
        action, name, key.get("error")
    )
    if not ret["result"]:
        ret["changes"] = {}
    return ret
async def present(
    hub,
    ctx,
    name,
    resource_group,
    prefix_length=None,
    sku="standard",
    public_ip_address_version="IPv4",
    zones=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 4.0.0

    Ensure a public IP prefix exists.

    :param name: The name of the public IP prefix.

    :param resource_group: The resource group of the public IP prefix.

    :param prefix_length: An integer representing the length of the Public IP Prefix. This value is immutable
        once set. If the value of the ``public_ip_address_version`` parameter is "IPv4", then possible values include
        28, 29, 30, 31. If the value of the ``public_ip_address_version`` parameter is "IPv6", then possible values
        include 124, 125, 126, 127.

    :param sku: The name of a public IP prefix SKU. Possible values include: "standard". Defaults to "standard".

    :param public_ip_address_version: The public IP address version. Possible values include: "IPv4" and "IPv6".
        Defaults to "IPv4".

    :param zones: A list of availability zones that denotes where the IP allocated for the resource needs
        to come from.

    :param tags: A dictionary of strings can be passed as tag metadata to the public IP prefix object.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure public IP prefix exists:
            azurerm.network.public_ip_prefix.present:
                - name: test_prefix
                - resource_group: test_group
                - prefix_length: 28
                - sku: "standard"
                - public_ip_version: "IPv4"
                - tags:
                    contact_name: Elmer Fudd Gantry

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    prefix = await hub.exec.azurerm.network.public_ip_prefix.get(
        ctx, name, resource_group, azurerm_log_level="info", **connection_auth)

    if "error" not in prefix:
        action = "update"

        # tag changes
        tag_changes = differ.deep_diff(prefix.get("tags", {}), tags or {})
        if tag_changes:
            ret["changes"]["tags"] = tag_changes

        # public_ip_address_version changes
        if public_ip_address_version:
            if (public_ip_address_version.lower() != prefix.get(
                    "public_ip_address_version", "").lower()):
                ret["changes"]["public_ip_address_version"] = {
                    "old": prefix.get("public_ip_address_version"),
                    "new": public_ip_address_version,
                }

        # zones changes
        if zones is not None:
            if zones.sort() != prefix.get("zones").sort():
                ret["changes"]["zones"] = {
                    "old": prefix.get("zones"),
                    "new": zones
                }

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Public IP prefix {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Public IP prefix {0} would be updated.".format(
                name)
            return ret

    if ctx["test"]:
        ret["comment"] = "Public IP prefix {0} would be created.".format(name)
        ret["result"] = None
        return ret

    prefix_kwargs = kwargs.copy()
    prefix_kwargs.update(connection_auth)

    if action == "create" or len(ret["changes"]) > 1 or not tag_changes:
        prefix = await hub.exec.azurerm.network.public_ip_prefix.create_or_update(
            ctx=ctx,
            name=name,
            resource_group=resource_group,
            prefix_length=prefix_length,
            sku=sku,
            tags=tags,
            public_ip_address_version=public_ip_address_version,
            zones=zones,
            **prefix_kwargs,
        )

    # no idea why create_or_update doesn't work for tags
    if action == "update" and tag_changes:
        prefix = await hub.exec.azurerm.network.public_ip_prefix.update_tags(
            ctx,
            name=name,
            resource_group=resource_group,
            tags=tags,
            **prefix_kwargs,
        )

    if action == "create":
        ret["changes"] = {"old": {}, "new": prefix}

    if "error" not in prefix:
        ret["result"] = True
        ret["comment"] = f"Public IP prefix {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} public IP prefix {1}! ({2})".format(
        action, name, prefix.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #18
0
async def present(
    hub,
    ctx,
    name,
    resource_group,
    group_type="standard",
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 4.0.0

    Ensures the specified proximity placement group exists.

    :param name: The name of the proximity placement group.

    :param resource_group: The name of the resource group.

    :param group_type: The type of the proximity placement group. Possible values include: "standard", "ultra".
        Defaults to "standard".

    :param tags: A dictionary of strings can be passed as tag metadata to the proximity placement group object.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure proximity placement group exists:
            azurerm.compute.proximity_placement_group.present:
                - name: test_ppg
                - resource_group: test_group
                - group_type: test_type
                - tags:
                    contact_name: Elmer Fudd Gantry

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    ppg = await hub.exec.azurerm.compute.proximity_placement_group.get(
        ctx, name, resource_group, azurerm_log_level="info", **connection_auth)

    if "error" not in ppg:
        action = "update"

        tag_changes = differ.deep_diff(ppg.get("tags", {}), tags or {})
        if tag_changes:
            ret["changes"]["tags"] = tag_changes

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Proximity placement group {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Proximity placement group {0} would be updated.".format(
                name)
            return ret

    if ctx["test"]:
        ret["comment"] = "Proximity placement group {0} would be created.".format(
            name)
        ret["result"] = None
        return ret

    ppg_kwargs = kwargs.copy()
    ppg_kwargs.update(connection_auth)

    ppg = await hub.exec.azurerm.compute.proximity_placement_group.create_or_update(
        ctx=ctx,
        name=name,
        resource_group=resource_group,
        group_type=group_type,
        tags=tags,
        **ppg_kwargs,
    )

    if action == "create":
        ret["changes"] = {"old": {}, "new": ppg}

    if "error" not in ppg:
        ret["result"] = True
        ret["comment"] = f"Proximity placement group {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} proximity placement group {1}! ({2})".format(
        action, name, ppg.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #19
0
async def definition_present(
    hub,
    ctx,
    name,
    policy_rule=None,
    policy_type=None,
    mode=None,
    display_name=None,
    description=None,
    metadata=None,
    parameters=None,
    policy_rule_json=None,
    policy_rule_file=None,
    template="jinja",
    source_hash=None,
    source_hash_name=None,
    skip_verify=False,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 1.0.0

    .. versionchanged:: 2.0.0

    Ensure a security policy definition exists.

    :param name:
        Name of the policy definition.

    :param policy_rule:
        A YAML dictionary defining the policy rule. See `Azure Policy Definition documentation
        <https://docs.microsoft.com/en-us/azure/azure-policy/policy-definition#policy-rule>`_ for details on the
        structure. One of ``policy_rule``, ``policy_rule_json``, or ``policy_rule_file`` is required, in that order of
        precedence for use if multiple parameters are used.

    :param policy_rule_json:
        A text field defining the entirety of a policy definition in JSON. See `Azure Policy Definition documentation
        <https://docs.microsoft.com/en-us/azure/azure-policy/policy-definition#policy-rule>`_ for details on the
        structure. One of ``policy_rule``, ``policy_rule_json``, or ``policy_rule_file`` is required, in that order of
        precedence for use if multiple parameters are used. Note that the `name` field in the JSON will override the
        ``name`` parameter in the state.

    :param policy_rule_file:
        The local source location of a JSON file defining the entirety of a policy definition. See `Azure Policy
        Definition documentation <https://docs.microsoft.com/en-us/azure/azure-policy/policy-definition#policy-rule>`_
        for details on the structure. One of ``policy_rule``, ``policy_rule_json``, or ``policy_rule_file`` is required,
        in that order of precedence for use if multiple parameters are used. Note that the `name` field in the JSON
        will override the ``name`` parameter in the state.

    :param policy_type:
        The type of policy definition. Possible values are NotSpecified, BuiltIn, and Custom. Only used with the
        ``policy_rule`` parameter.

    :param mode:
        The policy definition mode. Possible values are NotSpecified, Indexed, and All. Only used with the
        ``policy_rule`` parameter.

    :param display_name:
        The display name of the policy definition. Only used with the ``policy_rule`` parameter.

    :param description:
        The policy definition description. Only used with the ``policy_rule`` parameter.

    :param metadata:
        The policy definition metadata defined as a dictionary. Only used with the ``policy_rule`` parameter.

    :param parameters:
        Required dictionary if a parameter is used in the policy rule. Only used with the ``policy_rule`` parameter.

    :param connection_auth:
        A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure policy definition exists:
            azurerm.resource.policy.definition_present:
                - name: testpolicy
                - display_name: Test Policy
                - description: Test policy for testing policies.
                - policy_rule:
                    if:
                      allOf:
                        - equals: Microsoft.Compute/virtualMachines/write
                          source: action
                        - field: location
                          in:
                            - eastus
                            - eastus2
                            - centralus
                    then:
                      effect: deny
                - connection_auth: {{ profile }}

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    if not policy_rule and not policy_rule_json and not policy_rule_file:
        ret["comment"] = 'One of "policy_rule", "policy_rule_json", or "policy_rule_file" is required!'
        return ret

    if (sum(x is not None
            for x in [policy_rule, policy_rule_json, policy_rule_file]) > 1):
        ret["comment"] = 'Only one of "policy_rule", "policy_rule_json", or "policy_rule_file" is allowed!'
        return ret

    if (policy_rule_json
            or policy_rule_file) and (policy_type or mode or display_name or
                                      description or metadata or parameters):
        ret["comment"] = 'Policy definitions cannot be passed when "policy_rule_json" or "policy_rule_file" is defined!'
        return ret

    temp_rule = {}
    if policy_rule_json:
        try:
            temp_rule = json.loads(policy_rule_json)
        except Exception as exc:
            ret["comment"] = "Unable to load policy rule json! ({0})".format(
                exc)
            return ret
    elif policy_rule_file:
        try:
            with open(policy_rule_file, "r") as prf:
                temp_rule = json.load(prf)
        except Exception as exc:
            ret["comment"] = 'Unable to load policy rule file "{0}"! ({1})'.format(
                policy_rule_file, exc)
            return ret

    policy_name = name
    if policy_rule_json or policy_rule_file:
        if temp_rule.get("name"):
            policy_name = temp_rule.get("name")
        policy_rule = temp_rule.get("properties", {}).get("policyRule")
        policy_type = temp_rule.get("properties", {}).get("policyType")
        mode = temp_rule.get("properties", {}).get("mode")
        display_name = temp_rule.get("properties", {}).get("displayName")
        description = temp_rule.get("properties", {}).get("description")
        metadata = temp_rule.get("properties", {}).get("metadata")
        parameters = temp_rule.get("properties", {}).get("parameters")

    policy = await hub.exec.azurerm.resource.policy.definition_get(
        ctx, name, azurerm_log_level="info", **connection_auth)

    if "error" not in policy:
        action = "update"
        if policy_type and policy_type.lower() != policy.get(
                "policy_type", "").lower():
            ret["changes"]["policy_type"] = {
                "old": policy.get("policy_type"),
                "new": policy_type,
            }

        if (mode or "").lower() != policy.get("mode", "").lower():
            ret["changes"]["mode"] = {"old": policy.get("mode"), "new": mode}

        if (display_name or "").lower() != policy.get("display_name",
                                                      "").lower():
            ret["changes"]["display_name"] = {
                "old": policy.get("display_name"),
                "new": display_name,
            }

        if (description or "").lower() != policy.get("description",
                                                     "").lower():
            ret["changes"]["description"] = {
                "old": policy.get("description"),
                "new": description,
            }

        rule_changes = differ.deep_diff(policy.get("policy_rule", {}),
                                        policy_rule or {})
        if rule_changes:
            ret["changes"]["policy_rule"] = rule_changes

        meta_changes = differ.deep_diff(policy.get("metadata", {}), metadata
                                        or {})
        if meta_changes:
            ret["changes"]["metadata"] = meta_changes

        param_changes = differ.deep_diff(policy.get("parameters", {}),
                                         parameters or {})
        if param_changes:
            ret["changes"]["parameters"] = param_changes

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Policy definition {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["comment"] = "Policy definition {0} would be updated.".format(
                name)
            ret["result"] = None
            return ret

    else:
        ret["changes"] = {
            "old": {},
            "new": {
                "name": policy_name,
                "policy_type": policy_type,
                "mode": mode,
                "display_name": display_name,
                "description": description,
                "metadata": metadata,
                "parameters": parameters,
                "policy_rule": policy_rule,
            },
        }

    if ctx["test"]:
        ret["comment"] = "Policy definition {0} would be created.".format(name)
        ret["result"] = None
        return ret

    # Convert OrderedDict to dict
    if isinstance(metadata, dict):
        metadata = json.loads(json.dumps(metadata))
    if isinstance(parameters, dict):
        parameters = json.loads(json.dumps(parameters))

    policy_kwargs = kwargs.copy()
    policy_kwargs.update(connection_auth)

    policy = await hub.exec.azurerm.resource.policy.definition_create_or_update(
        ctx=ctx,
        name=policy_name,
        policy_rule=policy_rule,
        policy_type=policy_type,
        mode=mode,
        display_name=display_name,
        description=description,
        metadata=metadata,
        parameters=parameters,
        **policy_kwargs,
    )

    if "error" not in policy:
        ret["result"] = True
        ret["comment"] = f"Policy definition {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} policy definition {1}! ({2})".format(
        action, name, policy.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
async def present(
    hub,
    ctx,
    name,
    resource_group,
    location,
    sku=None,
    retention=None,
    customer_id=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 2.0.0

    Ensure a specified log analytics workspace exists.

    :param name: The name of the workspace.

    :param resource_group: The resource group name of the workspace.

    :param location: The resource location.

    :param sku: The name of the SKU. Possible values include: 'Free', 'Standard', 'Premium', 'Unlimited', 'PerNode',
        'PerGB2018', 'Standalone'.

    :param retention: The workspace data retention in days. -1 means Unlimited retention for the Unlimited Sku.
        730 days is the maximum allowed for all other Skus.

    :param customer_id: The ID associated with the workspace. Setting this value at creation time allows the workspace
        being created to be linked to an existing workspace.

    :param tags: A dictionary of strings can be passed as tag metadata to the key vault.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure log analytics workspace exists:
            azurerm.log_analytics.workspace.present:
                - name: my_vault
                - resource_group: my_rg
                - location: my_location
                - tags:
                    contact_name: Elmer Fudd Gantry
                - connection_auth: {{ profile }}

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret[
                "comment"
            ] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    workspace = await hub.exec.azurerm.log_analytics.workspace.get(
        ctx, name, resource_group, azurerm_log_level="info", **connection_auth
    )

    if "error" not in workspace:
        action = "update"
        if tags:
            tag_changes = differ.deep_diff(workspace.get("tags", {}), tags)
            if tag_changes:
                ret["changes"]["tags"] = tag_changes

        if sku:
            if sku.lower() != workspace.get("sku").get("name").lower():
                ret["changes"]["sku"] = {
                    "old": workspace.get("sku").get("name"),
                    "new": sku,
                }

        if retention is not None:
            if retention != workspace.get("retention_in_days"):
                ret["changes"]["retention_in_days"] = {
                    "old": workspace.get("retention_in_days"),
                    "new": retention,
                }

        if customer_id:
            if customer_id != workspace.get("customer_id"):
                ret["changes"]["customer_id"] = {
                    "old": workspace.get("customer_id"),
                    "new": customer_id,
                }

        if kwargs.get("etag"):
            if kwargs.get("etag") != workspace.get("e_tag"):
                ret["changes"]["e_tag"] = {
                    "old": workspace.get("e_tag"),
                    "new": kwargs.get("etag"),
                }

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Log Analytics Workspace {0} is already present.".format(
                name
            )
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Log Analytics Workspace {0} would be updated.".format(
                name
            )
            return ret

    else:
        ret["changes"] = {
            "old": {},
            "new": {
                "name": name,
                "resource_group": resource_group,
                "location": location,
            },
        }

        if tags:
            ret["changes"]["new"]["tags"] = tags
        if sku:
            ret["changes"]["new"]["sku"] = {"name": sku}
        if customer_id:
            ret["changes"]["new"]["customer_id"] = customer_id
        if retention is not None:
            ret["changes"]["new"]["retention_in_days"] = retention
        if kwargs.get("etag"):
            ret["changes"]["new"]["e_tag"] = kwargs.get("etag")

    if ctx["test"]:
        ret["comment"] = "Log Analytics Workspace {0} would be created.".format(name)
        ret["result"] = None
        return ret

    workspace_kwargs = kwargs.copy()
    workspace_kwargs.update(connection_auth)

    workspace = await hub.exec.azurerm.log_analytics.workspace.create_or_update(
        ctx=ctx,
        name=name,
        resource_group=resource_group,
        location=location,
        sku=sku,
        retention=retention,
        customer_id=customer_id,
        tags=tags,
        **workspace_kwargs,
    )

    if "error" not in workspace:
        ret["result"] = True
        ret["comment"] = f"Log Analytics Workspace {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} Log Analytics Workspace {1}! ({2})".format(
        action, name, workspace.get("error")
    )
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #21
0
async def present(
    hub,
    ctx,
    name,
    resource_group,
    sku=None,
    frontend_ip_configurations=None,
    backend_address_pools=None,
    load_balancing_rules=None,
    probes=None,
    inbound_nat_rules=None,
    inbound_nat_pools=None,
    outbound_nat_rules=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 1.0.0

    Ensure a load balancer exists.

    :param name:
        Name of the load balancer.

    :param resource_group:
        The resource group assigned to the load balancer.

    :param sku:
        The load balancer SKU, which can be 'Basic' or 'Standard'.

    :param tags:
        A dictionary of strings can be passed as tag metadata to the load balancer object.

    :param frontend_ip_configurations:
        An optional list of dictionaries representing valid FrontendIPConfiguration objects. A frontend IP
        configuration can be either private (using private IP address and subnet parameters) or public (using a
        reference to a public IP address object). Valid parameters are:

        - ``name``: The name of the resource that is unique within a resource group.
        - ``private_ip_address``: The private IP address of the IP configuration. Required if
          'private_ip_allocation_method' is 'Static'.
        - ``private_ip_allocation_method``: The Private IP allocation method. Possible values are: 'Static' and
          'Dynamic'.
        - ``subnet``: Name of an existing subnet inside of which the frontend IP will reside.
        - ``public_ip_address``: Name of an existing public IP address which will be assigned to the frontend IP object.

    :param backend_address_pools:
        An optional list of dictionaries representing valid BackendAddressPool objects. Only the 'name' parameter is
        valid for a BackendAddressPool dictionary. All other parameters are read-only references from other objects
        linking to the backend address pool. Inbound traffic is randomly load balanced across IPs in the backend IPs.

    :param probes:
        An optional list of dictionaries representing valid Probe objects. Valid parameters are:

        - ``name``: The name of the resource that is unique within a resource group.
        - ``protocol``: The protocol of the endpoint. Possible values are 'Http' or 'Tcp'. If 'Tcp' is specified, a
          received ACK is required for the probe to be successful. If 'Http' is specified, a 200 OK response from the
          specified URI is required for the probe to be successful.
        - ``port``: The port for communicating the probe. Possible values range from 1 to 65535, inclusive.
        - ``interval_in_seconds``: The interval, in seconds, for how frequently to probe the endpoint for health status.
          Typically, the interval is slightly less than half the allocated timeout period (in seconds) which allows two
          full probes before taking the instance out of rotation. The default value is 15, the minimum value is 5.
        - ``number_of_probes``: The number of probes where if no response, will result in stopping further traffic from
          being delivered to the endpoint. This values allows endpoints to be taken out of rotation faster or slower
          than the typical times used in Azure.
        - ``request_path``: The URI used for requesting health status from the VM. Path is required if a protocol is
          set to 'Http'. Otherwise, it is not allowed. There is no default value.

    :param load_balancing_rules:
        An optional list of dictionaries representing valid LoadBalancingRule objects. Valid parameters are:

        - ``name``: The name of the resource that is unique within a resource group.
        - ``load_distribution``: The load distribution policy for this rule. Possible values are 'Default', 'SourceIP',
          and 'SourceIPProtocol'.
        - ``frontend_port``: The port for the external endpoint. Port numbers for each rule must be unique within the
          Load Balancer. Acceptable values are between 0 and 65534. Note that value 0 enables 'Any Port'.
        - ``backend_port``: The port used for internal connections on the endpoint. Acceptable values are between 0 and
          65535. Note that value 0 enables 'Any Port'.
        - ``idle_timeout_in_minutes``: The timeout for the TCP idle connection. The value can be set between 4 and 30
          minutes. The default value is 4 minutes. This element is only used when the protocol is set to TCP.
        - ``enable_floating_ip``: Configures a virtual machine's endpoint for the floating IP capability required
          to configure a SQL AlwaysOn Availability Group. This setting is required when using the SQL AlwaysOn
          Availability Groups in SQL server. This setting can't be changed after you create the endpoint.
        - ``disable_outbound_snat``: Configures SNAT for the VMs in the backend pool to use the public IP address
          specified in the frontend of the load balancing rule.
        - ``frontend_ip_configuration``: Name of the frontend IP configuration object used by the load balancing rule
          object.
        - ``backend_address_pool``: Name of the backend address pool object used by the load balancing rule object.
          Inbound traffic is randomly load balanced across IPs in the backend IPs.
        - ``probe``: Name of the probe object used by the load balancing rule object.

    :param inbound_nat_rules:
        An optional list of dictionaries representing valid InboundNatRule objects. Defining inbound NAT rules on your
        load balancer is mutually exclusive with defining an inbound NAT pool. Inbound NAT pools are referenced from
        virtual machine scale sets. NICs that are associated with individual virtual machines cannot reference an
        Inbound NAT pool. They have to reference individual inbound NAT rules. Valid parameters are:

        - ``name``: The name of the resource that is unique within a resource group.
        - ``frontend_ip_configuration``: Name of the frontend IP configuration object used by the inbound NAT rule
          object.
        - ``protocol``: Possible values include 'Udp', 'Tcp', or 'All'.
        - ``frontend_port``: The port for the external endpoint. Port numbers for each rule must be unique within the
          Load Balancer. Acceptable values range from 1 to 65534.
        - ``backend_port``: The port used for the internal endpoint. Acceptable values range from 1 to 65535.
        - ``idle_timeout_in_minutes``: The timeout for the TCP idle connection. The value can be set between 4 and 30
          minutes. The default value is 4 minutes. This element is only used when the protocol is set to TCP.
        - ``enable_floating_ip``: Configures a virtual machine's endpoint for the floating IP capability required
          to configure a SQL AlwaysOn Availability Group. This setting is required when using the SQL AlwaysOn
          Availability Groups in SQL server. This setting can't be changed after you create the endpoint.

    :param inbound_nat_pools:
        An optional list of dictionaries representing valid InboundNatPool objects. They define an external port range
        for inbound NAT to a single backend port on NICs associated with a load balancer. Inbound NAT rules are created
        automatically for each NIC associated with the Load Balancer using an external port from this range. Defining an
        Inbound NAT pool on your Load Balancer is mutually exclusive with defining inbound NAT rules. Inbound NAT pools
        are referenced from virtual machine scale sets. NICs that are associated with individual virtual machines cannot
        reference an inbound NAT pool. They have to reference individual inbound NAT rules. Valid parameters are:

        - ``name``: The name of the resource that is unique within a resource group.
        - ``frontend_ip_configuration``: Name of the frontend IP configuration object used by the inbound NAT pool
          object.
        - ``protocol``: Possible values include 'Udp', 'Tcp', or 'All'.
        - ``frontend_port_range_start``: The first port number in the range of external ports that will be used to
          provide Inbound NAT to NICs associated with a load balancer. Acceptable values range between 1 and 65534.
        - ``frontend_port_range_end``: The last port number in the range of external ports that will be used to
          provide Inbound NAT to NICs associated with a load balancer. Acceptable values range between 1 and 65535.
        - ``backend_port``: The port used for internal connections to the endpoint. Acceptable values are between 1 and
          65535.

    :param outbound_nat_rules:
        An optional list of dictionaries representing valid OutboundNatRule objects. Valid parameters are:

        - ``name``: The name of the resource that is unique within a resource group.
        - ``frontend_ip_configuration``: Name of the frontend IP configuration object used by the outbound NAT rule
          object.
        - ``backend_address_pool``: Name of the backend address pool object used by the outbound NAT rule object.
          Outbound traffic is randomly load balanced across IPs in the backend IPs.
        - ``allocated_outbound_ports``: The number of outbound ports to be used for NAT.

    :param connection_auth:
        A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure load balancer exists:
            azurerm.network.load_balancer.present:
                - name: lb1
                - resource_group: group1
                - location: eastus
                - frontend_ip_configurations:
                  - name: lb1_feip1
                    public_ip_address: pub_ip1
                - backend_address_pools:
                  - name: lb1_bepool1
                - probes:
                  - name: lb1_webprobe1
                    protocol: tcp
                    port: 80
                    interval_in_seconds: 5
                    number_of_probes: 2
                - load_balancing_rules:
                  - name: lb1_webprobe1
                    protocol: tcp
                    frontend_port: 80
                    backend_port: 80
                    idle_timeout_in_minutes: 4
                    frontend_ip_configuration: lb1_feip1
                    backend_address_pool: lb1_bepool1
                    probe: lb1_webprobe1
                - tags:
                    contact_name: Elmer Fudd Gantry
                - connection_auth: {{ profile }}

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    if sku:
        sku = {"name": sku.capitalize()}

    load_bal = await hub.exec.azurerm.network.load_balancer.get(
        ctx, name, resource_group, azurerm_log_level="info", **connection_auth)

    if "error" not in load_bal:
        action = "update"
        # tag changes
        tag_changes = differ.deep_diff(load_bal.get("tags", {}), tags or {})
        if tag_changes:
            ret["changes"]["tags"] = tag_changes

        # sku changes
        if sku:
            sku_changes = differ.deep_diff(load_bal.get("sku", {}), sku)
            if sku_changes:
                ret["changes"]["sku"] = sku_changes

        # frontend_ip_configurations changes
        if frontend_ip_configurations:
            comp_ret = await hub.exec.azurerm.utils.compare_list_of_dicts(
                load_bal.get("frontend_ip_configurations", []),
                frontend_ip_configurations,
                ["public_ip_address", "subnet"],
            )

            if comp_ret.get("comment"):
                ret["comment"] = '"frontend_ip_configurations" {0}'.format(
                    comp_ret["comment"])
                return ret

            if comp_ret.get("changes"):
                ret["changes"]["frontend_ip_configurations"] = comp_ret[
                    "changes"]

        # backend_address_pools changes
        if backend_address_pools:
            comp_ret = await hub.exec.azurerm.utils.compare_list_of_dicts(
                load_bal.get("backend_address_pools", []),
                backend_address_pools)

            if comp_ret.get("comment"):
                ret["comment"] = '"backend_address_pools" {0}'.format(
                    comp_ret["comment"])
                return ret

            if comp_ret.get("changes"):
                ret["changes"]["backend_address_pools"] = comp_ret["changes"]

        # probes changes
        if probes:
            comp_ret = await hub.exec.azurerm.utils.compare_list_of_dicts(
                load_bal.get("probes", []), probes)

            if comp_ret.get("comment"):
                ret["comment"] = '"probes" {0}'.format(comp_ret["comment"])
                return ret

            if comp_ret.get("changes"):
                ret["changes"]["probes"] = comp_ret["changes"]

        # load_balancing_rules changes
        if load_balancing_rules:
            comp_ret = await hub.exec.azurerm.utils.compare_list_of_dicts(
                load_bal.get("load_balancing_rules", []),
                load_balancing_rules,
                ["frontend_ip_configuration", "backend_address_pool", "probe"],
            )

            if comp_ret.get("comment"):
                ret["comment"] = '"load_balancing_rules" {0}'.format(
                    comp_ret["comment"])
                return ret

            if comp_ret.get("changes"):
                ret["changes"]["load_balancing_rules"] = comp_ret["changes"]

        # inbound_nat_rules changes
        if inbound_nat_rules:
            comp_ret = await hub.exec.azurerm.utils.compare_list_of_dicts(
                load_bal.get("inbound_nat_rules", []),
                inbound_nat_rules,
                ["frontend_ip_configuration"],
            )

            if comp_ret.get("comment"):
                ret["comment"] = '"inbound_nat_rules" {0}'.format(
                    comp_ret["comment"])
                return ret

            if comp_ret.get("changes"):
                ret["changes"]["inbound_nat_rules"] = comp_ret["changes"]

        # inbound_nat_pools changes
        if inbound_nat_pools:
            comp_ret = await hub.exec.azurerm.utils.compare_list_of_dicts(
                load_bal.get("inbound_nat_pools", []),
                inbound_nat_pools,
                ["frontend_ip_configuration"],
            )

            if comp_ret.get("comment"):
                ret["comment"] = '"inbound_nat_pools" {0}'.format(
                    comp_ret["comment"])
                return ret

            if comp_ret.get("changes"):
                ret["changes"]["inbound_nat_pools"] = comp_ret["changes"]

        # outbound_nat_rules changes
        if outbound_nat_rules:
            comp_ret = await hub.exec.azurerm.utils.compare_list_of_dicts(
                load_bal.get("outbound_nat_rules", []),
                outbound_nat_rules,
                ["frontend_ip_configuration"],
            )

            if comp_ret.get("comment"):
                ret["comment"] = '"outbound_nat_rules" {0}'.format(
                    comp_ret["comment"])
                return ret

            if comp_ret.get("changes"):
                ret["changes"]["outbound_nat_rules"] = comp_ret["changes"]

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Load balancer {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Load balancer {0} would be updated.".format(name)
            return ret

    else:
        ret["changes"] = {
            "old": {},
            "new": {
                "name": name,
                "sku": sku,
                "tags": tags,
                "frontend_ip_configurations": frontend_ip_configurations,
                "backend_address_pools": backend_address_pools,
                "load_balancing_rules": load_balancing_rules,
                "probes": probes,
                "inbound_nat_rules": inbound_nat_rules,
                "inbound_nat_pools": inbound_nat_pools,
                "outbound_nat_rules": outbound_nat_rules,
            },
        }

    if ctx["test"]:
        ret["comment"] = "Load balancer {0} would be created.".format(name)
        ret["result"] = None
        return ret

    lb_kwargs = kwargs.copy()
    lb_kwargs.update(connection_auth)

    load_bal = await hub.exec.azurerm.network.load_balancer.create_or_update(
        ctx=ctx,
        name=name,
        resource_group=resource_group,
        sku=sku,
        tags=tags,
        frontend_ip_configurations=frontend_ip_configurations,
        backend_address_pools=backend_address_pools,
        load_balancing_rules=load_balancing_rules,
        probes=probes,
        inbound_nat_rules=inbound_nat_rules,
        inbound_nat_pools=inbound_nat_pools,
        outbound_nat_rules=outbound_nat_rules,
        **lb_kwargs,
    )

    if "error" not in load_bal:
        ret["result"] = True
        ret["comment"] = f"Load balancer {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} load balancer {1}! ({2})".format(
        action, name, load_bal.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #22
0
async def table_present(
    hub,
    ctx,
    name,
    resource_group,
    routes=None,
    disable_bgp_route_propagation=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 1.0.0

    .. versionchanged:: 4.0.0

    Ensure a route table exists.

    :param name: Name of the route table.

    :param resource_group: The resource group assigned to the route table.

    :param routes: A list of dictionaries representing valid Route objects contained within a route table.
        See the documentation for the route_present state or route_create_or_update execution module for more
        information on required and optional parameters for routes. The routes are only managed if this parameter is
        present. When this parameter is absent, implemented routes will not be removed, and will merely become
        unmanaged.

    :param disable_bgp_route_propagation: A boolean parameter setting whether to disable the routes learned
        by BGP on the route table.

    :param tags: A dictionary of strings can be passed as tag metadata to the route table object.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure route table exists:
            azurerm.network.route.table_present:
                - name: rt1
                - resource_group: group1
                - routes:
                  - name: rt1_route1
                    address_prefix: '0.0.0.0/0'
                    next_hop_type: internet
                  - name: rt1_route2
                    address_prefix: '192.168.0.0/16'
                    next_hop_type: vnetlocal
                - tags:
                    contact_name: Elmer Fudd Gantry

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    rt_tbl = await hub.exec.azurerm.network.route.table_get(
        ctx, name, resource_group, azurerm_log_level="info", **connection_auth)

    if "error" not in rt_tbl:
        action = "update"

        # tag changes
        tag_changes = differ.deep_diff(rt_tbl.get("tags", {}), tags or {})
        if tag_changes:
            ret["changes"]["tags"] = tag_changes

        # disable_bgp_route_propagation changes
        # pylint: disable=line-too-long
        if disable_bgp_route_propagation is not None and (
                disable_bgp_route_propagation !=
                rt_tbl.get("disable_bgp_route_propagation")):
            ret["changes"]["disable_bgp_route_propagation"] = {
                "old": rt_tbl.get("disable_bgp_route_propagation"),
                "new": disable_bgp_route_propagation,
            }

        # routes changes
        if routes:
            comp_ret = await hub.exec.azurerm.utils.compare_list_of_dicts(
                rt_tbl.get("routes", []), routes)

            if comp_ret.get("comment"):
                ret["comment"] = '"routes" {0}'.format(comp_ret["comment"])
                return ret

            if comp_ret.get("changes"):
                ret["changes"]["routes"] = comp_ret["changes"]

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Route table {0} is already present.".format(name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Route table {0} would be updated.".format(name)
            return ret

    if ctx["test"]:
        ret["comment"] = "Route table {0} would be created.".format(name)
        ret["result"] = None
        return ret

    rt_tbl_kwargs = kwargs.copy()
    rt_tbl_kwargs.update(connection_auth)

    if action == "create" or len(ret["changes"]) > 1 or not tag_changes:
        rt_tbl = await hub.exec.azurerm.network.route.table_create_or_update(
            ctx=ctx,
            name=name,
            resource_group=resource_group,
            disable_bgp_route_propagation=disable_bgp_route_propagation,
            routes=routes,
            tags=tags,
            **rt_tbl_kwargs,
        )

    # no idea why create_or_update doesn't work for tags
    if action == "update" and tag_changes:
        rt_tbl = await hub.exec.azurerm.network.route.table_update_tags(
            ctx,
            name=name,
            resource_group=resource_group,
            tags=tags,
            **rt_tbl_kwargs,
        )

    if action == "create":
        ret["changes"] = {"old": {}, "new": rt_tbl}

    if "error" not in rt_tbl:
        ret["result"] = True
        ret["comment"] = f"Route table {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} route table {1}! ({2})".format(
        action, name, rt_tbl.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #23
0
async def present(
    hub,
    ctx,
    name,
    resource_group,
    sku="Basic",
    replica_locations=None,
    admin_user_enabled=False,
    default_action=None,
    virtual_network_rules=None,
    ip_rules=None,
    trust_policy=None,
    quarantine_policy=None,
    retention_policy=None,
    retention_days=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 3.0.0

    .. versionchanged:: 4.0.0

    Ensure a container registry exists.

    :param name: The name of the container registry.

    :param resource_group: The name of the resource group to which the container registry belongs.

    :param sku: The SKU name of the container registry. Required for registry creation. Possible
        values include: 'Basic', 'Standard', 'Premium'

    :param replica_locations: A list of valid Azure regions can be provided in order to enable replication
        to locations other than the location in which the repository was configured.

    :param admin_user_enabled: This value that indicates whether the admin user is enabled.

    :param default_action: The default action of allow or deny when no other rules match.
        Possible values include: 'Allow', 'Deny'. Only available for the 'Premium' tier.

    :param virtual_network_rules: A list of virtual network rule dictionaries where one key is the "action"
        of the rule (Allow/Deny) and the other key is the "virtual_network_resource_id" which is the full
        resource ID path of a subnet. Only available for the 'Premium' tier.

    :param ip_rules: A list of IP rule dictionaries where one key is the "action" of the rule (Allow/Deny)
        and the other key is the "ip_address_or_range" which specifies the IP or IP range in CIDR format.
        Only IPV4 addresses are allowed. Only available for the 'Premium' tier.

    :param trust_policy: Accepts boolean True/False or string "enabled"/"disabled" to configure.
        Image publishers can sign their container images and image consumers can verify their integrity.
        Container Registry supports both by implementing Docker's content trust model. Only available
        for the 'Premium' tier.

    :param quarantine_policy: Accepts boolean True/False or string "enabled"/"disabled" to configure.
        To assure a registry only contains images that have been vulnerability scanned, ACR introduces
        the Quarantine pattern. When a registries policy is set to Quarantine Enabled, all images pushed
        to that registry are put in quarantine by default. Only after the image has been verifed, and the
        quarantine flag removed may a subsequent pull be completed. Only available for the 'Premium' tier.

    :param retention_policy: Accepts boolean True/False or string "enabled"/"disabled" to configure.
        Indicates whether retention policy is enabled. Only available for the 'Premium' tier.

    :param tags: A dictionary of strings can be passed as tag metadata to the object.

    Example usage:

    .. code-block:: yaml

        Ensure container registry exists:
            azurerm.containerregistry.registry.present:
                - name: testrepo
                - resource_group: testgroup
                - sku: Premium
                - location: eastus
                - replica_locations:
                    - westus
                - admin_user_enabled: True
                - default_action: Deny
                - ip_rules:
                    - action: Allow
                      ip_address_or_range: 8.8.8.8/32
                - quarantine_policy: Enabled
                - retention_policy: Enabled
                - retention_days: 7
                - tags:
                    how_awesome: very
                    contact_name: Elmer Fudd Gantry

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"
    just_repl = False

    if not replica_locations:
        replica_locations = []

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    # get existing container registry if present
    acr = await hub.exec.azurerm.containerregistry.registry.get(
        ctx, name, resource_group, azurerm_log_level="info", **connection_auth)

    if "error" not in acr:
        action = "update"

        # sku changes
        if sku.upper() != acr["sku"]["name"].upper():
            ret["changes"]["sku"] = {"old": acr["sku"]["name"], "new": sku}

        # admin_user_enabled changes
        if (admin_user_enabled is not None
                and admin_user_enabled != acr["admin_user_enabled"]):
            ret["changes"]["admin_user_enabled"] = {
                "old": acr["admin_user_enabled"],
                "new": admin_user_enabled,
            }

        # default_action changes
        old_da = acr.get("network_rule_set", {}).get("default_action", "")
        if default_action and default_action.upper() != old_da.upper():
            ret["changes"]["default_action"] = {
                "old": old_da,
                "new": default_action
            }

        # virtual_network_rules changes
        if virtual_network_rules:
            old_rules = acr.get("network_rule_set",
                                {}).get("virtual_network_rules", [])
            comp = await hub.exec.azurerm.utils.compare_list_of_dicts(
                old_rules,
                virtual_network_rules,
                key_name="virtual_network_resource_id")

            if comp.get("changes"):
                ret["changes"]["virtual_network_rules"] = comp["changes"]

        # ip_rules changes
        if ip_rules:
            old_rules = acr.get("network_rule_set", {}).get("ip_rules", [])
            comp = await hub.exec.azurerm.utils.compare_list_of_dicts(
                old_rules, ip_rules, key_name="ip_address_or_range")

            if comp.get("changes"):
                ret["changes"]["ip_rules"] = comp["changes"]

        # trust_policy changes
        old_pol = acr.get("policies", {}).get("trust_policy",
                                              {}).get("status", "")
        if trust_policy and trust_policy.upper() != old_pol.upper():
            ret["changes"]["trust_policy"] = {
                "old": old_pol,
                "new": trust_policy
            }

        # quarantine_policy changes
        old_pol = acr.get("policies", {}).get("quarantine_policy",
                                              {}).get("status", "")
        if quarantine_policy and quarantine_policy.upper() != old_pol.upper():
            ret["changes"]["quarantine_policy"] = {
                "old": old_pol,
                "new": quarantine_policy,
            }

        # retention_policy changes
        old_pol = acr.get("policies", {}).get("retention_policy",
                                              {}).get("status", "")
        if retention_policy and retention_policy.upper() != old_pol.upper():
            ret["changes"]["retention_policy"] = {
                "old": old_pol,
                "new": retention_policy,
            }

        # retention_days changes
        old_pol = acr.get("policies", {}).get("retention_policy",
                                              {}).get("days")
        if retention_days and int(retention_days) != old_pol:
            ret["changes"]["retention_policy"] = {
                "old": old_pol,
                "new": retention_days
            }

        # tag changes
        tag_diff = differ.deep_diff(acr.get("tags", {}), tags or {})
        if tag_diff:
            ret["changes"]["tags"] = tag_diff

        # replica changes
        locs = await hub.exec.azurerm.containerregistry.replication.list(
            ctx,
            name,
            resource_group,
            azurerm_log_level="info",
            **connection_auth)
        locs = sorted([loc.lower() for loc in locs])
        if not locs:
            locs = [acr["location"].lower()]
        replica_locations = sorted(
            [loc.lower() for loc in replica_locations + [acr["location"]]])
        if locs != replica_locations:
            if not ret["changes"]:
                just_repl = True
            ret["changes"]["replica_locations"] = {
                "old": locs.copy(),
                "new": replica_locations.copy(),
            }

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Container registry {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["comment"] = "Container registry {0} would be updated.".format(
                name)
            ret["result"] = None
            return ret

    elif ctx["test"]:
        ret["comment"] = "Container registry {0} would be created.".format(
            name)
        ret["result"] = None
        return ret

    acr_kwargs = kwargs.copy()
    acr_kwargs.update(connection_auth)
    acr = {}

    if action == "create" or not just_repl:
        acr = await hub.exec.azurerm.containerregistry.registry.create_or_update(
            ctx,
            name,
            resource_group,
            sku=sku,
            admin_user_enabled=admin_user_enabled,
            default_action=default_action,
            virtual_network_rules=virtual_network_rules,
            ip_rules=ip_rules,
            trust_policy=trust_policy,
            quarantine_policy=quarantine_policy,
            retention_policy=retention_policy,
            retention_days=retention_days,
            tags=tags,
            **acr_kwargs,
        )

    if action == "create":
        ret["changes"] = {"old": {}, "new": acr}

    if "error" not in acr:
        if "location" in acr_kwargs:
            acr_kwargs.pop("location")

        if action == "create" or ret["changes"].get("replica_locations"):
            delete = []
            repl = ret["changes"].get("replica_locations")
            if repl:
                for loc in repl["new"]:
                    if loc in repl["old"]:
                        replica_locations.remove(loc)
                for loc in repl["old"]:
                    if loc not in repl["new"]:
                        delete.append(loc)

            for repl in replica_locations:
                loc = await hub.exec.azurerm.containerregistry.replication.create_or_update(
                    ctx=ctx,
                    location=repl,
                    registry_name=name,
                    resource_group=resource_group,
                    tags=tags,
                    **acr_kwargs,
                )
                if "error" in loc:
                    log.error("Unable to enable replication to %s!", repl)

            for repl in delete:
                loc = await hub.exec.azurerm.containerregistry.replication.delete(
                    ctx=ctx,
                    location=repl,
                    registry_name=name,
                    resource_group=resource_group,
                    **acr_kwargs,
                )
                if not loc:
                    log.error("Unable to disable replication to %s!", repl)

        ret["result"] = True
        ret["comment"] = f"Container registry {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} container registry {1}! ({2})".format(
        action, name, acr.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #24
0
async def present(
    hub,
    ctx,
    name,
    resource_uri,
    metrics,
    logs,
    workspace_id=None,
    log_analytics_destination_type=None,
    storage_account_id=None,
    event_hub_name=None,
    event_hub_authorization_rule_id=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 2.0.0

    .. versionchanged:: 4.0.0

    Ensure a diagnostic setting exists. At least one destination for the diagnostic setting logs is required. Any
    combination of the following destinations is acceptable:

    1. Archive the diagnostic settings to a storage account. This would require the ``storage_account_id`` parameter.
    2. Stream the diagnostic settings to an event hub. This would require the ``event_hub_name`` and
    ``event_hub_authorization_rule_id`` parameters.
    3. Send the diagnostic settings to Log Analytics. This would require the ``workspace_id`` parameter.

    :param name: The name of the diagnostic setting.

    :param resource_uri: The identifier of the resource.

    :param metrics: A list of dictionaries representing valid MetricSettings objects. If this list is empty,
        then the list passed as the logs parameter must have at least one element. Valid parameters are:

        - ``category``: Name of a diagnostic metric category for the resource type this setting is applied to. To obtain
          the list of diagnostic metric categories for a resource, first perform a GET diagnostic setting operation.
          This is a required parameter.
        - ``enabled``: A value indicating whether this category is enabled. This is a required parameter.
        - ``time_grain``: An optional timegrain of the metric in ISO-8601 format.
        - ``retention_policy``: An optional dictionary representing a RetentionPolicy object for the specified category.
          The default retention policy for a diagnostic setting is {'enabled': False, 'days': 0}. Required parameters
          include:

            - ``days``: The number of days for the retention in days. A value of 0 will retain the events indefinitely.
            - ``enabled``: A value indicating whether the retention policy is enabled.

    :param logs: A list of dictionaries representing valid LogSettings objects. If this list is empty, then
        the list passed as the metrics parameter must have at least one element. Valid parameters are:

        - ``category``: Name of a diagnostic log category for the resource type this setting is applied to. To obtain
          the list of diagnostic log categories for a resource, first perform a GET diagnostic setting operation.
          This is a required parameter.
        - ``enabled``: A value indicating whether this category is enabled. This is a required parameter.
        - ``retention_policy``: An optional dictionary representing a RetentionPolicy object for the specified category.
          The default retention policy for a diagnostic setting is {'enabled': False, 'days': 0}. Required parameters
          include:

            - ``days``: The number of days for the retention in days. A value of 0 will retain the events indefinitely.
            - ``enabled``: A value indicating whether the retention policy is enabled.

    :param workspace_id: The resource ID of the Log Analytics workspace to which you would like to send Diagnostic Logs.
        Required if you want to send the diagnostic settings data to Log Analytics.

    :param log_analytics_destination_type: A string indicating whether the export to the Log Analytics Workspace
        should store the logs within the legacy default destination type, the AzureDiagnostics table, or a dedicated,
        resource specific table. Optional, used with the ``workspace_id`` parameter. Possible values include:
        "Dedicated" and "AzureDiagnostics".

    :param storage_account_id: The resource ID of the storage account to which you would like to send Diagnostic Logs.
        Required if you want to archive the diagnostic settings data to a storage account.

    :param event_hub_name: The name of the event hub. If none is specified, the default event hub will be selected.
        Required to stream the diagnostic settings data to an event hub.

    :param event_hub_authorization_rule_id: The resource ID for the event hub authorization rule. Required with the
        ``event_hub_name`` parameter.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure diagnostic setting exists:
            azurerm.monitor.diagnostic_setting.present:
                - name: my_setting
                - resource_uri: my_resource
                - metrics:
                  - category: my_category
                    enabled: True
                    retention_policy:
                      enabled: True
                      days: 10
                - logs:
                  - category: my_category
                    enabled: True
                - storage_account_id: my_account_id

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    setting = await hub.exec.azurerm.monitor.diagnostic_setting.get(
        ctx, name, resource_uri, azurerm_log_level="info", **connection_auth)

    if "error" not in setting:
        action = "update"
        # Checks for changes in the metrics parameter
        if len(metrics) == len(setting.get("metrics", [])):
            new_metrics_sorted = sorted(metrics,
                                        key=itemgetter("category", "enabled"))
            old_metrics_sorted = sorted(setting.get("metrics", []),
                                        key=itemgetter("category", "enabled"))
            for index, metric in enumerate(new_metrics_sorted):
                changes = differ.deep_diff(old_metrics_sorted[index], metric)
                if changes:
                    ret["changes"]["metrics"] = {
                        "old": setting.get("metrics", []),
                        "new": metrics,
                    }
                    break
        else:
            ret["changes"]["metrics"] = {
                "old": setting.get("metrics", []),
                "new": metrics,
            }

        # Checks for changes in the logs parameter
        if len(logs) == len(setting.get("logs", [])):
            new_logs_sorted = sorted(logs,
                                     key=itemgetter("category", "enabled"))
            old_logs_sorted = sorted(setting.get("logs", []),
                                     key=itemgetter("category", "enabled"))
            for index, log in enumerate(new_logs_sorted):
                changes = differ.deep_diff(old_logs_sorted[index], log)
                if changes:
                    ret["changes"]["logs"] = {
                        "old": setting.get("logs", []),
                        "new": logs,
                    }
                    break
        else:
            ret["changes"]["logs"] = {
                "old": setting.get("logs", []),
                "new": logs
            }
        if storage_account_id:
            if storage_account_id != setting.get("storage_account_id", None):
                ret["changes"]["storage_account_id"] = {
                    "old": setting.get("storage_account_id"),
                    "new": storage_account_id,
                }

        if workspace_id:
            if workspace_id != setting.get("workspace_id", None):
                ret["changes"]["workspace_id"] = {
                    "old": setting.get("workspace_id"),
                    "new": workspace_id,
                }

        if event_hub_authorization_rule_id:
            if event_hub_authorization_rule_id != setting.get(
                    "event_hub_authorization_rule_id", None):
                ret["changes"]["event_hub_authorization_rule_id"] = {
                    "old": setting.get("event_hub_authorization_rule_id"),
                    "new": event_hub_authorization_rule_id,
                }

        if event_hub_name:
            if event_hub_name != setting.get("event_hub_name", None):
                ret["changes"]["event_hub_name"] = {
                    "old": setting.get("event_hub_name"),
                    "new": event_hub_name,
                }

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Diagnostic setting {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Diagnostic setting {0} would be updated.".format(
                name)
            return ret

    if ctx["test"]:
        ret["comment"] = "Diagnostic setting {0} would be created.".format(
            name)
        ret["result"] = None
        return ret

    setting_kwargs = kwargs.copy()
    setting_kwargs.update(connection_auth)

    setting = await hub.exec.azurerm.monitor.diagnostic_setting.create_or_update(
        ctx=ctx,
        name=name,
        resource_uri=resource_uri,
        logs=logs,
        metrics=metrics,
        storage_account_id=storage_account_id,
        workspace_id=workspace_id,
        log_analytics_destination_type=log_analytics_destination_type,
        event_hub_name=event_hub_name,
        event_hub_authorization_rule_id=event_hub_authorization_rule_id,
        **setting_kwargs,
    )

    if action == "create":
        ret["changes"] = {"old": {}, "new": setting}

    if "error" not in setting:
        ret["result"] = True
        ret["comment"] = f"Diagnostic setting {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} diagnostic setting {1}! ({2})".format(
        action, name, setting.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret
async def present(
    hub,
    ctx,
    name,
    resource_group,
    functions_file_path,
    os_type,
    runtime_stack,
    storage_account,
    storage_rg=None,
    app_service_plan=None,
    functions_version=2,
    enable_app_insights=None,
    app_insights=None,
    tags=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 3.0.0

    Ensure that a Function App exists.

    :param name: The name of the Function App.

    :param resource_group: The name of the resource group of the Function App.

    :param functions_file_path: The file path of the compressed (zip) file containing any Azure Functions that should
        be deployed to the Function App. The Azure Functions inside of this zip file should be using the same runtime
        stack or language that is specified within the runtime_stack parameter. This file will be uploaded every
        successfully run of this state. If there is a prior version of the zip file it will be overwritten. IMPORTANT:
        The code for all the functions in a specific function app should be located in a root project folder that
        contains a host configuration file and one or more subfolders. Each subfolder contains the code for a separate
        function. The folder structure is shown in the representation below::

        | functionapp.zip
        |   - host.json
        |   - MyFirstFunction/
        |     - function.json
        |     - ..
        |   - MySecondFunction/
        |     - function.json
        |     - ..
        |   - SharedCode/
        |   - bin/

    :param os_type: The operation system utilized by the Function App. This cannot be changed after the Function App
        has been created. Possible values are "linux" or "windows".

    :param runtime_stack: The language stack to be used for functions in this Function App. Possible values are
        "dotnet", "node", "java", "python", or "powershell".

    :param storage_account: The name of the storage account that will hold the Azure Functions used by the Function App.
        This storage account must be of the kind "Storage" or "StorageV2". If not already present, a container named
        "function-releases" will be created within this storage account to hold the zip file containing any Azure
        Functions.

    :param storage_rg: (Optional, used with storage_account) The resource group of the storage account passed. This
        parameter is only necessary if the storage account has a different resource group than the one specified for
        the Function App.

    :param app_service_plan: The name of the App Service (Consumption) Plan used by the Function App. If this
        parameter is not provided or the provided name is invalid/does not exist, then an App Service (Consumption)
        Plan will be built for the Function App with the name "plan-{name}". This plan should have the same OS as
        specified by the os_type parameter.

    :param functions_version: The version of Azure Functions to use. Additional information about Azure Functions
        versions can be found here: https://docs.microsoft.com/en-us/azure/azure-functions/functions-versions.
        Possible values include: 1, 2, and 3. Defaults to 2.

    :param enable_app_insights: Boolean flag for enabling Application Insights.

    :param app_insights: (Optional, used with enable_app_insights) The name of the Application Insights Component to
        use for the Function App. If the specified Application Insights Component does not exist, then it will be
        created. If this parameter is not specified, then an Application Insights Component named "app-insights-{name}"
        will be created and used.

    :param tags: A dictionary of strings representing tag metadata for the Function App.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure function app exists:
            azurerm.web.function_app.present:
                - name: my_app
                - resource_group: my_group
                - functions_file_path: "/path/to/functions.zip"
                - os_type: "linux"
                - runtime_stack: "python"
                - storage_account: my_account
                - app_service_plan: my_plan
                - enable_app_insights: True
                - tags:
                    "Owner": "EITR Technologies"

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"
    app_settings = [
        {"name": "FUNCTIONS_WORKER_RUNTIME", "value": runtime_stack.lower()},
        {"name": "FUNCTIONS_EXTENSION_VERSION", "value": f"~{functions_version}",},
        {"name": "FUNCTION_APP_EDIT_MODE", "value": "readonly"},
        {"name": "SCM_DO_BUILD_DURING_DEPLOYMENT", "value": "false"},
    ]

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret[
                "comment"
            ] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    # Ensures location is specified
    if "location" not in kwargs:
        rg_props = await hub.exec.azurerm.resource.group.get(
            ctx, resource_group, **kwargs
        )

        if "error" in rg_props:
            log.error("Unable to determine location from resource group specified.")
            return {
                "error": "Unable to determine location from resource group specified."
            }
        kwargs["location"] = rg_props["location"]

    # Handle storage account validation
    if not storage_rg:
        storage_rg = resource_group

    storage_acct = await hub.exec.azurerm.storage.account.get_properties(
        ctx, name=storage_account, resource_group=storage_rg
    )

    if "error" in storage_acct:
        log.error(
            f"The storage account {storage_account} does not exist within the given resource group {resource_group}."
        )
        ret[
            "comment"
        ] = f"The storage account {storage_account} does not exist within the given resource group {resource_group}."
        return ret

    # Ensure that the file path contains a zip file
    filename = os.path.basename(functions_file_path)
    if not filename.lower().endswith(".zip"):
        log.error(
            "The specified file in functions_file_path is not a compressed (zip) file."
        )
        ret[
            "comment"
        ] = "The specified file in functions_file_path is not a compressed (zip) file."
        return ret

    # Set reserved for the ASP and Function App based upon OS type
    if os_type.lower() == "windows":
        reserved = False
    else:  # linux
        reserved = True

    # Handle App Service Plan creation
    if not app_service_plan:
        app_service_plan = f"plan-{name}"

    plan = await hub.exec.azurerm.web.app_service_plan.get(
        ctx, name=app_service_plan, resource_group=resource_group
    )

    if "error" in plan:
        plan = await hub.exec.azurerm.web.app_service_plan.create_or_update(
            ctx,
            name=app_service_plan,
            resource_group=resource_group,
            kind="functionapp",
            reserved=reserved,
            sku="Y1",
            **connection_auth,
        )

        if "error" in plan:
            log.error(
                f"Unable to create the App Service Plan {app_service_plan} in the resource group {resource_group}."
            )
            ret[
                "comment"
            ] = f"Unable to create the App Service Plan {app_service_plan} in the resource group {resource_group}."
            return ret
    elif plan["reserved"] != reserved:
        log.error(
            f"The OS of the App Service Plan {app_service_plan} does not match the specified OS type for the Function App and thus cannot be used."
        )
        ret[
            "comment"
        ] = f"The OS of the App Service Plan {app_service_plan} does not match the specified OS type for the Function App and thus cannot be used."
        return ret

    # Gets the resource ID of the ASP
    server_farm_id = plan["id"]

    # Handle App Insights Validation and Creation
    if enable_app_insights:
        if not app_insights:
            app_insights = f"app-insights-{name}"

        component = await hub.exec.azurerm.application_insights.component.get(
            ctx, name=app_insights, resource_group=resource_group
        )

        if "error" in component:
            component = await hub.exec.azurerm.application_insights.component.create_or_update(
                ctx,
                name=app_insights,
                resource_group=resource_group,
                kind="web",
                application_type="web",
            )

            if "error" in component:
                log.error(
                    f"Unable to create the Application Insights Component {app_insights} within the resource group {resource_group}."
                )
                ret[
                    "comment"
                ] = f"Unable to create the Application Insights Component {app_insights} within the resource group {resource_group}."
                return ret

        instrumentation_key = component["instrumentation_key"]
        # Configures the application insights for the app settings
        app_settings.append(
            {"name": "APPINSIGHTS_INSTRUMENTATIONKEY", "value": instrumentation_key}
        )

    # Builds a storage container named "function-releases" within the specified storage account if not already present
    container = await hub.exec.azurerm.storage.container.get(
        ctx,
        name="function-releases",
        account=storage_account,
        resource_group=storage_rg,
    )

    if "error" in container:
        container = await hub.exec.azurerm.storage.container.create(
            ctx,
            name="function-releases",
            account=storage_account,
            resource_group=storage_rg,
            public_access="None",
        )

    # Upload the zip file containing the Azure Functions
    upload_zip = await hub.exec.azurerm.storage.container.upload_blob(
        ctx,
        name=filename,
        container="function-releases",
        account=storage_account,
        resource_group=storage_rg,
        file_path=functions_file_path,
        overwrite=True,
    )

    if "error" in upload_zip:
        log.error(
            f"Unable to upload {filename} to the function-releases container within the storage account {storage_account}."
        )
        ret[
            "comment"
        ] = f"Unable to upload {filename} to the function-releases container within the storage account {storage_account}."
        return ret

    # Retrieves the access keys for the storage account
    storage_acct_keys = await hub.exec.azurerm.storage.account.list_keys(
        ctx, name=storage_account, resource_group=storage_rg
    )
    if "error" not in storage_acct_keys:
        storage_acct_key = storage_acct_keys["keys"][0]["value"]
    else:
        log.error(
            f"Unable to get the account access key for the specified storage account {storage_account} within the given resource group {storage_rg}."
        )
        ret[
            "comment"
        ] = f"Unable to get the account access key for the specified storage account {storage_account} within the given resource group {storage_rg}."
        return ret

    # Generate the sas token used within app settings
    sas_token = generate_account_sas(
        account_name=storage_account,
        account_key=storage_acct_key,
        resource_types=ResourceTypes(object=True, container=True, service=True),
        permission=AccountSasPermissions(read=True, write=True, list=True, delete=True),
        expiry=datetime.utcnow() + timedelta(days=2),
    )

    # Update app settings information from the storage account
    app_settings.append(
        {
            "name": "AzureWebJobsStorage",
            "value": f"DefaultEndpointsProtocol=https;AccountName={storage_account};AccountKey={storage_acct_key}",
        }
    )
    app_settings.append(
        {
            "name": "WEBSITE_RUN_FROM_PACKAGE",
            "value": f"https://{storage_account}.blob.core.windows.net/function-releases/{filename}?{sas_token}",
        }
    )

    # Add any app settings related to a specific OSs
    if os_type.lower() == "windows":
        app_settings.append(
            {
                "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
                "value": f"DefaultEndpointsProtocol=https;AccountName={storage_account};AccountKey={storage_acct_key}",
            }
        )
        app_settings.append({"name": "WEBSITE_CONTENTSHARE", "value": name.lower()})

    # Check for the existence of the Function App
    function_app = await hub.exec.azurerm.web.app.get(
        ctx, name, resource_group, azurerm_log_level="info", **connection_auth
    )

    if "error" not in function_app:
        action = "update"

        # tag changes
        tag_changes = differ.deep_diff(function_app.get("tags", {}), tags or {})
        if tag_changes:
            ret["changes"]["tags"] = tag_changes

        # app service plan changes
        if function_app.get("server_farm_id") != server_farm_id:
            ret["changes"]["server_farm_id"] = {
                "new": server_farm_id,
                "old": function_app.get("server_farm_id"),
            }

        # app setting changes
        existing_settings = await hub.exec.azurerm.web.app.list_application_settings(
            ctx, name=name, resource_group=resource_group
        )
        old_settings = existing_settings["properties"]
        new_settings = {}
        for setting in app_settings:
            new_settings.update({setting.get("name"): setting.get("value")})

        # Checks specifically for changes within the WEBSITE_RUN_FROM_PACKAGE app setting because the value of that
        # setting is changed every run.
        new_run_package_setting = new_settings.pop("WEBSITE_RUN_FROM_PACKAGE", "")
        old_run_package_setting = old_settings.pop("WEBSITE_RUN_FROM_PACKAGE", "")

        new_beginning = (new_run_package_setting.split("?"))[0]
        old_beginning = (old_run_package_setting.split("?"))[0]

        run_package_changes = False
        if old_beginning != new_beginning:
            run_package_changes = True

        # If there are changes within WEBSITE_RUN_FROM_PACKAGE, then that app setting should be readded to both settings
        # dictionaries to be recorded as app setting changes changes
        if run_package_changes:
            new_settings.update({"WEBSITE_RUN_FROM_PACKAGE": new_run_package_setting})
            old_settings.update({"WEBSITE_RUN_FROM_PACKAGE": old_run_package_setting})

        app_settings_changes = differ.deep_diff(old_settings, new_settings)
        if app_settings_changes:
            ret["changes"]["site_config"] = {"app_settings": app_settings_changes}

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Function App {0} is already present.".format(name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Function App {0} would be updated.".format(name)
            return ret

    else:
        ret["changes"] = {
            "old": {},
            "new": {
                "name": name,
                "resource_group": resource_group,
                "app_service_plan": app_service_plan,
                "os_type": os_type,
                "runtime_stack": runtime_stack,
                "site_config": {"app_settings": app_settings},
                "tags": tags,
            },
        }

        if enable_app_insights:
            ret["changes"]["new"]["application_insights"] = app_insights

    if ctx["test"]:
        ret["comment"] = "Function App {0} would be created.".format(name)
        ret["result"] = None
        return ret

    app_kwargs = kwargs.copy()
    app_kwargs.update(connection_auth)

    kind = "functionapp"
    if os_type.lower() == "linux":
        kind = kind + ",Linux"

    function_app = await hub.exec.azurerm.web.app.create_or_update(
        ctx=ctx,
        name=name,
        resource_group=resource_group,
        tags=tags,
        server_farm_id=server_farm_id,
        kind=kind,
        site_config={"app_settings": app_settings},
        **app_kwargs,
    )

    if "error" not in function_app:
        ret["result"] = True
        ret["comment"] = f"Function App {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} Function App {1}! ({2})".format(
        action, name, function_app.get("error")
    )
    if not ret["result"]:
        ret["changes"] = {}
    return ret
Exemple #26
0
async def present(
    hub,
    ctx,
    name,
    account,
    resource_group,
    public_access,
    default_encryption_scope=None,
    deny_encryption_scope_override=None,
    metadata=None,
    connection_auth=None,
    **kwargs,
):
    """
    .. versionadded:: 2.0.0

    .. versionchanged:: 4.0.0

    Ensure a blob container exists.

    :param name: The name of the blob container within the specified storage account. Blob container names must be
        between 3 and 63 characters in length and use numbers, lower-case letters and dash (-) only. Every dash (-)
        character must be immediately preceded and followed by a letter or number.

    :param account: The name of the storage account within the specified resource group. Storage account names must be
        between 3 and 24 characters in length and use numbers and lower-case letters only.

    :param resource_group: The name of the resource group within the user's subscription. The name is case insensitive.

    :param public_access: Specifies whether data in the container may be accessed publicly and the level of access.
        Possible values include: "Container", "Blob", "None".

    :param default_encryption_scope: Set the default encryption scope for the container to use for all writes.

    :param deny_encryption_scope_override: A boolean flag representing whether or not to block the override of the
        encryption scope from the container default.

    :param metadata: A dictionary of name-value pairs to associate with the container as metadata.

    :param connection_auth: A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure blob container exists:
            azurerm.storage.container.present:
                - name: my_container
                - account: my_account
                - resource_group: my_rg
                - public_access: 'Blob'

    """
    ret = {"name": name, "result": False, "comment": "", "changes": {}}
    action = "create"

    if not isinstance(connection_auth, dict):
        if ctx["acct"]:
            connection_auth = ctx["acct"]
        else:
            ret["comment"] = "Connection information must be specified via acct or connection_auth dictionary!"
            return ret

    container = await hub.exec.azurerm.storage.container.get(
        ctx, name, account, resource_group, **connection_auth)

    if "error" not in container:
        action = "update"

        metadata_changes = differ.deep_diff(container.get("metadata", {}),
                                            metadata or {})
        if metadata_changes:
            ret["changes"]["metadata"] = metadata_changes

        if public_access and public_access != container.get("public_access"):
            ret["changes"]["public_access"] = {
                "old": container.get("public_access"),
                "new": public_access,
            }

        if deny_encryption_scope_override is not None:
            ret["changes"]["deny_encryption_scope_override"] = {
                "old": container.get("deny_encryption_scope_override"),
                "new": deny_encryption_scope_override,
            }

        if default_encryption_scope:
            ret["changes"]["default_encryption_scope"] = {
                "old": container.get("default_encryption_scope"),
                "new": default_encryption_scope,
            }

        if not ret["changes"]:
            ret["result"] = True
            ret["comment"] = "Blob container {0} is already present.".format(
                name)
            return ret

        if ctx["test"]:
            ret["result"] = None
            ret["comment"] = "Blob container {0} would be updated.".format(
                name)
            return ret

    if ctx["test"]:
        ret["comment"] = "Blob container {0} would be created.".format(name)
        ret["result"] = None
        return ret

    container_kwargs = kwargs.copy()
    container_kwargs.update(connection_auth)

    if action == "create":
        container = await hub.exec.azurerm.storage.container.create(
            ctx=ctx,
            name=name,
            account=account,
            resource_group=resource_group,
            public_access=public_access,
            deny_encryption_scope_override=deny_encryption_scope_override,
            default_encryption_scope=default_encryption_scope,
            metadata=metadata,
            **container_kwargs,
        )
    else:
        container = await hub.exec.azurerm.storage.container.update(
            ctx=ctx,
            name=name,
            account=account,
            resource_group=resource_group,
            public_access=public_access,
            deny_encryption_scope_override=deny_encryption_scope_override,
            default_encryption_scope=default_encryption_scope,
            metadata=metadata,
            **container_kwargs,
        )

    if action == "create":
        ret["changes"] = {"old": {}, "new": container}

    if "error" not in container:
        ret["result"] = True
        ret["comment"] = f"Blob container {name} has been {action}d."
        return ret

    ret["comment"] = "Failed to {0} blob container {1}! ({2})".format(
        action, name, container.get("error"))
    if not ret["result"]:
        ret["changes"] = {}
    return ret