Add an ini writer for configuration files

Add an ini writer that respects the file structure and the comments that may be
already present in the config file. Move Application/Config.php into
Config/Config.php.

refs #4352
This commit is contained in:
Matthias Jentsch 2013-08-06 16:28:26 +02:00
parent edebbf93ab
commit 56e47fd084
20 changed files with 823 additions and 232 deletions

View File

@ -28,7 +28,7 @@ namespace Icinga\Web\Form;
use Icinga\Web\Form; use Icinga\Web\Form;
use Icinga\Web\Session; use Icinga\Web\Session;
use Icinga\Web\Notification; use Icinga\Web\Notification;
use Icinga\Application\Config; use Icinga\Config\Config;
/** /**
* Class SettingsForm * Class SettingsForm

View File

@ -1,13 +1,13 @@
# Application and Module Configuration # Application and Module Configuration
The \Icinga\Application\Config class is a general purpose service to help you find, load and save The \Icinga\Config\Config class is a general purpose service to help you find, load and save
configuration data. It is used both by the Icinga 2 Web modules and the framework itself. With configuration data. It is used both by the Icinga 2 Web modules and the framework itself. With
INI files as source it enables you to store configuration in a familiar format. Icinga 2 Web INI files as source it enables you to store configuration in a familiar format. Icinga 2 Web
defines some configuration files for its own purposes. Please note that both modules and framework defines some configuration files for its own purposes. Please note that both modules and framework
keep their main configuration in the INI file called config.ini. Here's some example code: keep their main configuration in the INI file called config.ini. Here's some example code:
<?php <?php
use \Icinga\Application\Config as IcingaConfig; use \Icinga\Config\Config as IcingaConfig;
// Retrieve the default timezone using 'Europe/Berlin' in case it is not set // Retrieve the default timezone using 'Europe/Berlin' in case it is not set
IcingaConfig::app()->global->get('defaultTimezone', 'Europe/Berlin'); IcingaConfig::app()->global->get('defaultTimezone', 'Europe/Berlin');

View File

@ -31,6 +31,7 @@ namespace Icinga\Application;
use Icinga\Application\Modules\Manager as ModuleManager; use Icinga\Application\Modules\Manager as ModuleManager;
use Icinga\Application\Platform; use Icinga\Application\Platform;
use Icinga\Exception\ProgrammingError; use Icinga\Exception\ProgrammingError;
use Icinga\Config\Config;
use Zend_Loader_Autoloader; use Zend_Loader_Autoloader;
use Icinga\Exception\ConfigurationError; use Icinga\Exception\ConfigurationError;

View File

@ -29,7 +29,7 @@
namespace Icinga\Application\Modules; namespace Icinga\Application\Modules;
use Icinga\Application\ApplicationBootstrap; use Icinga\Application\ApplicationBootstrap;
use Icinga\Application\Config; use Icinga\Config\Config;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Web\Hook; use Icinga\Web\Hook;
use Zend_Controller_Router_Route as Route; use Zend_Controller_Router_Route as Route;

View File

@ -32,7 +32,7 @@ use Icinga\User;
use Icinga\Authentication\UserBackend; use Icinga\Authentication\UserBackend;
use Icinga\Authentication\Credentials; use Icinga\Authentication\Credentials;
use Icinga\Protocol\Ldap; use Icinga\Protocol\Ldap;
use Icinga\Application\Config as IcingaConfig; use Icinga\Config\Config as IcingaConfig;
/** /**
* User authentication backend (@see Icinga\Authentication\UserBackend) for * User authentication backend (@see Icinga\Authentication\UserBackend) for

View File

@ -29,7 +29,7 @@
namespace Icinga\Authentication; namespace Icinga\Authentication;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Application\Config as IcingaConfig; use Icinga\Config\Config as IcingaConfig;
use Icinga\Exception\ConfigurationError as ConfigError; use Icinga\Exception\ConfigurationError as ConfigError;
use Icinga\User; use Icinga\User;

View File

@ -2,7 +2,7 @@
namespace Icinga; namespace Icinga;
use Icinga\Application\Config as IcingaConfig; use Icinga\Config\Config as IcingaConfig;
use Icinga\Authentication\Manager as AuthManager; use Icinga\Authentication\Manager as AuthManager;
class Backend class Backend

View File

@ -26,7 +26,7 @@
*/ */
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Application; namespace Icinga\Config;
use Zend_Config_Ini; use Zend_Config_Ini;

View File

@ -1,5 +1,4 @@
<?php <?php
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/** /**
* This file is part of Icinga 2 Web. * This file is part of Icinga 2 Web.
@ -27,285 +26,547 @@
*/ */
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Config; namespace Icinga\Config;
/** /**
* A ini file adapter that preserves comments in the existing ini file, when writing changes to it * A ini file adapter that respects the file structure and the comments of already
* existing ini files
*/ */
class PreservingIniWriter extends \Zend_Config_Writer class PreservingIniWriter extends \Zend_Config_Writer_FileAbstract
{ {
/** /**
* The file that is written to * Create a new instance of PreservingIniWriter
* *
* @var string * @param array $options The options passed to the base class
*/ */
private $filename;
public function setFilename($filename)
{
$this->filename = $filename;
}
function __construct(array $options) function __construct(array $options)
{ {
parent::__construct($options); parent::__construct($options);
} }
/** /**
* Write the config to the file * Render the Zend_Config into a config file string
*
* @return string
*/ */
public function write() public function render()
{ {
if (empty($this->filename)) { $oldconfig = new \Zend_Config_Ini($this->_filename);
throw new Exception('No filename for configuration provided'); $newconfig = $this->_config;
} $editor = new IniEditor(file_get_contents($this->_filename));
$oldConfig = parse_ini_file($this->filename); $this->diffConfigs($oldconfig,$newconfig,$editor);
$newConfig = $this->_config; return $editor->getText();
$diff = $this->createPropertyDiff($oldConfig,$newConfig);
} }
/** /**
* Create a diff between the properties of two Zend_Config objects * Create a property diff and apply the changes to the editor
* *
* @param \Zend_Config $oldConfig * Compare two Zend_Config that represent the state change of an ini file and use the
* @param \Zend_Config $newConfig * IniEditor to write the changes back to the config, while preserving the structure and
* the comments of the original file.
*
* @param Zend_Config $oldconfig The config representing the state before the change
* @param Zend_Config $newconfig The config representing the state after the change
* @param IniEditor $editor The editor that should be used to edit the old config file
* @param array $parents The parent keys that should be respected when editing the config
*/ */
private function createConfigDiff(\Zend_Config $oldConfig, \Zend_Config $newConfig) private function diffConfigs(
\Zend_Config $oldconfig,
\Zend_Config $newconfig,
IniEditor $editor,
array $parents = array())
{ {
// TODO: Find deleted sections in old foreach ($newconfig as $key => $value) {
$oldvalue = $oldconfig->get($key);
$fullKey = array_merge($parents,array($key));
if ($value instanceof \Zend_Config) {
if (empty($parents)) {
$extends = $newconfig->getExtends();
$extend = array_key_exists($key,$extends) ? $extends[$key] : null;
$editor->setSection($key,$extend);
}
if (!isset($oldvalue)) {
$this->diffConfigs(new \Zend_Config(array()),$value,$editor,$fullKey);
} else {
$this->diffConfigs($oldvalue,$value,$editor,$fullKey);
}
} else {
if (is_numeric($key)){
$editor->setArrayEl($fullKey,$value);
} else {
$editor->set($fullKey,$value);
}
}
}
foreach ($oldconfig as $key => $value) {
$fullKey = array_merge($parents,array($key));
$o = $newconfig->get($key);
if (!isset($o)) {
if ($value instanceof \Zend_Config) {
$this->diffConfigs(
$value,new \Zend_Config(array()),$editor,$fullKey
);
$editor->removeSection($key);
} else {
if (is_numeric($key)) {
$editor->delArrayEl($fullKey);
} else {
$editor->reset($fullKey);
}
} }
private function updateIniWithDiff($fileDiff)
{
$iniReader = new IniKeyPositionReader();
$editor = new FileEditor($this->filename);
foreach ($fileDiff as $key => $diff) {
} }
} }
private function unwrapKeys($parents,$diffs)
{
} }
} }
/**
* Can read information about the position of ini-file keys and values
* from
*/
class IniKeyPositionReader
{
public function getKeyStart(array $parents,String $key)
{
// return line
}
public function getKeyContainerFirstEmpty(String $section)
{
// return line
}
public function getKeyLine(String $section,String $key)
{
// return line
}
}
/** /**
* Edit a file line by line * Edit the sections and keys of an ini in-place
*
* The functions delete, insert and update can be applied to certain lines
* of the file and are written to it, once applyChanges is called. Line inserts and deletes
* are handled automatically and the changes in line numbers don't need to be respected when
* calling the edit functions.
*/ */
class FileEditor class IniEditor
{ {
/** /**
* @var String * The text that is edited
*/
private $filename;
/**
* The symbol that delimits a comment.
* *
* @var string * @var string
*/ */
private $commentDelimiter = ''; private $text;
/** /**
* Set a new comment delimiter * The symbol that is used
*
* @var string
*/ */
public function setCommentDelimiter(String $delimiter) private $nestSeparator = '.';
/**
* Get the nest separator
*
* @return string The nest separator
*/
public function getNestSeparator()
{ {
$this->commentDelimiter = $delimiter; return $this->nestSeparator;
}
/**
* Set the nest separator
*
* @param $separator The nest separator
* @return mixed The current instance of IniReader
*/
public function setNestSeparator($separator)
{
$this->nestSeparator = $separator;
return $this; return $this;
} }
/** /**
* Get the current comment delimiter * Create a new IniEditor
* *
* @return string The comment delimiter * @param $content The content of the ini as string
*/ */
public function getCommentDelimiter() public function __construct($content)
{ {
return $this->commentDelimiter; $this->text = explode("\n",$content);
} }
/** /**
* Create a new FileEditor * Set the value of the given key.
* *
* @param $filename The file that should be edited. * Update the key, if it already exists, otherwise create it.
*
* @param array $key
* @param $value
*/ */
public function __constructor($filename) public function set(array $key,$value)
{ {
$this->filename = $filename; $line = $this->getKeyLine($key);
if ($line === -1) {
$this->insert($key,$value);
return;
}
$content = $this->formatKeyValuePair(
$this->truncateSection($key),$value);
$this->updateLine($line,$content);
}
public function delArrayEl(array $key)
{
$line = $this->getArrayEl($key);
if ($line !== -1) {
$this->deleteLine($line);
}
}
public function setArrayEl(array $key,$value)
{
$line = $this->getArrayEl($key);
if (count($key) > 1) {
$ident = $this->truncateSection($key);
$section = $key[0];
} else {
$ident = $key;
$section = null;
}
if ($line !== -1) {
if (count($ident) > 1){
$this->updateLine($line,$this->formatKeyValuePair($ident,$value));
} else {
// move into own section
$this->deleteLine($line);
$this->setSection($section);
$this->insert(array_merge(array($section),$ident),$value);
}
} else {
$e = $this->getSectionEnd($section);
$this->insertAtLine($e,$this->formatKeyValuePair($ident,$value));
}
} }
/** /**
* Delete a line * Get the line of an array element
* *
* @param $line The line * @param array $key The key of the property.
* @param $value The value
*/ */
public function delete($line) private function getArrayEl(array $key)
{ {
$line = 0;
if (count($key) > 1) {
$line = $this->getSectionDeclLine($key[0]) + 1;
$validKey = array_slice($key,1,null,true);
}
$index = array_pop($validKey);
$formattedKey = explode('=',$this->formatKeyValuePair($validKey,''));
$formattedKey = $formattedKey[0];
for (;$line < count($this->text);$line++) {
$l = $this->text[$line];
if ($this->isSectionDecl($l)) {
return -1;
}
if (strlen($formattedKey) > 0) {
if (preg_match('/^'.$formattedKey.'\[\]/',$l) === 1 ||
preg_match('/^'.$formattedKey.'.'.$index.'/',$l) === 1 ) {
return $line;
}
} else {
if (preg_match('/^'.$index.'/',$l) === 1 ) {
return $line;
}
}
}
return -1;
} }
/** /**
* Insert a text into the file * Reset the given key
* *
* @param $line The line where the text should be inserted * Set the key to null, if it already exists. Otherwise do nothing.
* @param $text The text *
* @param array $key
*/ */
public function insert($line,$text) public function reset(array $key)
{ {
$line = $this->getKeyLine($key);
if ($line === -1) {
return;
}
$this->deleteLine($line);
} }
/** /**
* Update the given line and insert $text * Change the extended section of $section
*
* Update the line but ignore text separated by a comment delimiter.
*
* @param $line The line number
* @param $text The text that will be inserted
*/ */
public function update($line,$text) public function setSection($section,$extend = null)
{ {
if (isset($extend)) {
$decl = '['.$section.' : '.$extend.']';
} else {
$decl = '['.$section.']';
}
$line = $this->getSectionDeclLine($section);
if ($line !== -1) {
$this->deleteLine($line);
$this->insertAtLine($line,$decl);
} else {
$line = $this->getLastLine();
$this->insertAtLine($line,$decl);
$this->insertAtLine($line,"");
}
} }
/** /**
* Write changes to the file * Remove the section declarationa of $section
*/ */
public function applyChanges() public function removeSection($section)
{ {
$line = $this->getSectionDeclLine($section);
if ($line !== -1) {
$this->deleteLine($line);
}
}
} /**
} * Insert a key
*
/** * Insert the key at the end of the corresponding section.
* A diff that describes the change of an object property *
*/ * @param array $key The key to insert
class PropertyDiff { * @param $value The value to insert
*/
/** private function insert(array $key,$value)
* Create the property diff between two objects {
* if (count($key) > 1) {
* @param stdClass $oldObject The object representing the state before the change // insert into end of section
* @param stdClass $newObject The object representing the state after the change $line = $this->getSectionEnd($key[0]);
* } else {
* @return array An associative array mapping all changed properties to a property diff // insert into section-less space
* describing the change $line = $this->getSectionEnd();
*/ }
public static function createObjectDiff(stdClass $oldObject,stdClass $newObject) $content = $this->formatKeyValuePair($this->truncateSection($key),$value);
{ $this->insertAtLine($line,$content);
$diffs = array(); }
/*
* Search inserted or updated properties
*/ /**
foreach ($newObject as $key => $value) { * Return the edited text
$newProperty = $value; *
$oldProperty = $oldObject->{$key}; * @return string The edited text
if (is_array($newProperty)) { */
if (empty($oldProperty)) { public function getText()
$diffs[$key] = new PropertyDiff( {
PropertyDiff::ACTION_INSERT, // clean up whitespaces
PropertyDiff::createObjectDiff(new \stdClass(),$newObject)); $i = count($this->text) - 1;
} else { for (;$i >= 0; $i--) {
$diffs[$key] = new PropertyDiff( $line = $this->text[$i];
PropertyDiff::ACTION_NONE, if ($this->isSectionDecl($line)) {
PropertyDiff::createObjectDiff($oldObject,$newObject) $i--;
); $line = $this->text[$i];
} while ($i >= 0 && preg_match('/^[\s]*$/',$line) === 1) {
} else { $this->deleteLine($i);
if (empty($oldProperty)) { $i--;
$diffs[$key] = $line = $this->text[$i];
new PropertyDiff(PropertyDiff::ACTION_INSERT,$newProperty); }
} elseif (strcasecmp($newProperty,$oldProperty) != 0) { if ($i !== 0) {
$diffs[$key] = $this->insertAtLine($i + 1,'');
new PropertyDiff(PropertyDiff::ACTION_UPDATE,$newProperty); }
} }
} }
} return implode("\n",$this->text);
/* }
* Search deleted properties
*/ /**
foreach ($oldObject as $key => $value) { * Insert the text at line $lineNr
if (empty($newObject->{$key})) { *
$oldProperty = $value; * @param $lineNr The line nr the inserted line should have
if (is_array($oldProperty)){ * @param $toInsert The text that will be inserted
$diffs[key] = new PropertyDiff( */
PropertyDiff::ACTION_DELETE, private function insertAtLine($lineNr,$toInsert)
PropertyDiff::createObjectDiff($oldObject,new \stdClass()) {
); $this->text = IniEditor::insertIntoArray($this->text,$lineNr,$toInsert);
} else { }
$diffs[$key] =
new PropertyDiff(PropertyDiff::ACTION_DELETE,null); /**
} * Update the line $lineNr
} *
} * @param $lineNr
} * @param $toInsert The lineNr starting at 0
*/
/** private function updateLine($lineNr,$toInsert)
* The available action types {
*/ $this->text[$lineNr] = $toInsert;
const ACTION_INSERT = 0; }
const ACTION_UPDATE = 1;
const ACTION_DELETE = 2; /**
const ACTION_NONE = 3; * Delete the line $lineNr
*
/** * @param $lineNr The lineNr starting at 0
* The action described by this diff */
* private function deleteLine($lineNr)
* @var String {
*/ $this->text = $this->removeFromArray($this->text,$lineNr);
public $action; }
/** /**
* The value after the change * Format a key-value pair to an INI file-entry
* *
* @var StdClass * @param array $key The key
*/ * @param $value The value
public $value; *
* @return string The formatted key-value pair
/** */
* Create a new PropertyDiff private function formatKeyValuePair(array $key,$value)
* {
* @param int $action The action described by this diff return implode($this->nestSeparator,$key).'='.$this->_prepareValue($value);
* @param string $value The value after the change }
*/
public function Diff($action, $value) /**
{ * Strip the section off of a key, when necessary.
if (action != ACTION_CREATE && *
action != ACTION_UPDATE && * @param array $key
action != ACTION_DELETE) { * @return array
throw new \Exception('Invalid action code: '.$action); */
} private function truncateSection(array $key)
$this->action = $action; {
$this->value = $value; if (count($key) > 1) {
unset($key[0]);
}
return $key;
}
/**
* Get the first line after the given $section
*
* If section is empty, return the end of section-less
* space at the file start.
*
* @param $section The name of the section
* @return int
*/
private function getSectionEnd($section = null)
{
$i = 0;
$started = false;
if (!isset($section)) {
$started = true;
}
foreach ($this->text as $line) {
if ($started) {
if (preg_match('/^\[/',$line) === 1) {
return $i;
}
} elseif (preg_match('/^\['.$section.'.*\]/',$line) === 1) {
$started = true;
}
$i++;
}
if (!$started) {
return -1;
}
return $i;
}
/**
* Check if the given line contains a section declaration
*
* @param $lineContent The content of the line
* @param string $section The optional section name that will be assumed
* @return bool
*/
private function isSectionDecl($lineContent,$section = "")
{
return preg_match('/^\[/'.$section,$lineContent) === 1;
}
private function getSectionDeclLine($section)
{
$i = 0;
foreach ($this->text as $line) {
if (preg_match('/^\['.$section.'/',$line)) {
return $i;
}
$i++;
}
return -1;
}
/**
* Return the line number of the given key
*
* When sections are active, return the first matching key in the key's
* section, otherwise return the first matching key.
*
* @param array $keys The key and its parents
*/
private function getKeyLine(array $keys)
{
// remove section
if (count($keys) > 1) {
// the key is in a section
$section = $keys[0];
$key = implode($this->nestSeparator,array_slice($keys,1,null,true));
$inSection = false;
} else {
// section-less key
$section = null;
$key = implode($this->nestSeparator,$keys);
$inSection = true;
}
$i = 0;
foreach ($this->text as $line) {
if ($inSection && preg_match('/^\[/',$line) === 1) {
return -1;
}
if ($inSection && preg_match('/^'.$key.'/',$line) === 1) {
return $i;
}
if (!$inSection && preg_match('/^\['.$section.'/',$line) === 1) {
$inSection = true;
}
$i++;
}
return -1;
}
/**
* Get the last line number
*
* @return int The line nr. of the last line
*/
private function getLastLine()
{
return count($this->text);
}
/**
* Insert a new element into a specific position of an array
*
* @param $array The array to use
* @param $pos The target position
* @param $element The element to insert
*/
private static function insertIntoArray($array,$pos,$element)
{
array_splice($array, $pos, 0, $element);
return $array;
}
/**
* Remove an element from an array
*
* @param $array The array to use
* @param $pos The position to remove
*/
private function removeFromArray($array,$pos)
{
unset($array[$pos]);
return array_values($array);
}
/**
* Prepare a value for INI
*
* @param $value
* @return string
* @throws Zend_Config_Exception
*/
protected function _prepareValue($value)
{
if (is_integer($value) || is_float($value)) {
return $value;
} elseif (is_bool($value)) {
return ($value ? 'true' : 'false');
} elseif (strpos($value, '"') === false) {
return '"' . $value . '"';
} else {
/** @see Zend_Config_Exception */
require_once 'Zend/Config/Exception.php';
throw new Zend_Config_Exception('Value can not contain double quotes "');
}
} }
} }

