View source
<?php
namespace Drupal\monitoring\Plugin\monitoring\SensorPlugin;
use Drupal\Core\Form\FormStateInterface;
use Drupal\monitoring\Result\SensorResultInterface;
use Drupal\monitoring\SensorPlugin\SensorPluginBase;
use Drupal\monitoring\SensorPlugin\ExtendedInfoSensorPluginInterface;
class GitDirtyTreeSensorPlugin extends SensorPluginBase implements ExtendedInfoSensorPluginInterface {
protected $status;
protected $distance;
protected $actualBranch;
protected $submodules;
public function runSensor(SensorResultInterface $result) {
if (exec('echo "enabled"') != 'enabled') {
$result
->addStatusMessage('The function exec() is disabled. You need to enable it.');
$result
->setStatus(SensorResultInterface::STATUS_CRITICAL);
return;
}
$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) {
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);
}
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);
}
}
protected function getShortFileList($input, $max_files = 2, $max_length = 50) {
$output = array();
foreach (array_slice($input, 0, $max_files) as $line) {
$parts = explode(' ', $line, 2);
if (strlen($parts[1]) > $max_length) {
$output[] = $parts[0] . ' …' . substr($parts[1], -$max_length);
}
else {
$output[] = $line;
}
}
return implode(', ', $output);
}
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;
}
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";
}
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;
}
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(
'invisible' => array(
':input[name="settings[check_branch]"]' => array(
'checked' => FALSE,
),
),
),
);
return $form;
}
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;
}
}