Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00%
0 / 1
25.00%
1 / 4
CRAP
58.46%
38 / 65
ExecutableCheck
0.00%
0 / 1
25.00%
1 / 4
19.67
58.46%
38 / 65
 getDefaultGroupName()
100.00%
1 / 1
1
100.00%
1 / 1
 getDefaultSettingGroupName()
0.00%
0 / 1
2.15
66.67%
2 / 3
 run()
0.00%
0 / 1
3.06
81.40%
35 / 43
 validExecutable($value)
0.00%
0 / 1
30
0.00%
0 / 18
<?php
namespace Environaut\Checks;
use Environaut\Checks\Check;
/**
 * Check to get absolute paths to executable files that
 * may be used within an application. As even paths to
 * simple tools like "ls", "find" or "grep" may not be
 * accessible in e.g. crontabs due to non-configured
 * default PATH environment variables they can be searched
 * and configured prior running the application (and thus
 * always use the validated absolute executable paths).
 *
 * Supported parameters are:
 * - "command": name of executable/command to check
 * - "setting": name of setting to use for absolute path storage (for export)
 * - "setting_group": name of settings group to use (for export; defaults to "config")
 * - "question": question to ask the user (defaults to "Path to the executable [command]")
 * - "default": default command or executable path to try (defaults to "/usr/bin/[command]")
 * - "validator": validator callable to use (defaults to one, that uses "which" to find the path of the command)
 * - "max_attempts": maximum number of attempts a user is allowed to have for correct input of a path
 */
class ExecutableCheck extends Check
{
    /**
     * Default group of settings that this check stores in the result.
     */
    const DEFAULT_SETTING_GROUP_NAME = 'config';
    /**
     * Default group name used in messages of the report.
     * By default also used as default setting group name if not customized.
     */
    const DEFAULT_CUSTOM_GROUP_NAME = 'Executables';
    /**
     * Returns the default group name this check uses when none is specified.
     *
     * @return string default group name of the check
     */
    public function getDefaultGroupName()
    {
        return self::DEFAULT_CUSTOM_GROUP_NAME;
    }
    /**
     * Returns the default group name this check uses for settings when none is specified.
     *
     * @return string default group name of settings of this check
     */
    public function getDefaultSettingGroupName()
    {
        if ($this->group !== self::DEFAULT_CUSTOM_GROUP_NAME) {
            return $this->group;
        }
        return self::DEFAULT_SETTING_GROUP_NAME;
    }
    /**
     * Ask user for an absolute path to the given executable or try to determine it
     * by itself using the default value (on confirmation).
     *
     * @return boolean true
     *
     * @throws \InvalidArgumentException on setup errors like "choices" can't be interpreted or parameters being wrong
     * @throws \RuntimeException if there is no data to read in the input stream
     * @throws \Exception when the maximum number of attempts has been reached and no valid response has been given
     */
    public function run()
    {
        $output = $this->getOutputStream();
        $dialog = $this->getDialogHelper();
        $command = $this->parameters->get('command', $this->getName());
        $setting = $this->parameters->get('setting', 'cmd.' . $command);
        $setting_group = $this->parameters->get('setting_group');
        $default = $this->parameters->get('default', '/usr/bin/' . $command);
        $choices = $this->parameters->get(
            'choices',
            array(
                '/bin/',
                '/usr/bin/',
                '/usr/sbin/',
                '/usr/local/bin/',
                '/usr/local/sbin/',
            )
        );
        $validator = $this->parameters->get('validator', array($this, 'validExecutable'));
        $max_attempts = $this->parameters->get('max_attempts', false);
        if ($this->cache->has($setting, $setting_group)) {
            $cached_setting = $this->cache->get($setting, $setting_group);
            $this->addInfo(
                "Setting [" . $cached_setting->getGroup() . "][$setting] already configured. Using " .
                'value: ' . var_export($cached_setting->getValue(), true)
            );
            $this->result->addSetting($cached_setting);
            return true;
        }
        $question = '<question>' . $this->parameters->get(
            'question',
            'Path to the executable "' . $command . '"'
        );
        // add default value to question if specified
        if (null !== $default) {
            $question .= "</question> (Default: which $default)";
        } else {
            $question .=  '</question>';
        }
        $question .= ': ';
        // ask for path to executable with validation and autocomplete of common executable directories like /usr/bin
        $absolute_executable_path = $dialog->askAndValidate(
            $output,
            $question,
            $validator,
            $max_attempts,
            $default,
            $choices
        );
        $this->addCachableSetting($setting, $absolute_executable_path, $setting_group);
        $this->addInfo('Got path to executable "' . $command . '": ' . $absolute_executable_path);
        return true;
    }
    /**
     * Checks whether the given input value leads to an executable that
     * matches the configured version_mask parameter.
     *
     * @param string $value path to an executable
     *
     * @return string absolute path to executable
     *
     * @throws \InvalidArgumentException in case of non-existing executable at the given path or version mismatch
     */
    public function validExecutable($value)
    {
        $val = trim($value);
        if (empty($val)) {
            throw new \InvalidArgumentException(
                'Not a valid executable path. Please specify a command (like "ls") or a path (like "/usr/bin/ls").'
            );
        }
        $executable = trim(shell_exec('which ' . $val));
        if (!$executable) {
            throw new \InvalidArgumentException('Could not find executable: ' . $val);
        }
        $command = $this->parameters->get('command', $this->getName());
        $cli_option = $this->parameters->get('version_parameter', '--version');
        $version_mask = $this->parameters->get('version_mask', '/Version/');
        if ($version_mask) {
            $version_info_raw = trim(shell_exec('cat /dev/null | ' . $executable . ' ' . $cli_option . ' 2>&1'));
            if (!preg_match($version_mask, $version_info_raw, $matches, PREG_OFFSET_CAPTURE)) {
                throw new \InvalidArgumentException(
                    'Could not get version information for "' . $command . '" using "' . $executable . '".'
                );
            }
        }
        return $executable;
    }
}