parent
c709c00fbd
commit
93222099ad
|
@ -4,8 +4,10 @@ namespace Icinga\Module\Director\Clicommands;
|
|||
|
||||
use Icinga\Date\DateFormatter;
|
||||
use Icinga\Module\Director\Cli\Command;
|
||||
use Icinga\Module\Director\Core\Json;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\Basket;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshot;
|
||||
use Icinga\Module\Director\DirectorObject\ObjectPurgeHelper;
|
||||
|
||||
/**
|
||||
* Export Director Config Objects
|
||||
|
@ -79,14 +81,43 @@ class BasketCommand extends Command
|
|||
* icingacli director basket restore < basket-dump.json
|
||||
*
|
||||
* OPTIONS
|
||||
* --purge <ObjectType>[,<ObjectType] Purge objects of the
|
||||
* Given types. WARNING: this removes ALL objects that are
|
||||
* not shipped with the given basket
|
||||
* --force Purge refuses to purge Objects in case there are
|
||||
* no Objects of a given ObjectType in the provided basket
|
||||
* unless forced to do so
|
||||
*/
|
||||
public function restoreAction()
|
||||
{
|
||||
if ($purge = $this->params->get('purge')) {
|
||||
$purge = explode(',', $purge);
|
||||
ObjectPurgeHelper::assertObjectTypesAreEligibleForPurge($purge);
|
||||
}
|
||||
$json = file_get_contents('php://stdin');
|
||||
BasketSnapshot::restoreJson($json, $this->db());
|
||||
if ($purge) {
|
||||
$this->purgeObjectTypes(Json::decode($json), $purge, $this->params->get('force'));
|
||||
}
|
||||
echo "Objects from Basket Snapshot have been restored\n";
|
||||
}
|
||||
|
||||
protected function purgeObjectTypes($objects, array $types, $force = false)
|
||||
{
|
||||
$helper = new ObjectPurgeHelper($this->db());
|
||||
if ($force) {
|
||||
$helper->force();
|
||||
}
|
||||
foreach ($types as $type) {
|
||||
list($className, $typeFilter) = BasketSnapshot::getClassAndObjectTypeForType($type);
|
||||
$helper->purge(
|
||||
isset($objects->$type) ? (array) $objects->$type : [],
|
||||
$className,
|
||||
$typeFilter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
protected function requireBasket()
|
||||
|
|
|
@ -14,6 +14,9 @@ next (will be 1.9.0)
|
|||
### Import and Sync
|
||||
* FEATURE: introduce 'disable' as your purge action on Sync (#2285)
|
||||
|
||||
### Configuration Baskets
|
||||
* FEATURE: it's now possible to purge objects of specific types (#2201)
|
||||
|
||||
### Permissions and Restrictions
|
||||
* FEATURE: allow using monitoring module permissions (#2304)
|
||||
|
||||
|
|
|
@ -111,9 +111,18 @@ class BasketSnapshot extends DbObject
|
|||
|
||||
if (is_array(self::$typeClasses[$type])) {
|
||||
return self::$typeClasses[$type][0];
|
||||
} else {
|
||||
}
|
||||
|
||||
return self::$typeClasses[$type];
|
||||
}
|
||||
|
||||
public static function getClassAndObjectTypeForType($type)
|
||||
{
|
||||
if (is_array(self::$typeClasses[$type])) {
|
||||
return self::$typeClasses[$type];
|
||||
}
|
||||
|
||||
return [self::$typeClasses[$type], null];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -259,7 +268,7 @@ class BasketSnapshot extends DbObject
|
|||
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
|
||||
* @throws \Zend_Db_Adapter_Exception
|
||||
*/
|
||||
protected function restoreType(
|
||||
public function restoreType(
|
||||
&$all,
|
||||
$typeName,
|
||||
BasketSnapshotFieldResolver $fieldResolver,
|
||||
|
@ -357,9 +366,9 @@ class BasketSnapshot extends DbObject
|
|||
{
|
||||
if ($this->hasBeenLoadedFromDb()) {
|
||||
return $this->getContent()->get('summary');
|
||||
} else {
|
||||
return Json::encode($this->getSummary(), JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
return Json::encode($this->getSummary(), JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -370,14 +379,14 @@ class BasketSnapshot extends DbObject
|
|||
{
|
||||
if ($this->hasBeenLoadedFromDb()) {
|
||||
return Json::decode($this->getContent()->get('summary'));
|
||||
} else {
|
||||
$summary = [];
|
||||
foreach (array_keys($this->objects) as $key) {
|
||||
$summary[$key] = count($this->objects[$key]);
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
$summary = [];
|
||||
foreach (array_keys($this->objects) as $key) {
|
||||
$summary[$key] = count($this->objects[$key]);
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -388,19 +397,15 @@ class BasketSnapshot extends DbObject
|
|||
{
|
||||
if ($this->hasBeenLoadedFromDb()) {
|
||||
return $this->getContent()->get('content');
|
||||
} else {
|
||||
return Json::encode($this->objects, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
return Json::encode($this->objects, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
protected function addAll($typeName)
|
||||
{
|
||||
$class = static::getClassForType($typeName);
|
||||
if (is_array(self::$typeClasses[$typeName])) {
|
||||
$filter = self::$typeClasses[$typeName][1];
|
||||
} else {
|
||||
$filter = null;
|
||||
}
|
||||
list($class, $filter) = static::getClassAndObjectTypeForType($typeName);
|
||||
|
||||
/** @var IcingaObject $dummy */
|
||||
$dummy = $class::create();
|
||||
/** @var ExportInterface $object */
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\DirectorObject;
|
||||
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\Objects\IcingaObject;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class ObjectPurgeHelper
|
||||
{
|
||||
protected $db;
|
||||
|
||||
protected $force = false;
|
||||
|
||||
public function __construct(Db $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function force($force = true)
|
||||
{
|
||||
$this->force = $force;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function purge(array $keep, $class, $objectType = null)
|
||||
{
|
||||
if (empty($keep) && ! $this->force) {
|
||||
throw new InvalidArgumentException('I will NOT purge all object unless being forced to do so');
|
||||
}
|
||||
$db = $this->db->getDbAdapter();
|
||||
/** @var IcingaObject $class cheating, it's a class name, not an object */
|
||||
$dummy = $class::create();
|
||||
assert($dummy instanceof IcingaObject);
|
||||
$keyCols = (array) $dummy->getKeyName();
|
||||
if ($objectType !== null) {
|
||||
$keyCols[] = 'object_type';
|
||||
}
|
||||
|
||||
$keepKeys = [];
|
||||
foreach ($keep as $object) {
|
||||
if ($object instanceof \stdClass) {
|
||||
$properties = (array) $object;
|
||||
// TODO: this is object-specific and to be found in the ::import() function!
|
||||
unset($properties['fields']);
|
||||
$object = $class::fromPlainObject($properties);
|
||||
} elseif (\get_class($object) !== $class) {
|
||||
throw new InvalidArgumentException(
|
||||
'Can keep only matching objects, expected "%s", got "%s',
|
||||
$class,
|
||||
\get_class($keep)
|
||||
);
|
||||
}
|
||||
$key = [];
|
||||
foreach ($keyCols as $col) {
|
||||
$key[$col] = $object->get($col);
|
||||
}
|
||||
$keepKeys[$this->makeRowKey($key)] = true;
|
||||
}
|
||||
|
||||
$query = $db->select()->from(['o' => $dummy->getTableName()], $keyCols);
|
||||
if ($objectType !== null) {
|
||||
$query->where('object_type = ?', $objectType);
|
||||
}
|
||||
$allExisting = [];
|
||||
foreach ($db->fetchAll($query) as $row) {
|
||||
$allExisting[$this->makeRowKey($row)] = $row;
|
||||
}
|
||||
$remove = [];
|
||||
foreach ($allExisting as $key => $keyProperties) {
|
||||
if (! isset($keepKeys[$key])) {
|
||||
$remove[] = $keyProperties;
|
||||
}
|
||||
}
|
||||
$db->beginTransaction();
|
||||
foreach ($remove as $keyProperties) {
|
||||
$keyColumn = $class::getKeyColumnName();
|
||||
if (is_array($keyColumn)) {
|
||||
$object = $class::load((array) $keyProperties, $this->db);
|
||||
} else {
|
||||
$object = $class::load($keyProperties->$keyColumn, $this->db);
|
||||
}
|
||||
// $object->markForRemoval();
|
||||
$object->delete();
|
||||
}
|
||||
$db->commit();
|
||||
// DirectorActivityLog::logRemoval($this, $this->connection);
|
||||
}
|
||||
|
||||
public static function listObjectTypesAvailableForPurge()
|
||||
{
|
||||
return [
|
||||
'Basket',
|
||||
'Command',
|
||||
'CommandTemplate',
|
||||
'Dependency',
|
||||
'DirectorJob',
|
||||
'ExternalCommand',
|
||||
'HostGroup',
|
||||
'HostTemplate',
|
||||
'IcingaTemplateChoiceHost',
|
||||
'IcingaTemplateChoiceService',
|
||||
'ImportSource',
|
||||
'Notification',
|
||||
'NotificationTemplate',
|
||||
'ServiceGroup',
|
||||
'ServiceSet',
|
||||
'ServiceTemplate',
|
||||
'SyncRule',
|
||||
'TimePeriod',
|
||||
];
|
||||
}
|
||||
|
||||
public static function objectTypeIsEligibleForPurge($type)
|
||||
{
|
||||
return in_array($type, static::listObjectTypesAvailableForPurge(), true);
|
||||
}
|
||||
|
||||
public static function assertObjectTypesAreEligibleForPurge($types)
|
||||
{
|
||||
$invalid = [];
|
||||
foreach ($types as $type) {
|
||||
if (! static::objectTypeIsEligibleForPurge($type)) {
|
||||
$invalid[] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($invalid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (count($invalid) === 1) {
|
||||
$message = sprintf('"%s" is not eligible for purge', $invalid[0]);
|
||||
} else {
|
||||
$message = 'The following types are not eligible for purge: '
|
||||
. implode(', ', $invalid);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(
|
||||
"$message. Valid types: "
|
||||
. implode(', ', static::listObjectTypesAvailableForPurge())
|
||||
);
|
||||
}
|
||||
|
||||
protected function makeRowKey($row)
|
||||
{
|
||||
$row = (array) $row;
|
||||
ksort($row);
|
||||
return json_encode($row, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue