コード例 #1
0
def get_facts(state,
              name,
              args=None,
              ensure_hosts=None,
              apply_failed_hosts=True):
    '''
    Get a single fact for all hosts in the state.
    '''

    # Create an instance of the fact
    fact = get_fact_class(name)()

    if isinstance(fact, ShortFactBase):
        return get_short_facts(state,
                               fact,
                               args=args,
                               ensure_hosts=ensure_hosts)

    logger.debug('Getting fact: {0} (ensure_hosts: {1})'.format(
        name,
        ensure_hosts,
    ))

    args = args or []

    # Apply args or defaults
    sudo = state.config.SUDO
    sudo_user = state.config.SUDO_USER
    su_user = state.config.SU_USER
    ignore_errors = state.config.IGNORE_ERRORS
    shell_executable = state.config.SHELL
    use_sudo_password = state.config.USE_SUDO_PASSWORD

    # Facts can override the shell (winrm powershell vs cmd support)
    if fact.shell_executable:
        shell_executable = fact.shell_executable

    # Timeout for operations !== timeout for connect (config.CONNECT_TIMEOUT)
    timeout = None

    # If inside an operation, fetch global arguments
    current_global_kwargs = state.current_op_global_kwargs
    if current_global_kwargs:
        sudo = current_global_kwargs['sudo']
        sudo_user = current_global_kwargs['sudo_user']
        use_sudo_password = current_global_kwargs['use_sudo_password']
        su_user = current_global_kwargs['su_user']
        ignore_errors = current_global_kwargs['ignore_errors']
        timeout = current_global_kwargs['timeout']

    # Make a hash which keeps facts unique - but usable cross-deploy/threads.
    # Locks are used to maintain order.
    fact_hash = make_hash(
        (name, args, sudo, sudo_user, su_user, ignore_errors))

    # Already got this fact? Unlock and return them
    current_facts = state.facts.get(fact_hash, {})
    if current_facts:
        if not ensure_hosts or all(host in current_facts
                                   for host in ensure_hosts):
            return current_facts

    with FACT_LOCK:
        # Add any hosts we must have, whether considered in the inventory or not
        # (these hosts might be outside the --limit or current op limit_hosts).
        hosts = set(state.inventory.iter_active_hosts())
        if ensure_hosts:
            hosts.update(ensure_hosts)

        # Execute the command for each state inventory in a greenlet
        greenlet_to_host = {}

        for host in hosts:
            if host in current_facts:
                continue

            # Generate actual arguments by passing strings as jinja2 templates
            host_args = [get_arg_value(state, host, arg) for arg in args]

            command = _make_command(fact.command, host_args)
            requires_command = _make_command(fact.requires_command, host_args)
            if requires_command:
                command = '! command -v {0} > /dev/null || ({1})'.format(
                    requires_command,
                    command,
                )

            greenlet = state.fact_pool.spawn(
                host.run_shell_command,
                command,
                sudo=sudo,
                sudo_user=sudo_user,
                use_sudo_password=use_sudo_password,
                su_user=su_user,
                timeout=timeout,
                shell_executable=shell_executable,
                print_output=state.print_fact_output,
                print_input=state.print_fact_input,
                return_combined_output=True,
            )
            greenlet_to_host[greenlet] = host

        # Wait for all the commands to execute
        progress_prefix = 'fact: {0}'.format(name)
        if args:
            progress_prefix = '{0}{1}'.format(progress_prefix, args[0])

        with progress_spinner(
                greenlet_to_host.values(),
                prefix_message=progress_prefix,
        ) as progress:
            for greenlet in gevent.iwait(greenlet_to_host.keys()):
                host = greenlet_to_host[greenlet]
                progress(host)

        hostname_facts = {}
        failed_hosts = set()

        # Collect the facts and any failures
        for greenlet, host in six.iteritems(greenlet_to_host):
            status = False
            stdout = []

            try:
                status, combined_output_lines = greenlet.get()

            except (timeout_error, socket_error, SSHException) as e:
                failed_hosts.add(host)
                log_host_command_error(
                    host,
                    e,
                    timeout=timeout,
                )

            stdout, stderr = split_combined_output(combined_output_lines)

            data = fact.default()

            if status:
                if stdout:
                    data = fact.process(stdout)

            elif stderr:
                first_line = stderr[0]
                if (sudo_user and state.will_add_user(sudo_user)
                        and re.match(SUDO_REGEX, first_line)):
                    status = True
                if (su_user and state.will_add_user(su_user) and any(
                        re.match(regex, first_line) for regex in SU_REGEXES)):
                    status = True

            if not status:
                failed_hosts.add(host)

                if not state.print_fact_output:
                    print_host_combined_output(host, combined_output_lines)

                log_error_or_warning(
                    host,
                    ignore_errors,
                    description=('could not load fact: {0}{1}').format(
                        name, args or ''))

            hostname_facts[host] = data

        log_name = click.style(name, bold=True)

        filtered_args = list(filter(None, args))
        if filtered_args:
            log = 'Loaded fact {0}: {1}'.format(log_name, tuple(filtered_args))
        else:
            log = 'Loaded fact {0}'.format(log_name)

        if state.print_fact_info:
            logger.info(log)
        else:
            logger.debug(log)

        # Check we've not failed
        if not ignore_errors and apply_failed_hosts:
            state.fail_hosts(failed_hosts)

        # Assign the facts
        state.facts.setdefault(fact_hash, {}).update(hostname_facts)

    return state.facts[fact_hash]
コード例 #2
0
ファイル: facts.py プロジェクト: morrison12/pyinfra
def _get_fact(
    state,
    host,
    cls,
    args=None,
    kwargs=None,
    ensure_hosts=None,
    apply_failed_hosts=True,
    fact_hash=None,
):
    fact = cls()
    name = fact.name

    args = args or ()
    kwargs = kwargs or {}

    # Get the defaults *and* overrides by popping from kwargs, executor kwargs passed
    # into get_fact override everything else (applied below).
    override_kwargs, override_kwarg_keys = pop_global_arguments(
        kwargs,
        state=state,
        host=host,
        keys_to_check=get_executor_kwarg_keys(),
    )

    executor_kwargs = _get_executor_kwargs(
        state,
        host,
        override_kwargs=override_kwargs,
        override_kwarg_keys=override_kwarg_keys,
    )

    if args or kwargs:
        # Merges args & kwargs into a single kwargs dictionary
        kwargs = getcallargs(fact.command, *args, **kwargs)

    kwargs_str = get_kwargs_str(kwargs)
    logger.debug(
        "Getting fact: %s (%s) (ensure_hosts: %r)",
        name,
        kwargs_str,
        ensure_hosts,
    )

    if not host.connected:
        host.connect(
            reason=f"to load fact: {name} ({kwargs_str})",
            raise_exceptions=True,
        )

    ignore_errors = (host.current_op_global_kwargs or {}).get(
        "ignore_errors",
        state.config.IGNORE_ERRORS,
    )

    # Facts can override the shell (winrm powershell vs cmd support)
    if fact.shell_executable:
        executor_kwargs["shell_executable"] = fact.shell_executable

    command = _make_command(fact.command, kwargs)
    requires_command = _make_command(fact.requires_command, kwargs)
    if requires_command:
        command = StringCommand(
            # Command doesn't exist, return 0 *or* run & return fact command
            "!",
            "command",
            "-v",
            requires_command,
            ">/dev/null",
            "||",
            command,
        )

    status = False
    stdout = []
    combined_output_lines = []

    try:
        status, combined_output_lines = host.run_shell_command(
            command,
            print_output=state.print_fact_output,
            print_input=state.print_fact_input,
            return_combined_output=True,
            **executor_kwargs,
        )
    except (timeout_error, socket_error, SSHException) as e:
        log_host_command_error(
            host,
            e,
            timeout=executor_kwargs["timeout"],
        )

    stdout, stderr = split_combined_output(combined_output_lines)

    data = fact.default()

    if status:
        if stdout:
            data = fact.process(stdout)
    elif stderr:
        # If we have error output and that error is sudo or su stating the user
        # does not exist, do not fail but instead return the default fact value.
        # This allows for users that don't currently but may be created during
        # other operations.
        first_line = stderr[0]
        if executor_kwargs["sudo_user"] and re.match(SUDO_REGEX, first_line):
            status = True
        if executor_kwargs["su_user"] and any(
                re.match(regex, first_line) for regex in SU_REGEXES):
            status = True

    if status:
        log_message = "{0}{1}".format(
            host.print_prefix,
            "Loaded fact {0}{1}".format(
                click.style(name, bold=True),
                f" ({get_kwargs_str(kwargs)})" if kwargs else "",
            ),
        )
        if state.print_fact_info:
            logger.info(log_message)
        else:
            logger.debug(log_message)
    else:
        if not state.print_fact_output:
            print_host_combined_output(host, combined_output_lines)

        log_error_or_warning(
            host,
            ignore_errors,
            description=("could not load fact: {0} {1}").format(
                name, get_kwargs_str(kwargs)),
        )

    # Check we've not failed
    if not status and not ignore_errors and apply_failed_hosts:
        state.fail_hosts({host})

    if fact_hash:
        host.facts[fact_hash] = data
    return data
