def _process(self):
        # Prepare message
        msg = []
        for msg_index in range(0,len(self.message)):
            if self.exit_on_failure is False:
                msg_header = '{}{}'.format(self.exception_name,
                                           colorful.bold_white(':')) if msg_index == 0 else ' '*(len(self.exception_name)+1)
                msg.append('\t\t{} {}'.format(colorful.bold_red(msg_header), colorful.red(self.message[msg_index])))
            else:
                msg.append(self.message[msg_index] if msg_index == 0
                                                   else '{}{} {} {}'.format("\t"*2,
                                                                            ' '*(len(self.exception_name)+1),
                                                                            colorful.bold_white(':'),
                                                                            self.message[msg_index]))

        if self.exit_on_failure is False:
            for message in msg:
                console_write(message)

            if self.no_failure is False:
                self._fail_step(self.step_obj.id)
            else:
                self.step_obj.state = Step.State.SKIPPED
            return

        if self.no_failure is False:
            raise self.exception('\n'.join(msg))
Exemplo n.º 2
0
def skip_step(step, resource=None, message=None):
    if resource is None:
        resource = 'any'

    if message is None:
        message = '{} {} {}'.format(Defaults().yellow('Can not find'),
                                    Defaults().green(resource),
                                    Defaults().yellow('defined in target terraform plan.'))
        e_message = 'Can not find {} defined in target terraform plan.'.format(resource)
    else:
        e_message = message
        message = Defaults().yellow(message)


    if step.context.no_skip:
        if -1 in step.context.lines_to_noskip or step.line in step.context.lines_to_noskip:
            message = Defaults().failure_colour(message)
            Error(step, e_message)
            return
    
    if str(world.config.formatter) in ('gherkin'):
        console_write("\t{} {}: {}".format(Defaults().info_icon,
                                           Defaults().skip_colour('SKIPPING'),
                                           message.format(resource=Defaults().green(resource)))
        )
    step.skip()

    # Skip all steps in the scenario
    for each in step.parent.all_steps:
        each.runable = False
Exemplo n.º 3
0
def write_error(text):
    """
        Writes the given text to the console
    """
    console_write("{} {}: {}".format(Defaults().warning_icon,
                                     Defaults().failure_colour("ERROR"),
                                     Defaults().warning_colour(text)))
Exemplo n.º 4
0
def write_stdout(level, message):

    prefix = colorful.bold_yellow(u'\u229b INFO :')
    if level == 'WARNING':
        prefix = colorful.bold_red(u'\u2757 WARNING :')
        message = colorful.yellow(message)


    added_prefix = u'\n\t\t{}\t{} '.format(colorful.gray(u'\u2502'),' '*len(prefix))
    message = message.split('\n')

    console_write(u'\t\t\u251c\u2501\t{} {}'.format(prefix, added_prefix.join(message)))
Exemplo n.º 5
0
def python_version_check():
    python_version = sys.version.split(' ')[0]

    if not python_version:
        raise TerraformComplianceInternalFailure('Could not determine python version. '
                                                 'Please post this to issues: '.format(sys.version))

    python_version = VersionInfo.parse(python_version)

    if compare(str(python_version), Defaults.supported_min_python_versions) < 0:
        console_write('ERROR: Python version {} is not supported. '
                      'You must have minimum {} version.'.format(python_version,
                                                                 Defaults.supported_min_python_versions[0]))
        sys.exit(1)

    return True
    def _process(self):
        # Prepare message
        msg = []
        for msg_index in range(0, len(self.message)):
            if self.exit_on_failure is False or self.no_failure is True:
                msg_header = '{}{}'.format(
                    self.exception_name, colorful.bold_white(':')
                ) if msg_index == 0 else ' ' * (len(self.exception_name) + 1)
                if str(world.config.formatter) in ('gherkin'):
                    # this line could be improved by letting radish handle the printing
                    msg.append('\t\t{} {}'.format(
                        colorful.bold_red(msg_header),
                        colorful.red(self.message[msg_index])))
                elif str(world.config.formatter) in ('silent_formatter'):
                    msg.append('{} '.format(colorful.bold_red(msg_header)))
                    msg.append('{}'.format(
                        colorful.red(self.message[msg_index])))

            else:
                msg.append(
                    self.message[msg_index] if msg_index == 0 else '{}{} {} {}'
                    .format("\t" * 2, ' ' * (len(self.exception_name) + 1),
                            colorful.bold_white(':'), self.message[msg_index]))

        if self.exit_on_failure is False or (self.no_failure is True and msg):

            if str(world.config.formatter) in ('gherkin'):
                for message in msg:
                    console_write(message)
            elif str(world.config.formatter) in ('silent_formatter'):
                if not hasattr(self.step_obj.context,
                               'failure_msg'):  # where to put this
                    self.step_obj.context.failure_msg = []
                self.step_obj.context.failure_msg.extend(msg)

            if self.no_failure is False:
                self._fail_step(self.step_obj.id)
            else:
                self.step_obj.state = Step.State.SKIPPED
                for step in self.step_obj.parent.all_steps:
                    step.runable = False
            return

        if self.no_failure is False:
            raise self.exception('\n'.join(msg))
