class GitDirtyTreeSensorPlugin in Monitoring 8
Monitors the git repository for dirty files.
@SensorPlugin( id = "monitoring_git_dirty_tree", label = @Translation("Git Dirty Tree"), description = @Translation("Monitors the git repository for dirty files."), addable = FALSE )
Tracks both changed and untracked files. Supports git submodules and alerts if them are not initialized. Also checks branches.
Limitations:
- Does not check tag.
Hierarchy
- class \Drupal\monitoring\SensorPlugin\SensorPluginBase implements SensorPluginInterface uses MessengerTrait, StringTranslationTrait
- class \Drupal\monitoring\Plugin\monitoring\SensorPlugin\GitDirtyTreeSensorPlugin implements ExtendedInfoSensorPluginInterface
Expanded class hierarchy of GitDirtyTreeSensorPlugin
File
- src/
Plugin/ monitoring/ SensorPlugin/ GitDirtyTreeSensorPlugin.php, line 31 - Contains \Drupal\monitoring\Plugin\monitoring\SensorPlugin\GitDirtyTreeSensorPlugin.
Namespace
Drupal\monitoring\Plugin\monitoring\SensorPluginView source
class GitDirtyTreeSensorPlugin extends SensorPluginBase implements ExtendedInfoSensorPluginInterface {
/**
* The status_cmd command output.
*
* @var array
*/
protected $status;
/**
* The ahead_cmd command output.
*
* @var array
*/
protected $distance;
/**
* The actual_branch_cmd command output.
*
* @var array
*/
protected $actualBranch;
/**
* The submodules_cmd command output.
*
* @var array
*/
protected $submodules;
/**
* {@inheritdoc}
*/
public function runSensor(SensorResultInterface $result) {
// For security reasons, some hostings disable exec() related functions.
// Since they fail silently, we challenge exec() and check if the result
// matches the expectations.
if (exec('echo "enabled"') != 'enabled') {
$result
->addStatusMessage('The function exec() is disabled. You need to enable it.');
$result
->setStatus(SensorResultInterface::STATUS_CRITICAL);
return;
}
// Run commands.
$branch_control = $this->sensorConfig
->getSetting('check_branch');
if ($branch_control) {
$branch = $this
->runSensorCommand($result, 'actual_branch_cmd');
$this->actualBranch = $branch[0];
}
$this->status = $this
->runSensorCommand($result, 'status_cmd');
$this->distance = $this
->runSensorCommand($result, 'ahead_cmd');
$this->submodules = $this
->runSensorCommand($result, 'submodules_cmd');
$wrong_submodules = [];
foreach ($this->submodules as $submodule) {
$prefix = substr($submodule, 0, 1);
if ($prefix == '-' || $prefix == '+') {
$wrong_submodules[] = $submodule;
}
}
$is_expected_branch = $this->actualBranch === $this->sensorConfig
->getSetting('expected_branch');
if ($this->status || $this->distance || !$is_expected_branch && $branch_control || $wrong_submodules) {
// Critical situations.
if ($this->status) {
$result
->addStatusMessage('@num_files files in unexpected state: @files', array(
'@num_files' => count($this->status),
'@files' => $this
->getShortFileList($this->status),
));
$result
->setStatus(SensorResultInterface::STATUS_CRITICAL);
}
if ($wrong_submodules) {
$result
->addStatusMessage('@num_submodules submodules in unexpected state: @submodules', array(
'@num_submodules' => count($wrong_submodules),
'@submodules' => $this
->getShortFileList($wrong_submodules),
));
$result
->setStatus(SensorResultInterface::STATUS_CRITICAL);
}
// Warnings.
if ($this->distance) {
$result
->addStatusMessage('Branch is @distance ahead of origin', array(
'@distance' => count($this->distance),
));
if ($result
->getStatus() != SensorResultInterface::STATUS_CRITICAL) {
$result
->setStatus(SensorResultInterface::STATUS_WARNING);
}
}
if (!$is_expected_branch && $branch_control) {
$result
->addStatusMessage('Active branch @actual_branch, expected @expected_branch', array(
'@actual_branch' => $this->actualBranch,
'@expected_branch' => $this->sensorConfig
->getSetting('expected_branch'),
));
if ($result
->getStatus() != SensorResultInterface::STATUS_CRITICAL) {
$result
->setStatus(SensorResultInterface::STATUS_WARNING);
}
}
}
else {
$result
->addStatusMessage('Git repository clean');
$result
->setStatus(SensorResultInterface::STATUS_OK);
}
}
/**
* Returns a shortened file list for the status message.
*
* @param string $input
* Result from running the git command.
* @param int $max_files
* Limit the number of files returned.
* @param int $max_length
* Limit the length of the path to the file.
*
* @return string
* File names from $output.
*/
protected function getShortFileList($input, $max_files = 2, $max_length = 50) {
$output = array();
// Remove unnecessary whitespace.
foreach (array_slice($input, 0, $max_files) as $line) {
// Separate type of modification and path to file.
$parts = explode(' ', $line, 2);
if (strlen($parts[1]) > $max_length) {
// Put together type of modification and path to file limited by
// $pathLength.
$output[] = $parts[0] . ' …' . substr($parts[1], -$max_length);
}
else {
// Return whole line if path is shorter then $pathLength.
$output[] = $line;
}
}
return implode(', ', $output);
}
/**
* {@inheritdoc}
*/
public function resultVerbose(SensorResultInterface $result) {
$output = [];
$branch_control = $this->sensorConfig
->getSetting('check_branch');
if ($branch_control) {
$output['check_branch'] = array(
'#type' => 'fieldset',
'#title' => t('Check branch'),
'#attributes' => array(),
);
$output['check_branch']['cmd'] = array(
'#type' => 'item',
'#title' => t('Command'),
'#markup' => $this
->buildCommand('actual_branch_cmd'),
);
$output['check_branch']['output'] = array(
'#type' => 'item',
'#title' => t('Output'),
'#markup' => $this->actualBranch,
'#description' => t('Shows the current branch.'),
'#description_display' => 'after',
);
}
$output['ahead'] = array(
'#type' => 'fieldset',
'#title' => t('Ahead'),
'#attributes' => array(),
);
$output['ahead']['cmd'] = array(
'#type' => 'item',
'#title' => t('Command'),
'#markup' => $this
->buildCommand('ahead_cmd'),
);
$output['ahead']['output'] = array(
'#type' => 'item',
'#title' => t('Output'),
'#markup' => '<pre>' . implode("\n", $this->distance) . '</pre>',
'#description' => t('Shows local commits that have not been pushed.'),
'#description_display' => 'after',
);
$output['status'] = array(
'#type' => 'fieldset',
'#title' => t('Status'),
'#attributes' => array(),
);
$output['status']['cmd'] = array(
'#type' => 'item',
'#title' => t('Command'),
'#markup' => $this
->buildCommand('status_cmd'),
);
$output['status']['output'] = array(
'#type' => 'item',
'#title' => t('Output'),
'#markup' => '<pre>' . implode("\n", $this->status) . '</pre>',
'#description' => t('Shows uncommitted, changed and deleted files.'),
'#description_display' => 'after',
);
$output['submodules'] = array(
'#type' => 'fieldset',
'#title' => t('Submodules'),
'#attributes' => array(),
);
$output['submodules']['cmd'] = array(
'#type' => 'item',
'#title' => t('Command'),
'#markup' => $this
->buildCommand('submodules_cmd'),
);
$output['submodules']['output'] = array(
'#type' => 'item',
'#title' => t('Output'),
'#markup' => '<pre>' . implode("\n", $this->submodules) . '</pre>',
'#description' => t('Run "git submodule init" to initialize missing submodules ("-" prefix) or "git submodule update" to update submodules to the correct commit ("+" prefix).'),
'#description_display' => 'after',
);
return $output;
}
/**
* Build the command to be passed into shell_exec().
*
* @param string $cmd
* Command we want to run.
*
* @return string
* Shell command.
*/
protected function buildCommand($cmd) {
$repo_path = DRUPAL_ROOT . '/' . $this->sensorConfig
->getSetting('repo_path');
$cmd = $this->sensorConfig
->getSetting($cmd);
return 'cd ' . escapeshellarg($repo_path) . "\n{$cmd} 2>&1";
}
/**
* Run the command and set the status message and the status to the result.
*
* @param \Drupal\monitoring\Result\SensorResultInterface $result
* Sensor result object.
* @param string $cmd
* Command we want to run.
*
* @return array
* Output of executing the Shell command.
*/
protected function runSensorCommand(SensorResultInterface &$result, $cmd) {
$exit_code = 0;
$command = $this
->buildCommand($cmd);
exec($command, $output, $exit_code);
if ($exit_code > 0) {
$result
->addStatusMessage('Non-zero exit code @exit_code for command @command', array(
'@exit_code' => $exit_code,
'@command' => $command,
));
$result
->setStatus(SensorResultInterface::STATUS_CRITICAL);
}
return $output;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['repo_path'] = array(
'#type' => 'textfield',
'#default_value' => $this->sensorConfig
->getSetting('repo_path'),
'#title' => t('Repository path'),
'#description' => t('Path to the Git repository relative to the Drupal root directory.'),
);
$branches = $this
->runCommand('branches_cmd', t('Failing to get Git branches, Git might not be available.'));
$expected_branch = $this->sensorConfig
->getSetting('expected_branch');
if (empty($expected_branch)) {
$expected_branch = $this
->runCommand('actual_branch_cmd', t('Failing to get the actual branch, Git might not be available.'));
}
$options = array();
foreach ($branches as $branch) {
$options[$branch] = $branch;
}
$form['check_branch'] = array(
'#type' => 'checkbox',
'#default_value' => $this->sensorConfig
->getSetting('check_branch'),
'#title' => t('Branch control'),
'#description' => t('Check if the current branch is different from the selected.'),
'#disabled' => !$options,
);
$form['expected_branch'] = array(
'#type' => 'select',
'#default_value' => $expected_branch,
'#maxlength' => 255,
'#empty_option' => t('- Select -'),
'#options' => $options,
'#title' => t('Expected active branch'),
'#description' => t('The branch that is going to be checked out.'),
'#states' => array(
// Hide the branch selector when the check_branch checkbox is disabled.
'invisible' => array(
':input[name="settings[check_branch]"]' => array(
'checked' => FALSE,
),
),
),
);
return $form;
}
/**
* Run a command providing an error message.
*
* @param string $cmd
* Command we want to run.
* @param string $error
* Error message to show when failing.
*
* @return array
* Output of executing the Shell command.
*/
private function runCommand($cmd, $error) {
$exit_code = 0;
exec($this
->buildCommand($cmd), $output, $exit_code);
if ($exit_code > 0) {
$this
->messenger()
->addError($error);
}
return $output;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
GitDirtyTreeSensorPlugin:: |
protected | property | The actual_branch_cmd command output. | |
GitDirtyTreeSensorPlugin:: |
protected | property | The ahead_cmd command output. | |
GitDirtyTreeSensorPlugin:: |
protected | property | The status_cmd command output. | |
GitDirtyTreeSensorPlugin:: |
protected | property | The submodules_cmd command output. | |
GitDirtyTreeSensorPlugin:: |
protected | function | Build the command to be passed into shell_exec(). | |
GitDirtyTreeSensorPlugin:: |
public | function |
Form constructor. Overrides SensorPluginBase:: |
|
GitDirtyTreeSensorPlugin:: |
protected | function | Returns a shortened file list for the status message. | |
GitDirtyTreeSensorPlugin:: |
public | function |
Provide additional info about sensor call. Overrides ExtendedInfoSensorPluginInterface:: |
|
GitDirtyTreeSensorPlugin:: |
private | function | Run a command providing an error message. | |
GitDirtyTreeSensorPlugin:: |
public | function |
Runs the sensor, updating $sensor_result. Overrides SensorPluginInterface:: |
|
GitDirtyTreeSensorPlugin:: |
protected | function | Run the command and set the status message and the status to the result. | |
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
SensorPluginBase:: |
protected | property | Allows plugins to control if the value type can be configured. | 6 |
SensorPluginBase:: |
protected | property | The plugin implementation definition. | |
SensorPluginBase:: |
protected | property | The plugin_id. | |
SensorPluginBase:: |
protected | property | Current sensor config object. | |
SensorPluginBase:: |
protected | property | ||
SensorPluginBase:: |
public | function |
Service setter. Overrides SensorPluginInterface:: |
|
SensorPluginBase:: |
public | function |
Calculates dependencies for the configured plugin. Overrides SensorPluginInterface:: |
4 |
SensorPluginBase:: |
public static | function |
Creates an instance of the sensor with config. Overrides SensorPluginInterface:: |
7 |
SensorPluginBase:: |
public | function |
Configurable value type. Overrides SensorPluginInterface:: |
|
SensorPluginBase:: |
public | function |
Default configuration for a sensor. Overrides SensorPluginInterface:: |
8 |
SensorPluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
|
SensorPluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
SensorPluginBase:: |
public | function |
Gets sensor name (not the label). Overrides SensorPluginInterface:: |
|
SensorPluginBase:: |
public | function |
@todo: Replace with injection Overrides SensorPluginInterface:: |
|
SensorPluginBase:: |
public | function |
Determines if sensor is enabled. Overrides SensorPluginInterface:: |
|
SensorPluginBase:: |
public | function |
Form submission handler. Overrides PluginFormInterface:: |
3 |
SensorPluginBase:: |
public | function |
Form validation handler. Overrides PluginFormInterface:: |
2 |
SensorPluginBase:: |
function | Instantiates a sensor object. | 8 | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |