def _mount_references(self):
        '''
        Find the references that is defined in self.configuration
        :return:
        '''
        self.resources_raw = deepcopy(self.resources)
        invalid_references = ('var.', 'each.')

        # This section will link resources found in configuration part of the plan output.
        # The reference should be on both ways (A->B, B->A) since terraform sometimes report these references
        # in opposite ways, depending on the provider structure.
        for resource in self.configuration['resources']:
            if 'expressions' in self.configuration['resources'][resource]:
                ref_list = {}
                for key, value in self.configuration['resources'][resource][
                        'expressions'].items():
                    references = seek_key_in_dict(
                        value, 'references') if isinstance(
                            value, (dict, list)) else []

                    valid_references = []
                    for ref in references:
                        if isinstance(ref, dict) and ref.get('references'):
                            valid_references = [
                                r for r in ref['references']
                                if not r.startswith(invalid_references)
                            ]

                    for ref in valid_references:
                        # if ref is not in the correct format, handle it
                        if len(ref.split('.')) < 3 and ref.startswith(
                                'module'):

                            # Using for_each and modules together may introduce an issue where the plan.out.json won't include the necessary third part of the reference
                            # It is partially resolved by mounting the reference to all instances belonging to the module
                            if 'for_each_expression' in self.configuration[
                                    'resources'][resource]:

                                # extract source resources
                                assumed_source_resources = [
                                    k for k in self.resources.keys()
                                    if k.startswith(resource)
                                ]
                                # extract for_each keys
                                assumed_for_each_keys = [
                                    k[len(resource):].split('.')[0]
                                    for k in assumed_source_resources
                                ]
                                # combine ref with for each keys
                                assumed_refs = [
                                    '{}{}'.format(ref, key)
                                    for key in assumed_for_each_keys
                                ]
                                # get all the resources that start with updated ref
                                ambigious_references = []
                                for r in self.resources.keys():
                                    for assumed_ref in assumed_refs:
                                        if r.startswith(assumed_ref):
                                            if key in ref_list:
                                                ref_list[key].append(r)
                                            else:
                                                ref_list[key] = [r]

                                            ambigious_references.append(r)

                                # throw a warning
                                defaults = Defaults()
                                console_write('{} {}: {}'.format(
                                    defaults.warning_icon,
                                    defaults.warning_colour(
                                        'WARNING (Mounting)'),
                                    defaults.info_colour(
                                        'The reference "{}" in resource {} is ambigious.'
                                        ' It will be mounted to the following resources:'
                                    ).format(ref, resource)))
                                for i, r in enumerate(ambigious_references, 1):
                                    console_write(
                                        defaults.info_colour('{}. {}'.format(
                                            i, r)))

                            # if the reference can not be resolved, warn the user and continue.
                            else:
                                console_write('{} {}: {}'.format(
                                    Defaults().warning_icon,
                                    Defaults().warning_colour(
                                        'WARNING (Mounting)'),
                                    Defaults().info_colour(
                                        'The reference "{}" in resource {} is ambigious. It will not be mounted.'
                                        .format(ref, resource))))
                                continue
                        elif key not in ref_list:
                            ref_list[key] = self._find_resource_from_name(ref)
                        else:
                            ref_list[key].extend(
                                self._find_resource_from_name(ref))

                    # This is where we synchronise constant_value in the configuration section with the resource
                    # for filling up the missing elements that hasn't been defined in the resource due to provider
                    # implementation.
                    target_resource = [
                        t for t in
                        [self.resources.get(resource, {}).get('address')]
                        if t is not None
                    ]
                    if not target_resource:
                        target_resource = [
                            k for k in self.resources.keys()
                            if k.startswith(resource)
                        ]

                    for t_r in target_resource:
                        if type(value) is type(
                                self.resources[t_r]['values'].get(key)
                        ) and self.resources[t_r]['values'].get(key) != value:
                            if isinstance(value, (list, dict)):
                                merge_dicts(self.resources[t_r]['values'][key],
                                            value)

                if ref_list:
                    ref_type = self.configuration['resources'][resource][
                        'expressions'].get('type', {})

                    if 'references' in ref_type:
                        ref_type = resource.split('.')[0]

                    if not ref_type and not self.is_type(resource, 'data'):
                        ref_type = self.extract_resource_type_from_address(
                            resource)

                    for k, v in ref_list.items():
                        v = flatten_list(v)

                    # Mounting A->B
                    source_resources = self._find_resource_from_name(
                        self.configuration['resources'][resource]['address'])
                    self._mount_resources(source=source_resources,
                                          target=ref_list,
                                          ref_type=ref_type)

                    # Mounting B->A
                    for parameter, target_resources in ref_list.items():
                        for target_resource in target_resources:
                            if not self.is_type(
                                    resource, 'data') and not self.is_type(
                                        resource, 'var') and not self.is_type(
                                            resource, 'provider'):
                                ref_type = self.extract_resource_type_from_address(
                                    target_resource)

                                self._mount_resources(
                                    source=[target_resource],
                                    target={parameter: source_resources},
                                    ref_type=ref_type)