コード例 #3
0
ファイル: facts.py プロジェクト: wilypomegranate/pyinfra
def get_facts(
    state,
    name_or_cls,
    args=None,
    kwargs=None,
    ensure_hosts=None,
    apply_failed_hosts=True,
):
    '''
    Get a single fact for all hosts in the state.
    '''

    # TODO: tidy up the whole executor argument handling here!
    global_kwarg_overrides = {}
    if kwargs:
        global_kwarg_overrides.update({
            key: kwargs.pop(key)
            for key in get_executor_kwarg_keys() if key in kwargs
        })

    if isclass(name_or_cls) and issubclass(name_or_cls,
                                           (FactBase, ShortFactBase)):
        fact = name_or_cls()
        name = fact.name
    else:
        fact = get_fact_class(name_or_cls)()
        name = name_or_cls

    if isinstance(fact, ShortFactBase):
        return get_short_facts(state,
                               fact,
                               args=args,
                               ensure_hosts=ensure_hosts)

    args = args or ()
    kwargs = kwargs or {}
    if args or kwargs:
        # Merges args & kwargs into a single kwargs dictionary
        kwargs = getcallargs(fact.command, *args, **kwargs)

    logger.debug('Getting fact: {0} {1} (ensure_hosts: {2})'.format(
        name,
        get_kwargs_str(kwargs),
        ensure_hosts,
    ))

    # Apply args or defaults
    sudo = state.config.SUDO
    sudo_user = state.config.SUDO_USER
    su_user = state.config.SU_USER
    ignore_errors = state.config.IGNORE_ERRORS
    shell_executable = state.config.SHELL
    use_sudo_password = state.config.USE_SUDO_PASSWORD
    env = state.config.ENV

    # Facts can override the shell (winrm powershell vs cmd support)
    if fact.shell_executable:
        shell_executable = fact.shell_executable

    # Timeout for operations !== timeout for connect (config.CONNECT_TIMEOUT)
    timeout = None

    # If inside an operation, fetch global arguments
    current_global_kwargs = state.current_op_global_kwargs or {}
    # Allow `Host.get_fact` calls to explicitly override these
    current_global_kwargs.update(global_kwarg_overrides)
    if current_global_kwargs:
        sudo = current_global_kwargs.get('sudo', sudo)
        sudo_user = current_global_kwargs.get('sudo_user', sudo_user)
        use_sudo_password = current_global_kwargs.get('use_sudo_password',
                                                      use_sudo_password)
        su_user = current_global_kwargs.get('su_user', su_user)
        ignore_errors = current_global_kwargs.get('ignore_errors',
                                                  ignore_errors)
        timeout = current_global_kwargs.get('timeout', timeout)
        env = current_global_kwargs.get('env', env)

    # Make a hash which keeps facts unique - but usable cross-deploy/threads.
    # Locks are used to maintain order.
    fact_hash = make_hash(
        (name, kwargs, sudo, sudo_user, su_user, ignore_errors, env))

    # Already got this fact? Unlock and return them
    current_facts = state.facts.get(fact_hash, {})
    if current_facts:
        if not ensure_hosts or all(host in current_facts
                                   for host in ensure_hosts):
            return current_facts

    with FACT_LOCK:
        # Add any hosts we must have, whether considered in the inventory or not
        # (these hosts might be outside the --limit or current op limit_hosts).
        hosts = set(state.inventory.iter_active_hosts())
        if ensure_hosts:
            hosts.update(ensure_hosts)

        # Execute the command for each state inventory in a greenlet
        greenlet_to_host = {}

        for host in hosts:
            if host in current_facts:
                continue

            # Generate actual arguments by passing strings as jinja2 templates
            host_kwargs = {
                key: get_arg_value(state, host, arg)
                for key, arg in kwargs.items()
            }

            command = _make_command(fact.command, host_kwargs)
            requires_command = _make_command(fact.requires_command,
                                             host_kwargs)
            if requires_command:
                command = StringCommand(
                    # Command doesn't exist, return 0 *or* run & return fact command
                    '!',
                    'command',
                    '-v',
                    requires_command,
                    '>/dev/null',
                    '||',
                    command,
                )

            greenlet = state.fact_pool.spawn(
                host.run_shell_command,
                command,
                sudo=sudo,
                sudo_user=sudo_user,
                use_sudo_password=use_sudo_password,
                su_user=su_user,
                timeout=timeout,
                env=env,
                shell_executable=shell_executable,
                print_output=state.print_fact_output,
                print_input=state.print_fact_input,
                return_combined_output=True,
            )
            greenlet_to_host[greenlet] = host

        # Wait for all the commands to execute
        progress_prefix = 'fact: {0} {1}'.format(name, get_kwargs_str(kwargs))

        with progress_spinner(
                greenlet_to_host.values(),
                prefix_message=progress_prefix,
        ) as progress:
            for greenlet in gevent.iwait(greenlet_to_host.keys()):
                host = greenlet_to_host[greenlet]
                progress(host)

        hostname_facts = {}
        failed_hosts = set()

        # Collect the facts and any failures
        for greenlet, host in six.iteritems(greenlet_to_host):
            status = False
            stdout = []

            try:
                status, combined_output_lines = greenlet.get()
            except (timeout_error, socket_error, SSHException) as e:
                failed_hosts.add(host)
                log_host_command_error(
                    host,
                    e,
                    timeout=timeout,
                )

            stdout, stderr = split_combined_output(combined_output_lines)

            data = fact.default()

            if status:
                if stdout:
                    data = fact.process(stdout)

            elif stderr:
                first_line = stderr[0]
                if (sudo_user and state.will_add_user(sudo_user)
                        and re.match(SUDO_REGEX, first_line)):
                    status = True
                if (su_user and state.will_add_user(su_user) and any(
                        re.match(regex, first_line) for regex in SU_REGEXES)):
                    status = True

            if not status:
                failed_hosts.add(host)

                if not state.print_fact_output:
                    print_host_combined_output(host, combined_output_lines)

                log_error_or_warning(
                    host,
                    ignore_errors,
                    description=('could not load fact: {0} {1}').format(
                        name, get_kwargs_str(kwargs)))

            hostname_facts[host] = data

        log = 'Loaded fact {0} {1}'.format(click.style(name, bold=True),
                                           get_kwargs_str(kwargs))
        if state.print_fact_info:
            logger.info(log)
        else:
            logger.debug(log)

        # Check we've not failed
        if not ignore_errors and apply_failed_hosts:
            state.fail_hosts(failed_hosts)

        # Assign the facts
        state.facts.setdefault(fact_hash, {}).update(hostname_facts)

    return state.facts[fact_hash]