Exemple #1
0
def compare_list_of_dicts(old, new, convert_id_to_name=None):
    '''
    Compare lists of dictionaries representing Azure objects. Only keys found in the "new" dictionaries are compared to
    the "old" dictionaries, since getting Azure objects from the API returns some read-only data which should not be
    used in the comparison. A list of parameter names can be passed in order to compare a bare object name to a full
    Azure ID path for brevity. If string types are found in values, comparison is case insensitive. Return comment
    should be used to trigger exit from the calling function.
    '''
    ret = {}

    if not convert_id_to_name:
        convert_id_to_name = []

    if not isinstance(new, list):
        ret['comment'] = 'must be provided as a list of dictionaries!'
        return ret

    if len(new) != len(old):
        ret['changes'] = {
            'old': old,
            'new': new
        }
        return ret

    try:
        local_configs, remote_configs = [sorted(config, key=itemgetter('name')) for config in (new, old)]
    except TypeError:
        ret['comment'] = 'configurations must be provided as a list of dictionaries!'
        return ret
    except KeyError:
        ret['comment'] = 'configuration dictionaries must contain the "name" key!'
        return ret

    for idx in six_range(0, len(local_configs)):
        for key in local_configs[idx]:
            local_val = local_configs[idx][key]
            if key in convert_id_to_name:
                remote_val = remote_configs[idx].get(key, {}).get('id', '').split('/')[-1]
            else:
                remote_val = remote_configs[idx].get(key)
                if isinstance(local_val, six.string_types):
                    local_val = local_val.lower()
                if isinstance(remote_val, six.string_types):
                    remote_val = remote_val.lower()
            if local_val != remote_val:
                ret['changes'] = {
                    'old': remote_configs,
                    'new': local_configs
                }
                return ret

    return ret