Exemplo n.º 7
0
    def silent_formatter_after_each_feature(self, feature):
        """
            Writes failing features to the console

            :param Feature feature: the feature to write to the console
        """
        if not any(scenario.state == 'failed'
                   for scenario in feature.all_scenarios):
            return

        # ConsoleWriter.console_writer_before_each_feature(self, feature)
        self.console_writer_before_each_feature(feature)

        for scenario in feature.all_scenarios:
            self.silent_formatter_after_each_scenario(scenario)

        # one newline between final scenario and results summary
        console_write('')
Exemplo n.º 8
0
def skip_step(step, resource=None, message=None):
    if resource is None:
        resource = 'any'

    if message is None:
        message = '{} {} {}'.format(
            colorful.orange('Can not find'), colorful.magenta(resource),
            colorful.orange('defined in target terraform plan.'))
    else:
        message = colorful.orange(message)

    console_write("\t{}: {}".format(
        colorful.bold_purple('SKIPPING'),
        message.format(resource=colorful.magenta(resource))))
    step.skip()

    # Skip all steps in the scenario
    for each in step.parent.all_steps:
        each.runable = False
def skip_step(step, resource=None, message=None):
    if resource is None:
        resource = 'any'

    if message is None:
        message = '{} {} {}'.format(Defaults().yellow('Can not find'),
                                    Defaults().green(resource),
                                    Defaults().yellow('defined in target terraform plan.'))
    else:
        message = Defaults().yellow(message)

    if str(world.config.formatter) in ('gherkin'):
        console_write("\t{} {}: {}".format(Defaults().info_icon,
                                           Defaults().skip_colour('SKIPPING'),
                                           message.format(resource=Defaults().green(resource)))
        )
    step.skip()

    # Skip all steps in the scenario
    for each in step.parent.all_steps:
        each.runable = False
Exemplo n.º 10
0
def handle_exception(exception):
    """
        Handle the given exception

        This will print more information about the given exception

        :param Exception exception: the exception to handle
    """
    if isinstance(exception, HookError):
        write_error(exception)
        write_failure(exception.failure)
        abort(1)
    elif isinstance(exception, RadishError):
        write_error(handle_radish_errors(exception))
        abort(1)
    elif isinstance(exception, KeyboardInterrupt):
        console_write("Aborted by the user...")
        abort(1)
    else:
        write_error(exception)
        write_failure(Failure(exception))
        abort(2)
    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)
Exemplo n.º 12
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
def it_contains_something_old(_step_obj, something, inherited_values=Null):
    console_write("\t{} {}: {}".format(
        Defaults().warning_icon,
        Defaults().warning_colour('WARNING'),
        Defaults().info_colour(
            '"When it contains {}" step functionality will be changed'
            ' on future versions and the functionality will be same '
            'as "When it has {}" step. Please use the '
            'latter.'.format(something, something))))
    match = _step_obj.context.match
    seek_key_in_dict = match.seek_key_in_dict
    seek_regex_key_in_dict_values = match.seek_regex_key_in_dict_values

    prop_list = []

    _step_obj.context.stash = inherited_values if inherited_values is not Null else _step_obj.context.stash

    if _step_obj.context.type in ('resource', 'data'):
        for resource in _step_obj.context.stash:
            if not isinstance(resource, dict) \
                    or 'values' not in resource \
                    or 'address' not in resource \
                    or 'type' not in resource:
                resource = {
                    'values': resource,
                    'address': resource,
                    'type': _step_obj.context.name
                }

            values = resource.get('values', resource.get('expressions', {}))
            if not values:
                values = seek_key_in_dict(resource, something)

            found_value = Null
            found_key = Null
            if isinstance(values, dict):
                found_key = match.get(values, something,
                                      seek_key_in_dict(values, something))
                if not isinstance(found_key, list):
                    found_key = [{something: found_key}]

                if len(found_key):
                    found_key = found_key[0] if len(
                        found_key) == 1 and match.contains(
                            found_key[0], something) else found_key

                    if isinstance(found_key, dict):
                        found_value = match.get(found_key, something,
                                                found_key)
                    else:
                        found_value = found_key
            elif isinstance(values, list):
                found_value = []

                for value in values:

                    if isinstance(value, dict):
                        # First search in the keys
                        found_key = seek_key_in_dict(value, something)

                        # Then search in the values with 'key'
                        if not found_key:
                            found_key = seek_regex_key_in_dict_values(
                                value, 'key', something)

                            if found_key:
                                found_key = found_key[0]
                                found_value = value.get('value')
                                break
                    elif isinstance(value, list):
                        found_key, found_value = it_contains_something_old(
                            _step_obj, something, value)

                    if found_key is not Null and len(found_key):
                        found_key = found_key[0] if len(
                            found_key) == 1 else found_key

                        if isinstance(found_key, dict):
                            found_value.append(
                                match.get(found_key, something, found_key))

            if isinstance(found_value,
                          dict) and 'constant_value' in found_value:
                found_value = found_value['constant_value']

            if found_value is not Null and found_value != [] and found_value != '' and found_value != {}:
                prop_list.append({
                    'address': resource['address'],
                    'values': found_value,
                    'type': _step_obj.context.name
                })

        if prop_list:
            _step_obj.context.stash = prop_list
            _step_obj.context.property_name = something

            return something, prop_list

        if _step_obj.state != Step.State.FAILED:
            skip_step(
                _step_obj,
                resource=_step_obj.context.name,
                message='Can not find any {} property for {} resource in '
                'terraform plan.'.format(something, _step_obj.context.name))

    elif _step_obj.context.type == 'provider':
        for provider_data in _step_obj.context.stash:
            values = seek_key_in_dict(provider_data, something)

            if values:
                _step_obj.context.stash = values
                _step_obj.context.property_name = something
                _step_obj.context.address = '{}.{}'.format(
                    provider_data.get('name', _step_obj.context.addresses),
                    provider_data.get('alias', "\b"))
                return True

    if _step_obj.state != Step.State.FAILED:
        skip_step(
            _step_obj,
            resource=_step_obj.context.name,
            message='Skipping the step since {} type does not have {} property.'
            .format(_step_obj.context.type, something))
