mirror of
https://github.com/Icinga/icingaweb2-module-director.git
synced 2025-08-16 07:18:09 +02:00
parent
1786d08321
commit
7fc0da6e21
399
library/Director/Core/LegacyDeploymentApi.php
Normal file
399
library/Director/Core/LegacyDeploymentApi.php
Normal file
@ -0,0 +1,399 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Core;
|
||||
|
||||
use Exception;
|
||||
use Icinga\Exception\IcingaException;
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
|
||||
use Icinga\Module\Director\Objects\DirectorDeploymentLog;
|
||||
|
||||
/**
|
||||
* Legacy DeploymentApi for Icinga 1.x configuration deployment
|
||||
*
|
||||
* @package Icinga\Module\Director\Core
|
||||
*/
|
||||
class LegacyDeploymentApi implements DeploymentApiInterface
|
||||
{
|
||||
protected $db;
|
||||
protected $deploymentPath;
|
||||
|
||||
public function __construct(Db $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
$this->deploymentPath = $this->db->settings()->deployment_path_v1;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: merge in common class
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function collectLogFiles(Db $db)
|
||||
{
|
||||
$existing = $this->listModuleStages('director');
|
||||
|
||||
foreach ($db->getUncollectedDeployments() as $deployment) {
|
||||
$stage = $deployment->get('stage_name');
|
||||
if (! in_array($stage, $existing)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$availableFiles = $this->listStageFiles($stage);
|
||||
} catch (Exception $e) {
|
||||
// Could not collect stage files. Doesn't matter, let's try next time
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array('startup.log', $availableFiles)
|
||||
&& in_array('status', $availableFiles)
|
||||
) {
|
||||
$status = $this->getStagedFile($stage, 'status');
|
||||
$status = trim($status);
|
||||
if ($status === '0') {
|
||||
$deployment->set('startup_succeeded', 'y');
|
||||
} else {
|
||||
$deployment->set('startup_succeeded', 'n');
|
||||
}
|
||||
$deployment->set('startup_log', $this->shortenStartupLog(
|
||||
$this->getStagedFile($stage, 'startup.log')
|
||||
));
|
||||
} else {
|
||||
// Stage seems to be incomplete, let's try again next time
|
||||
continue;
|
||||
}
|
||||
$deployment->set('stage_collected', 'y');
|
||||
|
||||
$deployment->store();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: merge in common class
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function wipeInactiveStages(Db $db)
|
||||
{
|
||||
$uncollected = $db->getUncollectedDeployments();
|
||||
$moduleName = 'director';
|
||||
$currentStage = $this->getActiveStageName();
|
||||
|
||||
foreach ($this->listModuleStages($moduleName) as $stage) {
|
||||
if (array_key_exists($stage, $uncollected)) {
|
||||
continue;
|
||||
}
|
||||
elseif ($stage === $currentStage) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
$this->deleteStage($moduleName, $stage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function getActiveStageName()
|
||||
{
|
||||
$this->assertDeploymentPath();
|
||||
|
||||
$path = $this->deploymentPath . DIRECTORY_SEPARATOR . 'active';
|
||||
|
||||
if (file_exists($path)) {
|
||||
if (is_link($path)) {
|
||||
$linkTarget = readlink($path);
|
||||
$linkTargetDir = dirname($linkTarget);
|
||||
$linkTargetName = basename($linkTarget);
|
||||
|
||||
if ($linkTargetDir === $this->deploymentPath || $linkTargetDir === '.') {
|
||||
return $linkTargetName;
|
||||
} else {
|
||||
throw new IcingaException(
|
||||
'Active stage link pointing to a invalid target: %s -> %s', $path, $linkTarget
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IcingaException('Active stage is not a symlink: %s', $path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function listStageFiles($stage)
|
||||
{
|
||||
$path = $this->getStagePath($stage);
|
||||
if (! is_dir($path)) {
|
||||
throw new IcingaException('Deployment stage "%s" does not exist at: %s', $stage, $path);
|
||||
}
|
||||
return $this->listDirectoryContents($path);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function listModuleStages($moduleName)
|
||||
{
|
||||
$this->assertModuleName($moduleName);
|
||||
$this->assertDeploymentPath();
|
||||
|
||||
$dh = @opendir($this->deploymentPath);
|
||||
if ($dh === null) {
|
||||
throw new IcingaException('Can not list contents of %s', $this->deploymentPath);
|
||||
}
|
||||
|
||||
$stages = array();
|
||||
while ($file = readdir($dh)) {
|
||||
if ($file === '.' || $file === '..') {
|
||||
continue;
|
||||
}
|
||||
elseif (
|
||||
is_dir($this->deploymentPath . DIRECTORY_SEPARATOR . $file)
|
||||
&& substr($file, 0, 9) === 'director-'
|
||||
) {
|
||||
$stages[] = $file;
|
||||
}
|
||||
}
|
||||
return $stages;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function getStagedFile($stage, $file)
|
||||
{
|
||||
$path = $this->getStagePath($stage);
|
||||
|
||||
$filePath = $path . DIRECTORY_SEPARATOR . $file;
|
||||
|
||||
if (! file_exists($filePath)) {
|
||||
throw new IcingaException('Could not find file %s', $filePath);
|
||||
}
|
||||
else {
|
||||
return file_get_contents($filePath);
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function deleteStage($moduleName, $stageName)
|
||||
{
|
||||
$this->assertModuleName($moduleName);
|
||||
$this->assertDeploymentPath();
|
||||
|
||||
$path = $this->getStagePath($stageName);
|
||||
|
||||
static::rrmdir($path);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
public function dumpConfig(IcingaConfig $config, Db $db, $moduleName = 'director')
|
||||
{
|
||||
$this->assertModuleName($moduleName);
|
||||
$this->assertDeploymentPath();
|
||||
|
||||
$start = microtime(true);
|
||||
$deployment = DirectorDeploymentLog::create(array(
|
||||
// 'config_id' => $config->id,
|
||||
// 'peer_identity' => $endpoint->object_name,
|
||||
'peer_identity' => $this->deploymentPath,
|
||||
'start_time' => date('Y-m-d H:i:s'),
|
||||
'config_checksum' => $config->getChecksum(),
|
||||
'last_activity_checksum' => $config->getLastActivityChecksum()
|
||||
// 'triggered_by' => Util::getUsername(),
|
||||
// 'username' => Util::getUsername(),
|
||||
// 'module_name' => $moduleName,
|
||||
));
|
||||
|
||||
$stage_name = 'director-' .date('Ymd-His');
|
||||
$deployment->set('stage_name', $stage_name);
|
||||
|
||||
try {
|
||||
$succeeded = $this->deployStage($stage_name, $config->getFileContents());
|
||||
# TODO: call deployment tool
|
||||
} catch (Exception $e) {
|
||||
$deployment->set('dump_succeeded', 'n');
|
||||
$deployment->store($db);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$duration = (int) ((microtime(true) - $start) * 1000);
|
||||
$deployment->set('duration_dump', $duration);
|
||||
|
||||
$deployment->set('dump_succeeded', $succeeded === true ? 'y' : 'n');
|
||||
|
||||
$deployment->store($db);
|
||||
return $succeeded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy a new stage, and write all files to it
|
||||
*
|
||||
* @param string $stage Name of the stage
|
||||
* @param array $files Array of files, $fileName => $content
|
||||
*
|
||||
* @return bool Success status
|
||||
*
|
||||
* @throws IcingaException When something could not be accessed
|
||||
*/
|
||||
protected function deployStage($stage, $files)
|
||||
{
|
||||
$path = $this->deploymentPath . DIRECTORY_SEPARATOR . $stage;
|
||||
|
||||
if (file_exists($path)) {
|
||||
throw new IcingaException('Stage "%s" does already exist at: ', $stage, $path);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
mkdir($path);
|
||||
} catch (Exception $e) {
|
||||
throw new IcingaException('Could not create stage "%s" at: %s - %s', $stage, $path, $e->getMessage());
|
||||
}
|
||||
|
||||
foreach ($files as $file => $content) {
|
||||
$fullPath = $path . DIRECTORY_SEPARATOR . $file;
|
||||
$relativeDir = dirname($file);
|
||||
if ($relativeDir !== '') {
|
||||
$fullDir = $path . DIRECTORY_SEPARATOR . $relativeDir;
|
||||
if (! file_exists($fullDir)) {
|
||||
if (! @mkdir($fullDir, 0755, true)) {
|
||||
throw new IcingaException('Could not create directory %s', $fullDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fh = @fopen($fullPath, 'w');
|
||||
if ($fh === null) {
|
||||
throw new IcingaException('Could not open file "%s" for writing.', $fullPath);
|
||||
}
|
||||
fwrite($fh, $content);
|
||||
fclose($fh);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively dump directory contents, with relative path
|
||||
*
|
||||
* @param string $path Absolute path to read from
|
||||
* @param int $depth Internal counter
|
||||
*
|
||||
* @return string[]
|
||||
*
|
||||
* @throws IcingaException When directory could not be opened
|
||||
*/
|
||||
protected function listDirectoryContents($path, $depth=0)
|
||||
{
|
||||
$dh = @opendir($path);
|
||||
if ($dh === null) {
|
||||
throw new IcingaException('Can not list contents of %s', $path);
|
||||
}
|
||||
|
||||
$files = array();
|
||||
while ($file = readdir($dh)) {
|
||||
$fullPath = $path . DIRECTORY_SEPARATOR . $file;
|
||||
if ($file === '.' || $file === '..') {
|
||||
continue;
|
||||
}
|
||||
elseif (is_dir($fullPath)) {
|
||||
$subdirFiles = $this->listDirectoryContents($fullPath, $depth + 1);
|
||||
foreach ($subdirFiles as $subFile) {
|
||||
$files[] = $file . DIRECTORY_SEPARATOR . $subFile;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$files[] = $file;
|
||||
}
|
||||
}
|
||||
if ($depth === 0) {
|
||||
sort($files);
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that only the director module is interacted with
|
||||
*
|
||||
* @param string $moduleName
|
||||
* @throws IcingaException When another module is requested
|
||||
*/
|
||||
protected function assertModuleName($moduleName)
|
||||
{
|
||||
if ($moduleName !== 'director') {
|
||||
throw new IcingaException('Does not supported different modules!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert the deployment path to be configured, existing, and writeable
|
||||
*
|
||||
* @throws IcingaException
|
||||
*/
|
||||
protected function assertDeploymentPath()
|
||||
{
|
||||
if ($this->deploymentPath === null) {
|
||||
throw new IcingaException('Deployment path is not configured for legacy config!');
|
||||
}
|
||||
elseif (! is_dir($this->deploymentPath)) {
|
||||
throw new IcingaException('Deployment path is not a directory: %s', $this->deploymentPath);
|
||||
}
|
||||
elseif (! is_writeable($this->deploymentPath)) {
|
||||
throw new IcingaException('Deployment path is not a writeable: %s', $this->deploymentPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: avoid code duplication: copied from CoreApi
|
||||
*
|
||||
* @param string $log The log contents to shorten
|
||||
* @return string
|
||||
*/
|
||||
protected function shortenStartupLog($log)
|
||||
{
|
||||
$logLen = strlen($log);
|
||||
if ($logLen < 1024 * 60) {
|
||||
return $log;
|
||||
}
|
||||
|
||||
$part = substr($log, 0, 1024 * 20);
|
||||
$parts = explode("\n", $part);
|
||||
array_pop($parts);
|
||||
$begin = implode("\n", $parts) . "\n\n";
|
||||
|
||||
$part = substr($log, -1024 * 20);
|
||||
$parts = explode("\n", $part);
|
||||
array_shift($parts);
|
||||
$end = "\n\n" . implode("\n", $parts);
|
||||
|
||||
return $begin . sprintf(
|
||||
'[..] %d bytes removed by Director [..]',
|
||||
$logLen - (strlen($begin) + strlen($end))
|
||||
) . $end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full path of a stage
|
||||
*
|
||||
* @param string $stage Name of the stage
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStagePath($stage)
|
||||
{
|
||||
$this->assertDeploymentPath();
|
||||
return $this->deploymentPath . DIRECTORY_SEPARATOR . $stage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @from https://php.net/manual/de/function.rmdir.php#108113
|
||||
* @param $dir
|
||||
*/
|
||||
protected static function rrmdir($dir) {
|
||||
foreach(glob($dir . '/*') as $file) {
|
||||
if(is_dir($file))
|
||||
static::rrmdir($file);
|
||||
else
|
||||
unlink($file);
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
}
|
@ -2,7 +2,9 @@
|
||||
|
||||
namespace Icinga\Module\Director\Objects;
|
||||
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Module\Director\Core\CoreApi;
|
||||
use Icinga\Module\Director\Core\LegacyDeploymentApi;
|
||||
use Icinga\Module\Director\Core\RestApiClient;
|
||||
|
||||
class IcingaEndpoint extends IcingaObject
|
||||
@ -42,23 +44,36 @@ class IcingaEndpoint extends IcingaObject
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CoreApi
|
||||
* Return a core API, depending on the configuration format
|
||||
*
|
||||
* @return CoreApi|LegacyDeploymentApi
|
||||
*
|
||||
* @throws ProgrammingError When configured config_format is unknown
|
||||
*/
|
||||
public function api()
|
||||
{
|
||||
$client = new RestApiClient(
|
||||
$this->getResolvedProperty('host', $this->getObjectName()),
|
||||
$this->getResolvedProperty('port')
|
||||
);
|
||||
$format = $this->connection->settings()->config_format;
|
||||
if ($format === 'v2') {
|
||||
$client = new RestApiClient(
|
||||
$this->getResolvedProperty('host', $this->getObjectName()),
|
||||
$this->getResolvedProperty('port')
|
||||
);
|
||||
|
||||
$user = $this->getApiUser();
|
||||
$client->setCredentials(
|
||||
$user = $this->getApiUser();
|
||||
$client->setCredentials(
|
||||
// TODO: $user->client_dn,
|
||||
$user->object_name,
|
||||
$user->password
|
||||
);
|
||||
$user->object_name,
|
||||
$user->password
|
||||
);
|
||||
|
||||
return new CoreApi($client);
|
||||
return new CoreApi($client);
|
||||
}
|
||||
elseif ($format === 'v1') {
|
||||
return new LegacyDeploymentApi($this->connection);
|
||||
}
|
||||
else {
|
||||
throw new ProgrammingError('Unsupported config format: %s', $format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user