def record_set_present(
    name,
    zone_name,
    resource_group,
    record_type,
    if_match=None,
    if_none_match=None,
    etag=None,
    metadata=None,
    ttl=None,
    arecords=None,
    aaaa_records=None,
    mx_records=None,
    ns_records=None,
    ptr_records=None,
    srv_records=None,
    txt_records=None,
    cname_record=None,
    soa_record=None,
    caa_records=None,
    connection_auth=None,
    **kwargs
):
    """
    .. versionadded:: 3000

    Ensure a record set exists in a DNS zone.

    :param name:
        The name of the record set, relative to the name of the zone.

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

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

    :param record_type:
        The type of DNS record in this record set. Record sets of type SOA can be updated but not created
        (they are created when the DNS zone is created). Possible values include: 'A', 'AAAA', 'CAA', 'CNAME',
        'MX', 'NS', 'PTR', 'SOA', 'SRV', 'TXT'

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

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

    :param etag:
        The etag of the record set. `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 metadata:
        A dictionary of strings can be passed as tag metadata to the record set object.

    :param ttl:
        The TTL (time-to-live) of the records in the record set. Required when specifying record information.

    :param arecords:
        The list of A records in the record set. View the
        `Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.arecord?view=azure-python>`__
        to create a list of dictionaries representing the record objects.

    :param aaaa_records:
        The list of AAAA records in the record set. View the
        `Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.aaaarecord?view=azure-python>`__
        to create a list of dictionaries representing the record objects.

    :param mx_records:
        The list of MX records in the record set. View the
        `Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.mxrecord?view=azure-python>`__
        to create a list of dictionaries representing the record objects.

    :param ns_records:
        The list of NS records in the record set. View the
        `Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.nsrecord?view=azure-python>`__
        to create a list of dictionaries representing the record objects.

    :param ptr_records:
        The list of PTR records in the record set. View the
        `Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.ptrrecord?view=azure-python>`__
        to create a list of dictionaries representing the record objects.

    :param srv_records:
        The list of SRV records in the record set. View the
        `Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.srvrecord?view=azure-python>`__
        to create a list of dictionaries representing the record objects.

    :param txt_records:
        The list of TXT records in the record set. View the
        `Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.txtrecord?view=azure-python>`__
        to create a list of dictionaries representing the record objects.

    :param cname_record:
        The CNAME record in the record set. View the
        `Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.cnamerecord?view=azure-python>`__
        to create a dictionary representing the record object.

    :param soa_record:
        The SOA record in the record set. View the
        `Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.soarecord?view=azure-python>`__
        to create a dictionary representing the record object.

    :param caa_records:
        The list of CAA records in the record set. View the
        `Azure SDK documentation <https://docs.microsoft.com/en-us/python/api/azure.mgmt.dns.models.caarecord?view=azure-python>`__
        to create a list of dictionaries representing the record objects.

    :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 record set exists:
            azurearm_dns.record_set_present:
                - name: web
                - zone_name: contoso.com
                - resource_group: my_rg
                - record_type: A
                - ttl: 300
                - arecords:
                  - ipv4_address: 10.0.0.1
                - metadata:
                    how_awesome: very
                    contact_name: Elmer Fudd Gantry
                - connection_auth: {{ profile }}

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

    record_vars = [
        "arecords",
        "aaaa_records",
        "mx_records",
        "ns_records",
        "ptr_records",
        "srv_records",
        "txt_records",
        "cname_record",
        "soa_record",
        "caa_records",
    ]

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

    rec_set = __salt__["azurearm_dns.record_set_get"](
        name,
        zone_name,
        resource_group,
        record_type,
        azurearm_log_level="info",
        **connection_auth
    )

    if "error" not in rec_set:
        metadata_changes = __utils__["dictdiffer.deep_diff"](
            rec_set.get("metadata", {}), metadata or {}
        )
        if metadata_changes:
            ret["changes"]["metadata"] = metadata_changes

        for record_str in record_vars:
            # pylint: disable=eval-used
            record = eval(record_str)
            if record:
                if not ttl:
                    ret[
                        "comment"
                    ] = "TTL is required when specifying record information!"
                    return ret
                if not rec_set.get(record_str):
                    ret["changes"] = {"new": {record_str: record}}
                    continue
                if record_str[-1] != "s":
                    if not isinstance(record, dict):
                        ret[
                            "comment"
                        ] = "{0} record information must be specified as a dictionary!".format(
                            record_str
                        )
                        return ret
                    for k, v in record.items():
                        if v != rec_set[record_str].get(k):
                            ret["changes"] = {"new": {record_str: record}}
                elif record_str[-1] == "s":
                    if not isinstance(record, list):
                        ret[
                            "comment"
                        ] = "{0} record information must be specified as a list of dictionaries!".format(
                            record_str
                        )
                        return ret
                    local, remote = [
                        sorted(config) for config in (record, rec_set[record_str])
                    ]
                    for idx in six_range(0, len(local)):
                        for key in local[idx]:
                            local_val = local[idx][key]
                            remote_val = remote[idx].get(key)
                            if isinstance(local_val, six.string_types):
                                local_val = local_val.lower()
                            if isinstance(remote_val, six.string_types):
                                remote_val = remote_val.lower()
                            if local_val != remote_val:
                                ret["changes"] = {"new": {record_str: record}}

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

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

    else:
        ret["changes"] = {
            "old": {},
            "new": {
                "name": name,
                "zone_name": zone_name,
                "resource_group": resource_group,
                "record_type": record_type,
                "etag": etag,
                "metadata": metadata,
                "ttl": ttl,
            },
        }
        for record in record_vars:
            # pylint: disable=eval-used
            if eval(record):
                # pylint: disable=eval-used
                ret["changes"]["new"][record] = eval(record)

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

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

    rec_set = __salt__["azurearm_dns.record_set_create_or_update"](
        name=name,
        zone_name=zone_name,
        resource_group=resource_group,
        record_type=record_type,
        if_match=if_match,
        if_none_match=if_none_match,
        etag=etag,
        ttl=ttl,
        metadata=metadata,
        arecords=arecords,
        aaaa_records=aaaa_records,
        mx_records=mx_records,
        ns_records=ns_records,
        ptr_records=ptr_records,
        srv_records=srv_records,
        txt_records=txt_records,
        cname_record=cname_record,
        soa_record=soa_record,
        caa_records=caa_records,
        **rec_set_kwargs
    )

    if "error" not in rec_set:
        ret["result"] = True
        ret["comment"] = "Record set {0} has been created.".format(name)
        return ret

    ret["comment"] = "Failed to create record set {0}! ({1})".format(
        name, rec_set.get("error")
    )
    return ret
Exemple #3
0
def get_client(client_type, **kwargs):
    """
    Dynamically load the selected client and return a management client object
    '''
    client_map = {'compute': 'ComputeManagement',
                  'authorization': 'AuthorizationManagement',
                  'dns': 'DnsManagement',
                  'storage': 'StorageManagement',
                  'managementlock': 'ManagementLock',
                  'monitor': 'MonitorManagement',
                  'network': 'NetworkManagement',
                  'policy': 'Policy',
                  'resource': 'ResourceManagement',
                  'subscription': 'Subscription',
                  'web': 'WebSiteManagement'}

    if client_type not in client_map:
        raise SaltSystemExit(
            msg='The Azure ARM client_type {0} specified can not be found.'.format(
                client_type)
        )

    map_value = client_map[client_type]

    if client_type in ['policy', 'subscription']:
        module_name = 'resource'
    elif client_type in ['managementlock']:
        module_name = 'resource.locks'
    else:
        module_name = client_type

    try:
        client_module = importlib.import_module("azure.mgmt." + module_name)
        # pylint: disable=invalid-name
        Client = getattr(client_module, "{0}Client".format(map_value))
    except ImportError:
        raise sys.exit("The azure {0} client is not available.".format(client_type))

    credentials, subscription_id, cloud_env = _determine_auth(**kwargs)

    if client_type == 'subscription':
        client = Client(
            credentials=credentials, base_url=cloud_env.endpoints.resource_manager,
        )
    else:
        client = Client(
            credentials=credentials,
            subscription_id=subscription_id,
            base_url=cloud_env.endpoints.resource_manager,
        )

    client.config.add_user_agent("Salt/{0}".format(salt.version.__version__))

    return client


def log_cloud_error(client, message, **kwargs):
    """
    Log an azurearm cloud error exception
    """
    try:
        cloud_logger = getattr(log, kwargs.get("azurearm_log_level"))
    except (AttributeError, TypeError):
        cloud_logger = getattr(log, "error")

    cloud_logger(
        "An AzureARM %s CloudError has occurred: %s", client.capitalize(), message
    )

    return


def paged_object_to_list(paged_object):
    """
    Extract all pages within a paged object as a list of dictionaries
    """
    paged_return = []
    while True:
        try:
            page = next(paged_object)
            paged_return.append(page.as_dict())
        except CloudError:
            raise
        except StopIteration:
            break

    return paged_return


def create_object_model(module_name, object_name, **kwargs):
    """
    Assemble an object from incoming parameters.
    """
    object_kwargs = {}

    try:
        model_module = importlib.import_module(
            "azure.mgmt.{0}.models".format(module_name)
        )
        # pylint: disable=invalid-name
        Model = getattr(model_module, object_name)
    except ImportError:
        raise sys.exit(
            "The {0} model in the {1} Azure module is not available.".format(
                object_name, module_name
            )
        )

    if "_attribute_map" in dir(Model):
        for attr, items in Model._attribute_map.items():
            param = kwargs.get(attr)
            if param is not None:
                if items["type"][0].isupper() and isinstance(param, dict):
                    object_kwargs[attr] = create_object_model(
                        module_name, items["type"], **param
                    )
                elif items["type"][0] == "{" and isinstance(param, dict):
                    object_kwargs[attr] = param
                elif items["type"][0] == "[" and isinstance(param, list):
                    obj_list = []
                    for list_item in param:
                        if items["type"][1].isupper() and isinstance(list_item, dict):
                            obj_list.append(
                                create_object_model(
                                    module_name,
                                    items["type"][
                                        items["type"].index("[")
                                        + 1 : items["type"].rindex("]")
                                    ],
                                    **list_item
                                )
                            )
                        elif items["type"][1] == "{" and isinstance(list_item, dict):
                            obj_list.append(list_item)
                        elif not items["type"][1].isupper() and items["type"][1] != "{":
                            obj_list.append(list_item)
                    object_kwargs[attr] = obj_list
                else:
                    object_kwargs[attr] = param

    # wrap calls to this function to catch TypeError exceptions
    return Model(**object_kwargs)


def compare_list_of_dicts(old, new, convert_id_to_name=None):
    """
    Compare lists of dictionaries representing Azure objects. Only keys found in the "new" dictionaries are compared to
    the "old" dictionaries, since getting Azure objects from the API returns some read-only data which should not be
    used in the comparison. A list of parameter names can be passed in order to compare a bare object name to a full
    Azure ID path for brevity. If string types are found in values, comparison is case insensitive. Return comment
    should be used to trigger exit from the calling function.
    """
    ret = {}

    if not convert_id_to_name:
        convert_id_to_name = []

    if not isinstance(new, list):
        ret["comment"] = "must be provided as a list of dictionaries!"
        return ret

    if len(new) != len(old):
        ret["changes"] = {"old": old, "new": new}
        return ret

    try:
        local_configs, remote_configs = [
            sorted(config, key=itemgetter("name")) for config in (new, old)
        ]
    except TypeError:
        ret["comment"] = "configurations must be provided as a list of dictionaries!"
        return ret
    except KeyError:
        ret["comment"] = 'configuration dictionaries must contain the "name" key!'
        return ret

    for idx in six_range(0, len(local_configs)):
        for key in local_configs[idx]:
            local_val = local_configs[idx][key]
            if key in convert_id_to_name:
                remote_val = (
                    remote_configs[idx].get(key, {}).get("id", "").split("/")[-1]
                )
            else:
                remote_val = remote_configs[idx].get(key)
                if isinstance(local_val, six.string_types):
                    local_val = local_val.lower()
                if isinstance(remote_val, six.string_types):
                    remote_val = remote_val.lower()
            if local_val != remote_val:
                ret["changes"] = {"old": remote_configs, "new": local_configs}
                return ret

    return ret