Exemplo n.º 14
0
def cli(arghandling=ArgHandling(), argparser=ArgumentParser(prog=__app_name__,
                                                            description='BDD Test Framework for Hashicorp terraform')):
    args = arghandling
    parser = argparser
    parser.add_argument('--terraform', '-t', dest='terraform_file', metavar='terraform_file', type=str, nargs='?',
                        help='The absolute path to the terraform executable.', required=False)
    parser.add_argument('--features', '-f', dest='features', metavar='feature directory', action=ReadableDir,
                        help='Directory (or git repository with "git:" prefix) consists of BDD features', required=True)
    parser.add_argument('--planfile', '-p', dest='plan_file', metavar='plan_file', action=ReadablePlan,
                        help='Plan output file generated by Terraform', required=True)
    parser.add_argument('--quit-early', '-q', dest='exit_on_failure', action='store_true',
                        help='Stops executing any more steps in a scenario on first failure.', required=False)
    parser.add_argument('--no-failure', '-n', dest='no_failure', action='store_true',
                        help='Skip all the tests that is failed, but giving proper failure message', required=False)
    parser.add_argument('--silent', '-S', dest='silence', action='store_true',
                        help='Do not output any scenarios, just write results or failures', required=False)
    parser.add_argument('--identity', '-i', dest='ssh_key', metavar='ssh private key', type=str, nargs='?',
                        help='SSH Private key that will be use on git authentication.', required=False)

    parser.add_argument('--version', '-v', action='version', version=__version__)

    _, radish_arguments = parser.parse_known_args(namespace=args)

    steps_directory = os.path.join(os.path.split(os.path.abspath(__file__))[0], 'steps')

    # SSH Key is given for git authentication
    ssh_cmd = {}
    if args.ssh_key:
        ssh_cmd = {'GIT_SSH_COMMAND': 'ssh -l {} -i {}'.format('git', args.ssh_key)}

    features_dir = '/'
    # A remote repository used here
    if args.features.startswith(('http', 'https', 'ssh')):
        # Default to master branch and full repository
        if args.features.endswith('.git'):
            features_git_repo = args.features
            features_git_branch = 'master'

        # Optionally allow for directory and branch
        elif '.git//' in args.features and '?ref=' in args.features:
            # Split on .git/
            features_git_list = args.features.split('.git/', 1)
            # Everything up to .git is the repository
            features_git_repo = features_git_list[0] + '.git'

            # Split the directory and branch ref
            features_git_list = features_git_list[1].split('?ref=', 1)
            features_dir = features_git_list[0]
            features_git_branch = features_git_list[1]

        else:  # invalid
            raise ValueError("Bad feature directory:" + args.features)

        # Clone repository
        args.features = mkdtemp()
        Repo.clone_from(url=features_git_repo, to_path=args.features, env=ssh_cmd, depth=1, branch=features_git_branch)

    features_directory = os.path.join(os.path.abspath(args.features) + features_dir)

    commands = ['radish',
                '--write-steps-once',
                features_directory,
                '--basedir', steps_directory,
                '--user-data=plan_file={}'.format(args.plan_file),
                '--user-data=exit_on_failure={}'.format(args.exit_on_failure),
                '--user-data=terraform_executable={}'.format(args.terraform_file),
                '--user-data=no_failure={}'.format(args.no_failure),
                '--user-data=silence_mode_enabled={}'.format(args.silence)]
    commands.extend(radish_arguments)

    console_write('{} {} {}{}'.format(Defaults().icon,
                                      Defaults().yellow('Features\t:'),
                                      features_directory,
                                       (' ({})'.format(features_git_repo) if 'features_git_repo' in locals() else '')))

    console_write('{} {} {}'.format(Defaults().icon,
                                    Defaults().green('Plan File\t:'),
                                    args.plan_file))

    if args.silence is True:
        console_write('{} Suppressing output enabled.'.format(Defaults().icon))
        commands.append('--formatter=dotter')

    if args.exit_on_failure is True:
        console_write('{} {}\t\t: Scenario executions will stop on first step {}.'.format(Defaults().info_icon,
                                                                                          Defaults().info_colour('INFO'),
                                                                                          Defaults().failure_colour('failure')))

    if args.no_failure is True:
        console_write('{} {}\t: {}ping all {} steps, exit code will always be {}.'.format(Defaults().warning_icon,
                                                                                          Defaults().warning_colour('WARNING'),
                                                                                          Defaults().skip_colour('SKIP'),
                                                                                          Defaults().failure_colour('failure'),
                                                                                          Defaults().info_colour(0)))

    if Defaults().interactive_mode is False:
        console_write('{} Running in non-interactive mode.'.format(Defaults().info_icon))

    if args.silence is False:
        console_write('\n{} Running tests. {}\n'.format(Defaults().icon,
                                                        Defaults().tada))

    try:
        result = call_radish(args=commands[1:])
    except IndexError as e:
        print(e)

    return result
Exemplo n.º 15
0
def write_failure(failure):
    """
        Writes the failure to the console
    """
    console_write("\n{0}".format(Defaults().failure_colour(failure.traceback)))