View File

@ -6,7 +6,7 @@
namespace Icinga\Protocol\Ldap; namespace Icinga\Protocol\Ldap;
use Icinga\Application\Platform; use Icinga\Application\Platform;
use Icinga\Application\Config; use Icinga\Config\Config;
use Icinga\Application\Logger as Log; use Icinga\Application\Logger as Log;
/** /**

View File

@ -31,7 +31,7 @@ namespace Icinga\Web;
use Icinga\Authentication\Manager as AuthManager; use Icinga\Authentication\Manager as AuthManager;
use Icinga\Application\Benchmark; use Icinga\Application\Benchmark;
use Icinga\Exception; use Icinga\Exception;
use Icinga\Application\Config; use Icinga\Config\Config;
use Icinga\Web\Notification; use Icinga\Web\Notification;
use Zend_Layout as ZfLayout; use Zend_Layout as ZfLayout;
use Zend_Controller_Action as ZfController; use Zend_Controller_Action as ZfController;

View File

@ -28,7 +28,7 @@
*/ */
namespace Icinga\Web; namespace Icinga\Web;
use Icinga\Application\Config as IcingaConfig; use Icinga\Config\Config as IcingaConfig;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
/** /**

View File

@ -3,7 +3,7 @@
namespace Icinga\Web\Widget; namespace Icinga\Web\Widget;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Application\Config; use Icinga\Config\Config;
use Icinga\Web\Widget; use Icinga\Web\Widget;
use Icinga\Web\Widget\Dashboard\Pane; use Icinga\Web\Widget\Dashboard\Pane;
use Icinga\Web\Url; use Icinga\Web\Url;

View File

@ -31,7 +31,7 @@
use Icinga\Application\Benchmark; use Icinga\Application\Benchmark;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Backend; use Icinga\Backend;
use Icinga\Application\Config; use Icinga\Config\Config;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Authentication\Manager; use Icinga\Authentication\Manager;
use Icinga\Web\Form; use Icinga\Web\Form;

View File

@ -2,7 +2,7 @@
namespace Monitoring; namespace Monitoring;
use Icinga\Application\Config as IcingaConfig; use Icinga\Config\Config as IcingaConfig;
use Icinga\Authentication\Manager as AuthManager; use Icinga\Authentication\Manager as AuthManager;
use Exception; use Exception;

View File

@ -2,7 +2,7 @@
namespace Monitoring; namespace Monitoring;
use Icinga\Application\Config; use Icinga\Config\Config;
use Icinga\Web\Session; use Icinga\Web\Session;
use Exception; use Exception;

View File

@ -10,7 +10,7 @@ require 'Zend/Controller/Action.php';
require '../../library/Icinga/Exception/ProgrammingError.php'; require '../../library/Icinga/Exception/ProgrammingError.php';
require '../../library/Icinga/Application/Benchmark.php'; require '../../library/Icinga/Application/Benchmark.php';
require '../../library/Icinga/Application/Config.php'; require '../../library/Icinga/Config/Config.php';
require '../../library/Icinga/Application/Icinga.php'; require '../../library/Icinga/Application/Icinga.php';
require '../../library/Icinga/Web/ActionController.php'; require '../../library/Icinga/Web/ActionController.php';
require '../../library/Icinga/Web/Notification.php'; require '../../library/Icinga/Web/Notification.php';

View File

@ -36,7 +36,7 @@ require_once('Zend/Config/Ini.php');
require_once('Zend/Db.php'); require_once('Zend/Db.php');
require_once('../../library/Icinga/Authentication/UserBackend.php'); require_once('../../library/Icinga/Authentication/UserBackend.php');
require_once('../../library/Icinga/Protocol/Ldap/Exception.php'); require_once('../../library/Icinga/Protocol/Ldap/Exception.php');
require_once('../../library/Icinga/Application/Config.php'); require_once('../../library/Icinga/Config/Config.php');
require_once('../../library/Icinga/Authentication/Credentials.php'); require_once('../../library/Icinga/Authentication/Credentials.php');
require_once('../../library/Icinga/Authentication/Backend/DbUserBackend.php'); require_once('../../library/Icinga/Authentication/Backend/DbUserBackend.php');
require_once('../../library/Icinga/User.php'); require_once('../../library/Icinga/User.php');
@ -45,7 +45,7 @@ use Icinga\Authentication\Backend\DbUserBackend;
use Icinga\Util\Crypto; use Icinga\Util\Crypto;
use Icinga\Authentication\Credentials; use Icinga\Authentication\Credentials;
use Icinga\User; use Icinga\User;
use Icinga\Application\Config; use Icinga\Config\Config;
/** /**
* *

View File

@ -6,9 +6,9 @@
namespace Tests\Icinga\Application; namespace Tests\Icinga\Application;
require_once 'Zend/Config/Ini.php'; require_once 'Zend/Config/Ini.php';
require_once dirname(__FILE__) . '/../../../../../library/Icinga/Application/Config.php'; require_once '../../library/Icinga/Config/Config.php';
use Icinga\Application\Config as IcingaConfig; use Icinga\Config\Config as IcingaConfig;
class ConfigTest extends \PHPUnit_Framework_TestCase class ConfigTest extends \PHPUnit_Framework_TestCase
{ {

View File

@ -0,0 +1,329 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga 2 Web.
*
* Icinga 2 Web - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Tests\Icinga\PreservingIniWriterTest;
require_once 'Zend/Config.php';
require_once 'Zend/Config/Ini.php';
require_once 'Zend/Config/Writer/Ini.php';
require_once('../../library/Icinga/Config/PreservingIniWriter.php');
use Icinga\Config\PreservingIniWriter;
class PreservingIniWriterTest extends \PHPUnit_Framework_TestCase {
private $tmpfiles = array();
/**
* Set up the test fixture
*/
public function setUp()
{
$ini =
';1
trailing1="wert"
arr[]="0"
arr[]="1"
arr[]="2"
arr[]="3"
;2
;3
Trailing2=
[parent]
;4
;5
;6
;7
list[]="zero"
list[]="one"
;8
;9
many.many.nests="value"
propOne="value1"
propTwo="2"
propThree=
propFour="true"
Prop5="true"
[child : parent]
PropOne="overwritten"
;10
';
$this->writeToTmp('orig',$ini);
$this->writeToTmp('sonst','');
$emptyIni = " ";
$this->writeToTmp('empty',$emptyIni);
$editedIni =
';1
;2
;3
;4
;5
trailing1="1"
[parent]
;6
;7
;8
;9
;10
propOne="value1"
[different]
prop1="1"
prop2="2"
[nested : different]
prop2="5"
';
$this->writeToTmp('edited',$editedIni);
}
/**
* Write a string to a temporary file
*
* @param $name The name of the temporary file
* @param $content The content
*/
private function writeToTmp($name,$content)
{
$this->tmpfiles[$name] =
tempnam(dirname(__FILE__) . '/temp',$name);
$file = fopen($this->tmpfiles[$name],'w');
fwrite($file,$content);
fflush($file);
fclose($file);
}
/**
* Tear down the test fixture
*/
public function tearDown()
{
foreach ($this->tmpfiles as $filename) {
unlink($filename);
}
}
/**
* Test if the IniWriter works correctly when writing the changes back to
* the same ini file
*/
public function testPropertyChangeSameConfig()
{
$this->changeConfigAndWriteToFile('orig');
$config = new \Zend_Config_Ini(
$this->tmpfiles['orig'],null,array('allowModifications' => true)
);
$this->checkConfigProperties($config);
$this->checkConfigComments($this->tmpfiles['orig']);
}
/**
* Test if the IniWriter works correctly when writing to an empty file
*/
public function testPropertyChangeEmptyConfig()
{
$this->changeConfigAndWriteToFile('empty');
$config = new \Zend_Config_Ini(
$this->tmpfiles['empty'],null,array('allowModifications' => true)
);
$this->checkConfigProperties($config);
}
/**
* Test if the IniWriter works correctly when writing to a file with changes
*/
public function testPropertyChangeEditedConfig()
{
$original = $this->changeConfigAndWriteToFile('edited');
$config = new \Zend_Config_Ini(
$this->tmpfiles['edited'],null,array('allowModifications' => true)
);
$this->checkConfigProperties($config);
$this->checkConfigComments($this->tmpfiles['edited']);
}
/**
* Change the test config and write the changes to the temporary
* file $tmpFile
*
* @param $tmpFile
*/
private function changeConfigAndWriteToFile($tmpFile)
{
$config = $this->createTestConfig();
$this->alterConfig($config);
$writer = new PreservingIniWriter(
array('config' => $config,'filename' => $this->tmpfiles[$tmpFile])
);
$writer->write();
return $config;
}
/**
* Check if all comments are present
*
* @param $file
*/
private function checkConfigComments($file)
{
$i = 0;
foreach (explode("\n",file_get_contents($file)) as $line) {
if (preg_match('/^;/',$line)) {
$i++;
$this->assertEquals(
$i,intval(substr($line,1)),
'Comment unchanged'
);
}
}
$this->assertEquals(10,$i,'All comments exist');
}
/**
* Test if all configuration properties are set correctly
*
* @param $config
*/
private function checkConfigProperties($config)
{
$this->assertEquals('val',$config->Trailing2,
'Section-less property updated.');
$this->assertNull($config->trailing1,
'Section-less property deleted.');
$this->assertEquals('value',$config->new,
'Section-less property created.');
$this->assertEquals('0',$config->arr->{0},
'Value persisted in array');
$this->assertEquals('update',$config->arr->{2},
'Value changed in array');
$this->assertEquals('arrvalue',$config->arr->{4},
'Value added to array');
$this->assertEquals('',$config->parent->propOne,
'Section property deleted.');
$this->assertEquals("2",$config->parent->propTwo,
'Section property numerical unchanged.');
$this->assertEquals('update',$config->parent->propThree,
'Section property updated.');
$this->assertEquals("true",$config->parent->propFour,
'Section property boolean unchanged.');
$this->assertEquals("1",$config->parent->new,
'Section property numerical created.');
$this->assertNull($config->parent->list->{0},
'Section array deleted'
);
$this->assertEquals('new',$config->parent->list->{1},
'Section array changed.');
$this->assertEquals('changed',$config->parent->many->many->nests,
'Change strongy nested value.');
$this->assertEquals('new',$config->parent->many->many->new,
'Ccreate strongy nested value.');
$this->assertEquals('overwritten',$config->child->PropOne,
'Overridden inherited property unchanged.');
$this->assertEquals('somethingNew',$config->child->propTwo,
'Inherited property changed.');
$this->assertEquals('test',$config->child->create,
'Non-inherited property created.');
$this->assertInstanceOf('Zend_Config',$config->newsection,
'New section created.');
$extends = $config->getExtends();
$this->assertEquals('child',$extends['newsection'],
'New inheritance created.');
}
/**
* Change the content of a Zend_Config
*
* @param Zend_Config $config
*/
private function alterConfig(\Zend_Config $config)
{
$config->Trailing2 = 'val';
unset($config->trailing1);
$config->new = 'value';
$config->arr->{2} = "update";
$config->arr->{4} = "arrvalue";
$config->parent->propOne = null;
$config->parent->propThree = 'update';
$config->parent->new = 1;
unset($config->parent->list->{0});
$config->parent->list->{1} = 'new';
$config->parent->many->many->nests = "changed";
$config->parent->many->many->new = "new";
$config->child->propTwo = 'somethingNew';
$config->child->create = 'test';
$config->newsection = array();
$config->newsection->p1 = "prop";
$config->newsection->P2 = "prop";
$config->setExtend('newsection','child');
}
/**
* Create the the configuration that will be used for the tests.
*/
private function createTestConfig()
{
return new \Zend_Config_Ini(
$this->tmpfiles['orig'],
null,
array('allowModifications' => true)
);
}
}