예제 #2
0
    def _mount_resources(self, source, target, ref_type):
        '''
        Mounts values of the source resource to the target resource's values with ref_type key

        :param source: source resource
        :param target: target resource
        :param ref_type: reference type (e.g. ingress )
        :return: none
        '''
        for source_resource in source:

            if 'values' not in self.resources.get(source_resource, {}):
                continue
            for parameter, target_resources in target.items():
                for target_resource in target_resources:
                    if target_resource not in self.resources or 'values' not in self.resources[
                            target_resource]:
                        continue

                    resource = self.resources_raw[source_resource]['values']

                    # This is a very stupid terraform-provider bug. Somehow, sometimes it loses the state
                    # and sets the value to None - which is normally not allowed.. It should have been an empty
                    # dict instead. Hence, we are fixing that here.
                    if resource is None:
                        defaults = Defaults()
                        console_write('{} {}: {}'.format(
                            defaults.warning_icon,
                            defaults.warning_colour('WARNING (mounting)'),
                            defaults.info_colour(
                                'The resource "{}" has no values set. This is a terraform provider '
                                'bug. Its recommended to remove/fix this resource within your state.'
                                .format(source_resource))))
                        self.resources_raw[source_resource]['values'] = {}
                        self.resources[source_resource]['values'] = {}
                        resource = {}

                    resource[Defaults.mounted_ptr] = True

                    if Defaults.r_mount_ptr not in self.resources[
                            target_resource]:
                        self.resources[target_resource][
                            Defaults.r_mount_ptr] = {}

                    if Defaults.r_mount_addr_ptr not in self.resources[
                            target_resource]:
                        self.resources[target_resource][
                            Defaults.r_mount_addr_ptr] = {}

                    if Defaults.r_mount_addr_ptr_list not in self.resources[
                            target_resource]:
                        self.resources[target_resource][
                            Defaults.r_mount_addr_ptr_list] = []

                    # ensure resources[target_resource]['values'] is an
                    # empty dict and not None
                    if not self.resources[target_resource]['values']:
                        self.resources[target_resource]['values'] = dict()

                    if ref_type not in self.resources[target_resource][
                            'values']:
                        self.resources[target_resource]['values'][
                            ref_type] = []

                    self.resources[target_resource]['values'][ref_type].append(
                        resource)
                    self.resources[target_resource][
                        Defaults.r_mount_ptr][parameter] = ref_type
                    self.resources[target_resource][
                        Defaults.r_mount_addr_ptr][parameter] = source
                    target_set = set(self.resources[target_resource][
                        Defaults.r_mount_addr_ptr_list])
                    source_set = set(source)
                    self.resources[target_resource][
                        Defaults.r_mount_addr_ptr_list] = list(target_set
                                                               | source_set)

                    if parameter not in self.resources[source_resource][
                            'values']:
                        self.resources[source_resource]['values'][
                            parameter] = target_resource