diff --git a/config/lang/de.json b/config/lang/de.json index e6887784259519552b4f57e76df15d2742616cec..c5eea5d513cbfb2aaed0efb04c053ecfc41d1a2b 100644 --- a/config/lang/de.json +++ b/config/lang/de.json @@ -222,10 +222,10 @@ "creating-directory": "Lege das Verzeichnis %s an.", "creating-file": "Lege Datei %s an.", "defaultbranch": "[default: Master oder Trunk]", + "defaults-all-phases": "Default-Werte (für alle Phasen)", "deploy": "Deploy", "deploy-hint": "Deploy der Queue von Phase [%s]", "deploy-impossible": "Deploy der Queue von Phase [%s] ist nicht möglich.", - "deploy-settings": "Deployment - Einstellungen", "deploytimes": "Deployment Zeitpunkte", "deploytimes-immediately": "Ein Archiv in der Queue wird sofort ins Repo gestellt.", "deploymethod": "Deployment-Methode", @@ -237,6 +237,7 @@ "dir-archive": "Archiv-Verzeichnis", "dir-cache": "Cache-Verzeichnis", "dir-builds": "Build-Verzeichnis", + "edit": "Bearbeiten", "empty": "[leer]", "error": "FEHLER", "error-no-phase": "Es wurde keine Phase mitgegeben.", @@ -245,8 +246,18 @@ "fileprefix": "File Prefix", "fileprefix-label": "File-Prefix <span class=\"error\"><br>Nach dem ersten Build nicht mehr änderbar!</span>", "finished": "Beendet", + "foreman-error-missing-template": "In Foreman wurde das Templatefile [%s] definiert, aber dieses existiert nicht im Build.", + "foreman-error-no-replacement-for-id": "In Foreman wurde keine noch Ersetzung für [%s] definiert.", + "foreman-error-no-target": "In Foreman wurde keine Ziel-Datei definiert", + "foreman-error-template-unknown": "Das Template wurde in Foreman nicht aufgenommen", + "foreman-error-replacement-unknown": "Das Replacement [%s] in Foreman existiert im Template nicht.", "foreman-hostgroup": "Foreman Hostgruppe", + "foreman-hostgroup-override": "Foreman Hostgruppe (override)", + "foreman-hostgroup-id": "Foreman Hostgruppe (%s)", + "foreman-no-host": "Es wurde kein Host in Foreman konfiguriert. Es werden die Platzhalter in Templates erkannt, aber nicht deren Ziel und Ersetzungen.", + "foreman-targetfile": "Name der Zieldatei", "gotop": "Seitenanfang", + "host": "Host", "hosts": "Hosts", "hostname4puppet": "Hostname für Puppet Agent", "inactive": "inaktiv", @@ -275,7 +286,6 @@ "replacement-fields-not-found": "Es wurden keine Platzhalter im Format <em>@replace["Name"]</em> im Template gefunden.", "replacement-targetfile": "Ziel-Datei", "replacements": "Ersetzungen mit Template-Dateien", - "replacements-info": "Beim Build-Prozess gefundene Templates werden aufgelistet und darin vorhandene Platzhalter erkannt. Die Felder sind ausschliesslich für die neue Infrastruktur relevant.", "repositoryinfos": "Quell-Repository", "repository-access-browser": "Browserzugriff auf das Repo", "repository-auth": "Authentifizierung/ Dateiname zum SSH-Private-Key", diff --git a/config/lang/en.json b/config/lang/en.json index 5f2a7ea74bbe806b1d57a1f48e59393c7d7ced9a..223cb552088e7e981d06f612c5b632bf746d943a 100644 --- a/config/lang/en.json +++ b/config/lang/en.json @@ -224,10 +224,10 @@ "creating-directory": "Creating directory %s", "creating-file": "Creating file %s", "defaultbranch": "[default: master or trunk]", + "defaults-all-phases": "Default values (for all phases)", "deploy": "Deploy", "deploy-hint": "Deploy queue of phase [%s]", "deploy-impossible": "Deploy queue of phase [%s] is not possible.", - "deploy-settings": "Deployment - settings", "deploymethod": "Deployment method", "deploymethod-none": "None. Do not trigger any action.", "deploymethod-puppet": "Run Puppet on target host(s)", @@ -239,6 +239,7 @@ "dir-archive": "Archive directory", "dir-cache": "Cache directory", "dir-builds": "Build directory", + "edit": "Edit", "empty": "[empty]", "error": "ERROR", "error-no-phase": "There no phase was given as parameter.", @@ -247,8 +248,18 @@ "fileprefix": "File prefix", "fileprefix-label": "File prefix <span class=\"error\"><br>It cannot be changed after the first build!</span>", "finished": "Fisnished", + "foreman-error-missing-template": "In Foreman the templatefile [%s] was defined but it does not exist in the build.", + "foreman-error-no-replacement-for-id": "There is no replacement for [%s] in Foreman.", + "foreman-error-no-target": "No target file was set in Foreman", + "foreman-error-template-unknown": "The template was not added in Foreman.", + "foreman-error-replacement-unknown": "The replacement [%s] from Foreman has no plceholder in the template file.", "foreman-hostgroup": "Foreman hostgroup", + "foreman-hostgroup-override": "Foreman Hostgroup (override)", + "foreman-hostgroup-id": "Foreman Hostgroup (%s)", + "foreman-no-host": "No host was configured in Foreman.", + "foreman-targetfile": "Target file", "gotop": "top", + "host": "Host", "hosts": "Hosts", "hostname4puppet": "Hostname to start puppet agent", "inactive": "inactive", @@ -277,7 +288,6 @@ "replacement-fields-not-found": "No placeholder in the syntax <em>@replace["name"]</em> were found in the template.", "replacement-targetfile": "Target file", "replacements": "Replacements in template files", - "replacements-info": "List of detected templates with its placeholders.", "repositoryinfos": "Sourcecode Repository", "repository-access-browser": "Browser access to sources", "repository-auth": "Authentication/ filename of ssh private key", diff --git a/public_html/deployment/classes/Spyc.php b/public_html/deployment/classes/Spyc.php new file mode 100644 index 0000000000000000000000000000000000000000..2cf1bbc263db23da335fa1d74c8184493d5f8e5a --- /dev/null +++ b/public_html/deployment/classes/Spyc.php @@ -0,0 +1,1161 @@ +<?php +/** + * Spyc -- A Simple PHP YAML Class + * @version 0.6.2 + * @author Vlad Andersen <vlad.andersen@gmail.com> + * @author Chris Wanstrath <chris@ozmm.org> + * @link https://github.com/mustangostang/spyc/ + * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @package Spyc + */ + +if (!function_exists('spyc_load')) { + /** + * Parses YAML to array. + * @param string $string YAML string. + * @return array + */ + function spyc_load ($string) { + return Spyc::YAMLLoadString($string); + } +} + +if (!function_exists('spyc_load_file')) { + /** + * Parses YAML to array. + * @param string $file Path to YAML file. + * @return array + */ + function spyc_load_file ($file) { + return Spyc::YAMLLoad($file); + } +} + +if (!function_exists('spyc_dump')) { + /** + * Dumps array to YAML. + * @param array $data Array. + * @return string + */ + function spyc_dump ($data) { + return Spyc::YAMLDump($data, false, false, true); + } +} + +if (!class_exists('Spyc')) { + +/** + * The Simple PHP YAML Class. + * + * This class can be used to read a YAML file and convert its contents + * into a PHP array. It currently supports a very limited subsection of + * the YAML spec. + * + * Usage: + * <code> + * $Spyc = new Spyc; + * $array = $Spyc->load($file); + * </code> + * or: + * <code> + * $array = Spyc::YAMLLoad($file); + * </code> + * or: + * <code> + * $array = spyc_load_file($file); + * </code> + * @package Spyc + */ +class Spyc { + + // SETTINGS + + const REMPTY = "\0\0\0\0\0"; + + /** + * Setting this to true will force YAMLDump to enclose any string value in + * quotes. False by default. + * + * @var bool + */ + public $setting_dump_force_quotes = false; + + /** + * Setting this to true will forse YAMLLoad to use syck_load function when + * possible. False by default. + * @var bool + */ + public $setting_use_syck_is_possible = false; + + + + /**#@+ + * @access private + * @var mixed + */ + private $_dumpIndent; + private $_dumpWordWrap; + private $_containsGroupAnchor = false; + private $_containsGroupAlias = false; + private $path; + private $result; + private $LiteralPlaceHolder = '___YAML_Literal_Block___'; + private $SavedGroups = array(); + private $indent; + /** + * Path modifier that should be applied after adding current element. + * @var array + */ + private $delayedPath = array(); + + /**#@+ + * @access public + * @var mixed + */ + public $_nodeId; + +/** + * Load a valid YAML string to Spyc. + * @param string $input + * @return array + */ + public function load ($input) { + return $this->_loadString($input); + } + + /** + * Load a valid YAML file to Spyc. + * @param string $file + * @return array + */ + public function loadFile ($file) { + return $this->_load($file); + } + + /** + * Load YAML into a PHP array statically + * + * The load method, when supplied with a YAML stream (string or file), + * will do its best to convert YAML in a file into a PHP array. Pretty + * simple. + * Usage: + * <code> + * $array = Spyc::YAMLLoad('lucky.yaml'); + * print_r($array); + * </code> + * @access public + * @return array + * @param string $input Path of YAML file or string containing YAML + */ + public static function YAMLLoad($input) { + $Spyc = new Spyc; + return $Spyc->_load($input); + } + + /** + * Load a string of YAML into a PHP array statically + * + * The load method, when supplied with a YAML string, will do its best + * to convert YAML in a string into a PHP array. Pretty simple. + * + * Note: use this function if you don't want files from the file system + * loaded and processed as YAML. This is of interest to people concerned + * about security whose input is from a string. + * + * Usage: + * <code> + * $array = Spyc::YAMLLoadString("---\n0: hello world\n"); + * print_r($array); + * </code> + * @access public + * @return array + * @param string $input String containing YAML + */ + public static function YAMLLoadString($input) { + $Spyc = new Spyc; + return $Spyc->_loadString($input); + } + + /** + * Dump YAML from PHP array statically + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as nothing.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array|\stdClass $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + * @param bool $no_opening_dashes Do not start YAML file with "---\n" + */ + public static function YAMLDump($array, $indent = false, $wordwrap = false, $no_opening_dashes = false) { + $spyc = new Spyc; + return $spyc->dump($array, $indent, $wordwrap, $no_opening_dashes); + } + + + /** + * Dump PHP array to YAML + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as tasteful.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + */ + public function dump($array,$indent = false,$wordwrap = false, $no_opening_dashes = false) { + // Dumps to some very clean YAML. We'll have to add some more features + // and options soon. And better support for folding. + + // New features and options. + if ($indent === false or !is_numeric($indent)) { + $this->_dumpIndent = 2; + } else { + $this->_dumpIndent = $indent; + } + + if ($wordwrap === false or !is_numeric($wordwrap)) { + $this->_dumpWordWrap = 40; + } else { + $this->_dumpWordWrap = $wordwrap; + } + + // New YAML document + $string = ""; + if (!$no_opening_dashes) $string = "---\n"; + + // Start at the base of the array and move through it. + if ($array) { + $array = (array)$array; + $previous_key = -1; + foreach ($array as $key => $value) { + if (!isset($first_key)) $first_key = $key; + $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key, $array); + $previous_key = $key; + } + } + return $string; + } + + /** + * Attempts to convert a key / value array item to YAML + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + private function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0, $source_array = null) { + if(is_object($value)) $value = (array)$value; + if (is_array($value)) { + if (empty ($value)) + return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key, $source_array); + // It has children. What to do? + // Make it the right kind of item + $string = $this->_dumpNode($key, self::REMPTY, $indent, $previous_key, $first_key, $source_array); + // Add the indent + $indent += $this->_dumpIndent; + // Yamlize the array + $string .= $this->_yamlizeArray($value,$indent); + } elseif (!is_array($value)) { + // It doesn't have children. Yip. + $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key, $source_array); + } + return $string; + } + + /** + * Attempts to convert an array to YAML + * @access private + * @return string + * @param $array The array you want to convert + * @param $indent The indent of the current level + */ + private function _yamlizeArray($array,$indent) { + if (is_array($array)) { + $string = ''; + $previous_key = -1; + foreach ($array as $key => $value) { + if (!isset($first_key)) $first_key = $key; + $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key, $array); + $previous_key = $key; + } + return $string; + } else { + return false; + } + } + + /** + * Returns YAML from a key and a value + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + private function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0, $source_array = null) { + // do some folding here, for blocks + if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false || + strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value, '%') !== false || strpos ($value, ' ') !== false || + strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value, "'") !== false || strpos($value, "!") === 0 || + substr ($value, -1, 1) == ':') + ) { + $value = $this->_doLiteralBlock($value,$indent); + } else { + $value = $this->_doFolding($value,$indent); + } + + if ($value === array()) $value = '[ ]'; + if ($value === "") $value = '""'; + if (self::isTranslationWord($value)) { + $value = $this->_doLiteralBlock($value, $indent); + } + if (trim ($value) != $value) + $value = $this->_doLiteralBlock($value,$indent); + + if (is_bool($value)) { + $value = $value ? "true" : "false"; + } + + if ($value === null) $value = 'null'; + if ($value === "'" . self::REMPTY . "'") $value = null; + + $spaces = str_repeat(' ',$indent); + + //if (is_int($key) && $key - 1 == $previous_key && $first_key===0) { + if (is_array ($source_array) && array_keys($source_array) === range(0, count($source_array) - 1)) { + // It's a sequence + $string = $spaces.'- '.$value."\n"; + } else { + // if ($first_key===0) throw new Exception('Keys are all screwy. The first one was zero, now it\'s "'. $key .'"'); + // It's mapped + if (strpos($key, ":") !== false || strpos($key, "#") !== false) { $key = '"' . $key . '"'; } + $string = rtrim ($spaces.$key.': '.$value)."\n"; + } + return $string; + } + + /** + * Creates a literal block for dumping + * @access private + * @return string + * @param $value + * @param $indent int The value of the indent + */ + private function _doLiteralBlock($value,$indent) { + if ($value === "\n") return '\n'; + if (strpos($value, "\n") === false && strpos($value, "'") === false) { + return sprintf ("'%s'", $value); + } + if (strpos($value, "\n") === false && strpos($value, '"') === false) { + return sprintf ('"%s"', $value); + } + $exploded = explode("\n",$value); + $newValue = '|'; + if (isset($exploded[0]) && ($exploded[0] == "|" || $exploded[0] == "|-" || $exploded[0] == ">")) { + $newValue = $exploded[0]; + unset($exploded[0]); + } + $indent += $this->_dumpIndent; + $spaces = str_repeat(' ',$indent); + foreach ($exploded as $line) { + $line = trim($line); + if (strpos($line, '"') === 0 && strrpos($line, '"') == (strlen($line)-1) || strpos($line, "'") === 0 && strrpos($line, "'") == (strlen($line)-1)) { + $line = substr($line, 1, -1); + } + $newValue .= "\n" . $spaces . ($line); + } + return $newValue; + } + + /** + * Folds a string of text, if necessary + * @access private + * @return string + * @param $value The string you wish to fold + */ + private function _doFolding($value,$indent) { + // Don't do anything if wordwrap is set to 0 + + if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) { + $indent += $this->_dumpIndent; + $indent = str_repeat(' ',$indent); + $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent"); + $value = ">\n".$indent.$wrapped; + } else { + if ($this->setting_dump_force_quotes && is_string ($value) && $value !== self::REMPTY) + $value = '"' . $value . '"'; + if (is_numeric($value) && is_string($value)) + $value = '"' . $value . '"'; + } + + + return $value; + } + + private function isTrueWord($value) { + $words = self::getTranslations(array('true', 'on', 'yes', 'y')); + return in_array($value, $words, true); + } + + private function isFalseWord($value) { + $words = self::getTranslations(array('false', 'off', 'no', 'n')); + return in_array($value, $words, true); + } + + private function isNullWord($value) { + $words = self::getTranslations(array('null', '~')); + return in_array($value, $words, true); + } + + private function isTranslationWord($value) { + return ( + self::isTrueWord($value) || + self::isFalseWord($value) || + self::isNullWord($value) + ); + } + + /** + * Coerce a string into a native type + * Reference: http://yaml.org/type/bool.html + * TODO: Use only words from the YAML spec. + * @access private + * @param $value The value to coerce + */ + private function coerceValue(&$value) { + if (self::isTrueWord($value)) { + $value = true; + } else if (self::isFalseWord($value)) { + $value = false; + } else if (self::isNullWord($value)) { + $value = null; + } + } + + /** + * Given a set of words, perform the appropriate translations on them to + * match the YAML 1.1 specification for type coercing. + * @param $words The words to translate + * @access private + */ + private static function getTranslations(array $words) { + $result = array(); + foreach ($words as $i) { + $result = array_merge($result, array(ucfirst($i), strtoupper($i), strtolower($i))); + } + return $result; + } + +// LOADING FUNCTIONS + + private function _load($input) { + $Source = $this->loadFromSource($input); + return $this->loadWithSource($Source); + } + + private function _loadString($input) { + $Source = $this->loadFromString($input); + return $this->loadWithSource($Source); + } + + private function loadWithSource($Source) { + if (empty ($Source)) return array(); + if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) { + $array = syck_load (implode ("\n", $Source)); + return is_array($array) ? $array : array(); + } + + $this->path = array(); + $this->result = array(); + + $cnt = count($Source); + for ($i = 0; $i < $cnt; $i++) { + $line = $Source[$i]; + + $this->indent = strlen($line) - strlen(ltrim($line)); + $tempPath = $this->getParentPathByIndent($this->indent); + $line = self::stripIndent($line, $this->indent); + if (self::isComment($line)) continue; + if (self::isEmpty($line)) continue; + $this->path = $tempPath; + + $literalBlockStyle = self::startsLiteralBlock($line); + if ($literalBlockStyle) { + $line = rtrim ($line, $literalBlockStyle . " \n"); + $literalBlock = ''; + $line .= ' '.$this->LiteralPlaceHolder; + $literal_block_indent = strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1])); + while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) { + $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle, $literal_block_indent); + } + $i--; + } + + // Strip out comments + if (strpos ($line, '#')) { + $line = preg_replace('/\s*#([^"\']+)$/','',$line); + } + + while (++$i < $cnt && self::greedilyNeedNextLine($line)) { + $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t"); + } + $i--; + + $lineArray = $this->_parseLine($line); + + if ($literalBlockStyle) + $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock); + + $this->addArray($lineArray, $this->indent); + + foreach ($this->delayedPath as $indent => $delayedPath) + $this->path[$indent] = $delayedPath; + + $this->delayedPath = array(); + + } + return $this->result; + } + + private function loadFromSource ($input) { + if (!empty($input) && strpos($input, "\n") === false && file_exists($input)) + $input = file_get_contents($input); + + return $this->loadFromString($input); + } + + private function loadFromString ($input) { + $lines = explode("\n",$input); + foreach ($lines as $k => $_) { + $lines[$k] = rtrim ($_, "\r"); + } + return $lines; + } + + /** + * Parses YAML code and returns an array for a node + * @access private + * @return array + * @param string $line A line from the YAML file + */ + private function _parseLine($line) { + if (!$line) return array(); + $line = trim($line); + if (!$line) return array(); + + $array = array(); + + $group = $this->nodeContainsGroup($line); + if ($group) { + $this->addGroup($line, $group); + $line = $this->stripGroup ($line, $group); + } + + if ($this->startsMappedSequence($line)) + return $this->returnMappedSequence($line); + + if ($this->startsMappedValue($line)) + return $this->returnMappedValue($line); + + if ($this->isArrayElement($line)) + return $this->returnArrayElement($line); + + if ($this->isPlainArray($line)) + return $this->returnPlainArray($line); + + + return $this->returnKeyValuePair($line); + + } + + /** + * Finds the type of the passed value, returns the value as the new type. + * @access private + * @param string $value + * @return mixed + */ + private function _toType($value) { + if ($value === '') return ""; + $first_character = $value[0]; + $last_character = substr($value, -1, 1); + + $is_quoted = false; + do { + if (!$value) break; + if ($first_character != '"' && $first_character != "'") break; + if ($last_character != '"' && $last_character != "'") break; + $is_quoted = true; + } while (0); + + if ($is_quoted) { + $value = str_replace('\n', "\n", $value); + if ($first_character == "'") + return strtr(substr ($value, 1, -1), array ('\'\'' => '\'', '\\\''=> '\'')); + return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\\\''=> '\'')); + } + + if (strpos($value, ' #') !== false && !$is_quoted) + $value = preg_replace('/\s+#(.+)$/','',$value); + + if ($first_character == '[' && $last_character == ']') { + // Take out strings sequences and mappings + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $value = array(); + foreach ($explode as $v) { + $value[] = $this->_toType($v); + } + return $value; + } + + if (strpos($value,': ')!==false && $first_character != '{') { + $array = explode(': ',$value); + $key = trim($array[0]); + array_shift($array); + $value = trim(implode(': ',$array)); + $value = $this->_toType($value); + return array($key => $value); + } + + if ($first_character == '{' && $last_character == '}') { + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + // Inline Mapping + // Take out strings sequences and mappings + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $array = array(); + foreach ($explode as $v) { + $SubArr = $this->_toType($v); + if (empty($SubArr)) continue; + if (is_array ($SubArr)) { + $array[key($SubArr)] = $SubArr[key($SubArr)]; continue; + } + $array[] = $SubArr; + } + return $array; + } + + if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') { + return null; + } + + if ( is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/', $value) ){ + $intvalue = (int)$value; + if ($intvalue != PHP_INT_MAX && $intvalue != ~PHP_INT_MAX) + $value = $intvalue; + return $value; + } + + if ( is_string($value) && preg_match('/^0[xX][0-9a-fA-F]+$/', $value)) { + // Hexadecimal value. + return hexdec($value); + } + + $this->coerceValue($value); + + if (is_numeric($value)) { + if ($value === '0') return 0; + if (rtrim ($value, 0) === $value) + $value = (float)$value; + return $value; + } + + return $value; + } + + /** + * Used in inlines to check for more inlines or quoted strings + * @access private + * @return array + */ + private function _inlineEscape($inline) { + // There's gotta be a cleaner way to do this... + // While pure sequences seem to be nesting just fine, + // pure mappings and mappings with sequences inside can't go very + // deep. This needs to be fixed. + + $seqs = array(); + $maps = array(); + $saved_strings = array(); + $saved_empties = array(); + + // Check for empty strings + $regex = '/("")|(\'\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_empties = $strings[0]; + $inline = preg_replace($regex,'YAMLEmpty',$inline); + } + unset($regex); + + // Check for strings + $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_strings = $strings[0]; + $inline = preg_replace($regex,'YAMLString',$inline); + } + unset($regex); + + // echo $inline; + + $i = 0; + do { + + // Check for sequences + while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) { + $seqs[] = $matchseqs[0]; + $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1); + } + + // Check for mappings + while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) { + $maps[] = $matchmaps[0]; + $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1); + } + + if ($i++ >= 10) break; + + } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false); + + $explode = explode(',',$inline); + $explode = array_map('trim', $explode); + $stringi = 0; $i = 0; + + while (1) { + + // Re-add the sequences + if (!empty($seqs)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + foreach ($seqs as $seqk => $seq) { + $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value); + $value = $explode[$key]; + } + } + } + } + + // Re-add the mappings + if (!empty($maps)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLMap') !== false) { + foreach ($maps as $mapk => $map) { + $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value); + $value = $explode[$key]; + } + } + } + } + + + // Re-add the strings + if (!empty($saved_strings)) { + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLString') !== false) { + $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1); + unset($saved_strings[$stringi]); + ++$stringi; + $value = $explode[$key]; + } + } + } + + + // Re-add the empties + if (!empty($saved_empties)) { + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLEmpty') !== false) { + $explode[$key] = preg_replace('/YAMLEmpty/', '', $value, 1); + $value = $explode[$key]; + } + } + } + + $finished = true; + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLMap') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLString') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLEmpty') !== false) { + $finished = false; break; + } + } + if ($finished) break; + + $i++; + if ($i > 10) + break; // Prevent infinite loops. + } + + + return $explode; + } + + private function literalBlockContinues ($line, $lineIndent) { + if (!trim($line)) return true; + if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true; + return false; + } + + private function referenceContentsByAlias ($alias) { + do { + if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; } + $groupPath = $this->SavedGroups[$alias]; + $value = $this->result; + foreach ($groupPath as $k) { + $value = $value[$k]; + } + } while (false); + return $value; + } + + private function addArrayInline ($array, $indent) { + $CommonGroupPath = $this->path; + if (empty ($array)) return false; + + foreach ($array as $k => $_) { + $this->addArray(array($k => $_), $indent); + $this->path = $CommonGroupPath; + } + return true; + } + + private function addArray ($incoming_data, $incoming_indent) { + + // print_r ($incoming_data); + + if (count ($incoming_data) > 1) + return $this->addArrayInline ($incoming_data, $incoming_indent); + + $key = key ($incoming_data); + $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null; + if ($key === '__!YAMLZero') $key = '0'; + + if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values. + if ($key || $key === '' || $key === '0') { + $this->result[$key] = $value; + } else { + $this->result[] = $value; end ($this->result); $key = key ($this->result); + } + $this->path[$incoming_indent] = $key; + return; + } + + + + $history = array(); + // Unfolding inner array tree. + $history[] = $_arr = $this->result; + foreach ($this->path as $k) { + $history[] = $_arr = $_arr[$k]; + } + + if ($this->_containsGroupAlias) { + $value = $this->referenceContentsByAlias($this->_containsGroupAlias); + $this->_containsGroupAlias = false; + } + + + // Adding string or numeric key to the innermost level or $this->arr. + if (is_string($key) && $key == '<<') { + if (!is_array ($_arr)) { $_arr = array (); } + + $_arr = array_merge ($_arr, $value); + } else if ($key || $key === '' || $key === '0') { + if (!is_array ($_arr)) + $_arr = array ($key=>$value); + else + $_arr[$key] = $value; + } else { + if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; } + else { $_arr[] = $value; end ($_arr); $key = key ($_arr); } + } + + $reverse_path = array_reverse($this->path); + $reverse_history = array_reverse ($history); + $reverse_history[0] = $_arr; + $cnt = count($reverse_history) - 1; + for ($i = 0; $i < $cnt; $i++) { + $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i]; + } + $this->result = $reverse_history[$cnt]; + + $this->path[$incoming_indent] = $key; + + if ($this->_containsGroupAnchor) { + $this->SavedGroups[$this->_containsGroupAnchor] = $this->path; + if (is_array ($value)) { + $k = key ($value); + if (!is_int ($k)) { + $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k; + } + } + $this->_containsGroupAnchor = false; + } + + } + + private static function startsLiteralBlock ($line) { + $lastChar = substr (trim($line), -1); + if ($lastChar != '>' && $lastChar != '|') return false; + if ($lastChar == '|') return $lastChar; + // HTML tags should not be counted as literal blocks. + if (preg_match ('#<.*?>$#', $line)) return false; + return $lastChar; + } + + private static function greedilyNeedNextLine($line) { + $line = trim ($line); + if (!strlen($line)) return false; + if (substr ($line, -1, 1) == ']') return false; + if ($line[0] == '[') return true; + if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true; + return false; + } + + private function addLiteralLine ($literalBlock, $line, $literalBlockStyle, $indent = -1) { + $line = self::stripIndent($line, $indent); + if ($literalBlockStyle !== '|') { + $line = self::stripIndent($line); + } + $line = rtrim ($line, "\r\n\t ") . "\n"; + if ($literalBlockStyle == '|') { + return $literalBlock . $line; + } + if (strlen($line) == 0) + return rtrim($literalBlock, ' ') . "\n"; + if ($line == "\n" && $literalBlockStyle == '>') { + return rtrim ($literalBlock, " \t") . "\n"; + } + if ($line != "\n") + $line = trim ($line, "\r\n ") . " "; + return $literalBlock . $line; + } + + function revertLiteralPlaceHolder ($lineArray, $literalBlock) { + foreach ($lineArray as $k => $_) { + if (is_array($_)) + $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock); + else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder) + $lineArray[$k] = rtrim ($literalBlock, " \r\n"); + } + return $lineArray; + } + + private static function stripIndent ($line, $indent = -1) { + if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line)); + return substr ($line, $indent); + } + + private function getParentPathByIndent ($indent) { + if ($indent == 0) return array(); + $linePath = $this->path; + do { + end($linePath); $lastIndentInParentPath = key($linePath); + if ($indent <= $lastIndentInParentPath) array_pop ($linePath); + } while ($indent <= $lastIndentInParentPath); + return $linePath; + } + + + private function clearBiggerPathValues ($indent) { + + + if ($indent == 0) $this->path = array(); + if (empty ($this->path)) return true; + + foreach ($this->path as $k => $_) { + if ($k > $indent) unset ($this->path[$k]); + } + + return true; + } + + + private static function isComment ($line) { + if (!$line) return false; + if ($line[0] == '#') return true; + if (trim($line, " \r\n\t") == '---') return true; + return false; + } + + private static function isEmpty ($line) { + return (trim ($line) === ''); + } + + + private function isArrayElement ($line) { + if (!$line || !is_scalar($line)) return false; + if (substr($line, 0, 2) != '- ') return false; + if (strlen ($line) > 3) + if (substr($line,0,3) == '---') return false; + + return true; + } + + private function isHashElement ($line) { + return strpos($line, ':'); + } + + private function isLiteral ($line) { + if ($this->isArrayElement($line)) return false; + if ($this->isHashElement($line)) return false; + return true; + } + + + private static function unquote ($value) { + if (!$value) return $value; + if (!is_string($value)) return $value; + if ($value[0] == '\'') return trim ($value, '\''); + if ($value[0] == '"') return trim ($value, '"'); + return $value; + } + + private function startsMappedSequence ($line) { + return (substr($line, 0, 2) == '- ' && substr ($line, -1, 1) == ':'); + } + + private function returnMappedSequence ($line) { + $array = array(); + $key = self::unquote(trim(substr($line,1,-1))); + $array[$key] = array(); + $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key); + return array($array); + } + + private function checkKeysInValue($value) { + if (strchr('[{"\'', $value[0]) === false) { + if (strchr($value, ': ') !== false) { + throw new Exception('Too many keys: '.$value); + } + } + } + + private function returnMappedValue ($line) { + $this->checkKeysInValue($line); + $array = array(); + $key = self::unquote (trim(substr($line,0,-1))); + $array[$key] = ''; + return $array; + } + + private function startsMappedValue ($line) { + return (substr ($line, -1, 1) == ':'); + } + + private function isPlainArray ($line) { + return ($line[0] == '[' && substr ($line, -1, 1) == ']'); + } + + private function returnPlainArray ($line) { + return $this->_toType($line); + } + + private function returnKeyValuePair ($line) { + $array = array(); + $key = ''; + if (strpos ($line, ': ')) { + // It's a key/value pair most likely + // If the key is in double quotes pull it out + if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) { + $value = trim(str_replace($matches[1],'',$line)); + $key = $matches[2]; + } else { + // Do some guesswork as to the key and the value + $explode = explode(': ', $line); + $key = trim(array_shift($explode)); + $value = trim(implode(': ', $explode)); + $this->checkKeysInValue($value); + } + // Set the type of the value. Int, string, etc + $value = $this->_toType($value); + if ($key === '0') $key = '__!YAMLZero'; + $array[$key] = $value; + } else { + $array = array ($line); + } + return $array; + + } + + + private function returnArrayElement ($line) { + if (strlen($line) <= 1) return array(array()); // Weird %) + $array = array(); + $value = trim(substr($line,1)); + $value = $this->_toType($value); + if ($this->isArrayElement($value)) { + $value = $this->returnArrayElement($value); + } + $array[] = $value; + return $array; + } + + + private function nodeContainsGroup ($line) { + $symbolsForReference = 'A-z0-9_\-'; + if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-) + if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1]; + if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1]; + if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1]; + return false; + + } + + private function addGroup ($line, $group) { + if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1); + if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1); + //print_r ($this->path); + } + + private function stripGroup ($line, $group) { + $line = trim(str_replace($group, '', $line)); + return $line; + } +} +} + +// Enable use of Spyc from command line +// The syntax is the following: php Spyc.php spyc.yaml + +do { + if (PHP_SAPI != 'cli') break; + if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break; + if (empty ($_SERVER['PHP_SELF']) || FALSE === strpos ($_SERVER['PHP_SELF'], 'Spyc.php') ) break; + $file = $argv[1]; + echo json_encode (spyc_load_file ($file)); +} while (0); \ No newline at end of file diff --git a/public_html/deployment/classes/config-replacement.class.php b/public_html/deployment/classes/config-replacement.class.php index 5415757c16d5b8a3c6ab164bdba47f0087177d62..01da4014896e5480888fd6eefadabec3dd63a8ce 100644 --- a/public_html/deployment/classes/config-replacement.class.php +++ b/public_html/deployment/classes/config-replacement.class.php @@ -18,6 +18,8 @@ class configreplacement { * @var type */ protected $_oProject = false; + protected $_sPhase = false; + protected $_aForemanReplacements = false; /** @@ -35,18 +37,17 @@ class configreplacement { /** * get an array with a flat list of all templatefiles of a build - * @param string $sPhase name of phase * @return boolean|array */ - public function getTemplatefiles($sPhase=false){ - $aReturn = array(); - if (!$sPhase){ - $sPhase=$this->_oProject->getNextPhase(false); + public function getTemplatefiles(){ + if (!$this->_sPhase){ + return false; } + $aReturn = array(); - $aBuildfiles=$this->_oProject->getBuildfilesByPlace($sPhase, 'onhold'); + $aBuildfiles=$this->_oProject->getBuildfilesByPlace($this->_sPhase, 'onhold'); if (!$aBuildfiles){ - $aBuildfiles=$this->_oProject->getBuildfilesByPlace($sPhase, 'ready2install'); + $aBuildfiles=$this->_oProject->getBuildfilesByPlace($this->_sPhase, 'ready2install'); } if (!$aBuildfiles || !array_key_exists('types', $aBuildfiles) || !array_key_exists('templates', $aBuildfiles['types'])){ @@ -60,11 +61,13 @@ class configreplacement { /** * get an array with all template files (basename) and its replacement fields - * @param string $sPhase name of phase * @return array */ - public function getReplacements($sPhase=false){ - $aFiles=$this->getTemplatefiles($sPhase); + public function getReplacements(){ + if (!$this->_sPhase){ + return false; + } + $aFiles=$this->getTemplatefiles($this->_sPhase); if (!$aFiles){ return false; } @@ -80,11 +83,256 @@ class configreplacement { return $aReturn; } + /** + * get effective hostgroup id of the current phase + * @return integer + */ + public function getForemanHostgroup(){ + $aPrjConfig=$this->_oProject->getConfig(); + $iForemanHostgroupDefault = (int) $aPrjConfig['deploy']['foreman']['hostgroup']; + $iForemanHostgroup = (int) $aPrjConfig['phases'][$this->_sPhase]['foreman-hostgroup']; + return ($iForemanHostgroup===OPTION_DEFAULT) ? $iForemanHostgroupDefault : $iForemanHostgroup; + } + + /** + * get replacement definitions from foreman + * @global type $aConfig + * @return boolean + */ + protected function _getForemanReplacement(){ + global $aConfig; + if (!$this->_sPhase){ + return false; + } + + // abort if no foreman connection was configured + if (!array_key_exists('foreman', $aConfig)) { + return false; + } + + // return already cached result + if (array_key_exists($this->_sPhase, $this->_aForemanReplacements)){ + return $this->_aForemanReplacements[$this->_sPhase]; + } + + // rebuilt + $this->_aForemanReplacements[$this->_sPhase]=false; + $iEffectiveHostgroup=$this->getForemanHostgroup(); + + // abort if no hostgroup was set + if($iEffectiveHostgroup<=0){ + return false; + } + + require_once 'foremanapi.class.php'; + $oForeman=new ForemanApi($aConfig['foreman']); + + // get a host of this phase + $aHosts=$oForeman->read(array( + 'request' => array( + array('hosts' ), + ), + 'filter' => array( + 'hostgroup_id' => $iEffectiveHostgroup, + ), + 'response' => array('name', 'id'), + )); + + $sHost=''; + $aHostsOfPhase=array(); + + // HACK: phases are part of the hostname .. but not "live" ... and special handling for demo + $aPrjConfig=$this->_oProject->getConfig(); + $bIsDemo=(array_key_exists('fileprefix', $aPrjConfig) + && !strpos($aPrjConfig['fileprefix'], 'demo')===false); + $sPhase=$bIsDemo ? 'demo' : $this->_sPhase; + foreach($aHosts as $aName){ + $sName=$aName['name']; + if (($sPhase==='live' && + ( + strpos($sName, 'preview')===false + && strpos($sName, 'stage')===false + && strpos($sName, 'demo')===false + ) + ) + || (strpos($sName, $sPhase)) + ){ + $sHost=$sHost ? $sHost : $sName; + $aHostsOfPhase[]=$sName; + } + } + + // get created yaml of this host + $sUrl=$aConfig['foreman']['api']."hosts/$sHost/externalNodes?name=$sHost"; + $aData=$oForeman->makeRequest(array( + 'url'=>$sUrl, + 'method'=>'GET', + )); + + // HACK: minify YAML of the host ... because it is too large for SPYC parser + $sPart="---\n"; + $bCopy=false; + foreach(explode("\n", $aData['body']) as $sLine){ + if($bCopy){ + if (strpos($sLine, ' ')===false){ + $bCopy=false; + } else { + // remove leading spaces and html entities... + $sNewLine= html_entity_decode(preg_replace('/^\ \ \ \ /', '', $sLine, 1)); + $sNewLine=str_replace(''{', "'{", $sNewLine); + $sNewLine=str_replace('}'', "}'", $sNewLine); + + // fix json errors + $sNewLine=str_replace(', }', " }", $sNewLine); + $sNewLine=str_replace(',}', "}", $sNewLine); + $sPart.=$sNewLine."\n"; + } + } + if($sLine===' iml-deployment-config:'){ + $bCopy=true; + } + // echo 'DEBUG: '.($bCopy ? 'COPY':'SKIP').$sLine.'<br>'; + } + + require_once "spyc.php"; + $aYaml=spyc_load($sPart); + + $aReturn=array(); + foreach ($aYaml as $aProject){ + foreach ($aProject as $sFile=>$aParams){ + $aReturn[$sFile]=array(); + if (array_key_exists('target', $aParams)){ + $aReturn[$sFile]['target']=$aParams['target']; + } + if (array_key_exists('replace', $aParams)){ + $aReplace=json_decode($aParams['replace'], 1); + $aReturn[$sFile]['replace_source']=$aReplace; + foreach ($aReplace as $sVarname=>$value){ + if (is_array($value) && array_key_exists('_'.$this->_sPhase.'_', $value)){ + $value=$value['_'.$this->_sPhase.'_']; + } + $aReturn[$sFile]['replace'][$sVarname]=$value; + } + } + } + } + + $this->_aForemanReplacements[$this->_sPhase]=array( + 'rules'=>$aReturn, + 'host'=>$sHost, + 'yaml'=>$sPart, + 'hostgroup'=>$iEffectiveHostgroup, + 'hostsall'=>$aHosts, + 'hostsphase'=>$aHostsOfPhase, + ); + return $this->_aForemanReplacements[$this->_sPhase]; + } + + /** + * get foreman base url ... if foreman was activated in the setup + * + * @global array $aConfig ci config + * @return string + */ + private function _getForemanBaseUrl(){ + global $aConfig; + if (!array_key_exists('foreman', $aConfig) + || !array_key_exists('api', $aConfig['foreman']) + || !$aConfig['foreman']['api'] + ){ + return false; + } + return $aConfig['foreman']['api']; + } + + /** + * get html code for links to edit each host of the current phase in foreman + * + * @return boolean + */ + public function getForemanlink2Host(){ + $sForemanurl=$this->_getForemanBaseUrl(); + if (!$sForemanurl){ + return false; + } + $aTmp=$this->_getForemanReplacement(); + if (!array_key_exists('hostsphase', $aTmp)){ + return false; + } + require_once 'htmlguielements.class.php'; + $oHtml=new htmlguielements(); + $sReturn=''; + foreach ($aTmp['hostsphase'] as $sHost){ + $sReturn.=$oHtml->getLinkButton(array( + 'href'=>$sForemanurl.'hosts/'.$aTmp['host'], + 'target'=>'_foreman', + 'title'=>t('edit'), + 'icon'=>'host', + 'label'=>t('host').' '.$aTmp['host'], + )).' '; + } + return $sReturn; + } + + /** + * get html code for a link to edit hostgroup in foreman + * + * @return boolean + */ + public function getForemanlink2Hostgroup(){ + $iEffectiveHostgroup=$this->getForemanHostgroup(); + if($iEffectiveHostgroup<=0){ + return false; + } + $sForemanurl=$this->_getForemanBaseUrl(); + if (!$sForemanurl){ + return false; + } + require_once 'htmlguielements.class.php'; + $oHtml=new htmlguielements(); + return $oHtml->getLinkButton(array( + 'href'=>$sForemanurl.'hostgroups/'.$iEffectiveHostgroup.'/edit', + 'target'=>'_foreman', + 'title'=>t('edit'), + 'icon'=>'hostgroup', + 'label'=>sprintf(t('foreman-hostgroup-id'), $iEffectiveHostgroup), + )); + + } + + /** + * get replacements in foreman + * @return type + */ + public function getForemanReplacements(){ + return $this->_getForemanReplacement(); + } + /** * switch to a project * @param type $sProject */ - public function setProject($sProject){ + public function setProject($sProject, $sPhase=false){ $this->_oProject = new project($sProject); + $this->_aForemanReplacements=false; + $this->setPhase($sPhase); + return true; + } + + /** + * set a phase of a project + * @param string $sPhase name of valid phase + * @return boolean + */ + public function setPhase($sPhase=false){ + $this->_sPhase=false; + if (!$sPhase){ + $sPhase=$this->_oProject->getNextPhase(false); + } + if (!$sPhase || !$this->_oProject->isActivePhase($sPhase)) { + return false; + } + $this->_sPhase=$sPhase; + return true; } } diff --git a/public_html/deployment/classes/foremanapi.class.php b/public_html/deployment/classes/foremanapi.class.php new file mode 100644 index 0000000000000000000000000000000000000000..2c2e3e0878f2e38ab52f9d8f19fc4db725e8eb15 --- /dev/null +++ b/public_html/deployment/classes/foremanapi.class.php @@ -0,0 +1,508 @@ +<?php +/** + * ForemanApi + * + * foreman access to API + * + * @example + * in project class + * $oForeman=new ForemanApi($this->_aConfig['foreman']); + * + * // enable debugging + * $oForeman->setDebug(1); + * + * // self check + * $oForeman->selfcheck(); die(__FUNCTION__); + * + * // read operating systems and get id and title only + * $aForemanHostgroups=$oForeman->read(array( + * 'request'=>array( + * array('operatingsystems'), + * ), + * 'response'=>array( + * 'id','title' + * ), + * )); + * + * // read details for operating systems #4 + * $aForemanHostgroups=$oForeman->read(array( + * 'request'=>array( + * array('operatingsystems', 4), + * ), + * )); + * + * + * $aOptions ... can contain optional subkeys + * - request + * [] list of array(keyword [,id]) + * - filter (array) + * - search (string) + * - page (string) + * - per_page (string) + * - response (array) + * - list of keys, i.e. array('id', 'title') + + * @author hahn + */ +class ForemanApi { + + protected $_aCfg=array(); + protected $_bDebug = false; + + protected $_aAllowedUrls=array( + 'api'=>array( + ''=>array(), + 'architectures'=>array(), + 'audits'=>array('methods'=>array('GET')), + 'auth_source_ldaps'=>array(), + 'bookmarks'=>array(), + 'common_parameters'=>array(), + 'compliance'=>array(), + 'compute_attributes'=>array(), + 'compute_profiles'=>array(), + 'compute_resources'=>array(), + 'config_groups'=>array(), + 'config_reports'=>array(), + 'config_templates'=>array(), + 'dashboard'=>array('methods'=>array('GET')), + 'domains'=>array(), + 'environments'=>array(), + 'fact_values'=>array(), + 'filters'=>array(), + 'hosts'=>array(), + 'hostgroups'=>array(), + 'job_invocations'=>array(), + 'job_templates'=>array(), + 'locations'=>array(), + 'mail_notifications'=>array(), + 'media'=>array(), + 'models'=>array(), + 'operatingsystems'=>array('methods'=>array('GET')), + 'orchestration'=>array(), + 'organizations'=>array(), + 'permissions'=>array(), + 'plugins'=>array(), + 'provisioning_templates'=>array(), + 'ptables'=>array(), + 'puppetclasses'=>array(), + 'realms'=>array(), + 'remote_execution_features'=>array(), + 'reports'=>array(), + 'roles'=>array(), + 'settings'=>array(), + 'smart_class_parameters'=>array(), + 'smart_proxies'=>array(), + 'smart_variables'=>array(), + 'statistics'=>array('methods'=>array('GET')), + 'status'=>array('methods'=>array('GET')), + 'subnets'=>array(), + 'template_combinations'=>array(), + 'template_kinds'=>array('methods'=>array('GET')), + 'templates'=>array(), + 'usergroups'=>array(), + 'users'=>array(), + // ... + ), + 'api/v2'=>array( + 'discovered_hosts'=>array(), + 'discovery_rules'=>array(), + ), + 'foreman_tasks/api'=>array( + 'recurring_logics'=>array(), + 'tasks'=>array(), + ), + ); + + + protected $_aRequest=array(); + + + // ---------------------------------------------------------------------- + // constructor + // ---------------------------------------------------------------------- + + + public function __construct($aCfg) { + if(!is_array($aCfg) || !count($aCfg) || !array_key_exists('api', $aCfg)){ + die("ERROR: class ".__CLASS__." must be initialized with an array containing api config for foreman."); + return false; + } + $this->_aCfg=$aCfg; + + return true; + } + + // ---------------------------------------------------------------------- + // private functions + // ---------------------------------------------------------------------- + /** + * add a log messsage + * @global object $oLog + * @param string $sMessage messeage text + * @param string $sLevel warnlevel of the given message + * @return bool + */ + protected function log($sMessage, $sLevel = "info") { + global $oCLog; + return $oCLog->add(basename(__FILE__) . " class " . __CLASS__ . " - " . $sMessage, $sLevel); + } + + /** + * search url prefix in $this->_aAllowedUrls by given key + * @param type $sFunction + * @return type + */ + protected function _guessPrefixUrl($sFunction=false){ + $sReturn=''; + /* + if (!$sFunction){ + $sFunction=$this->_aRequest['request'][0][0]; + } + * + */ + foreach($this->_aAllowedUrls as $sPrefix=>$aUrls){ + foreach(array_keys($aUrls) as $sKeyword){ + if ($sFunction==$sKeyword){ + $sReturn=$sPrefix; + break; + } + } + } + return $sReturn; + } + + /** + * generate an url for foreman API request based on option keys + * @return string + */ + protected function _generateUrl(){ + if(!array_key_exists('request', $this->_aRequest)){ + die('ERROR: missing key [request]'); + } + $sReturn=$this->_aCfg['api']; + + $sPrefix=$this->_guessPrefixUrl(); + $sReturn.=$sPrefix.'/'; + + foreach($this->_aRequest['request'] as $aReqItem){ + if (!array_key_exists($aReqItem[0], $this->_aAllowedUrls[$sPrefix])){ + echo 'WARNING: wrong item: [' . $aReqItem[0]."]<br>\n"; + } + $sReturn.=$aReqItem[0] ? $aReqItem[0].'/' : ''; + if(count($aReqItem)>1){ + $sReturn.=(int)$aReqItem[1].'/'; + } + } + return $sReturn; + } + + /** + * add parameter for search and paging in an foreman API URL + * + * @return string + */ + protected function _generateParams(){ + if (!array_key_exists('filter', $this->_aRequest)){ + return ''; + } + $sReturn='?'; + + foreach ($this->_aRequest['filter'] as $sKey=>$value){ + $sReturn.='&'.$sKey.'='.urlencode($value); + } + return $sReturn; + } + + /** + * make an http get request and return the response body + * it is called by _makeRequest + * $aRequest contains subkeys + * - url + * - method; one of GET|POST|PUT|DELETE + * - postdata; for POST only + * + * @param array $aRequest arrayurl for Foreman API + * @return string + */ + protected function _httpCall($aRequest=false, $iTimeout = 15) { + if ($aRequest){ + $this->_aRequest=$aRequest; + } + $this->log(__FUNCTION__ . " start <pre>".print_r($this->_aRequest,1)."</pre>"); + if (!function_exists("curl_init")) { + die("ERROR: PHP CURL module is not installed."); + } + + $sApiUser=array_key_exists('user', $this->_aCfg) ? $this->_aCfg['user'] : false; + $sApiPassword=array_key_exists('password', $this->_aCfg) ? $this->_aCfg['password'] : false; + + $sFullUrl=$sApiUrl.$this->_aRequest['url']; + $this->log(__FUNCTION__ . " ".$this->_aRequest['method']." " . $this->_aRequest['url']); + $ch = curl_init($this->_aRequest['url']); + + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->_aRequest['method']); + if ($this->_aRequest['method']==='POST'){ + curl_setopt($ch, CURLOPT_POSTFIELDS, $this->_aRequest['postdata']); + } + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + curl_setopt($ch, CURLOPT_TIMEOUT, $iTimeout); + curl_setopt($ch, CURLOPT_USERAGENT, 'IML Deployment GUI :: ' . __CLASS__); + if ($sApiUser){ + curl_setopt($ch, CURLOPT_USERPWD, $sApiUser.":".$sApiPassword); + } + + $res = curl_exec($ch); + $aReturn=array('info'=>curl_getinfo($ch), 'error'=>curl_error($ch)); + curl_close($ch); + $this->log(__FUNCTION__ . " status ".$aReturn['info']['http_code'].' '.$this->_aRequest['method']." $sFullUrl"); + + $sHeader=substr($res, 0, $aReturn['info']['header_size']); + $aReturn['header']=explode("\n", $sHeader); + $aReturn['body']=str_replace($sHeader, "", $res); + + return $aReturn; + } + + /** + * write debug infos if enabled + * @param string $sMessage + * @return boolean + */ + protected function _writeDebug($sMessage){ + if ($this->_bDebug){ + echo "DEBUG :: ".__CLASS__." :: $sMessage<br>\n"; + } + return true; + } + + // ---------------------------------------------------------------------- + // public functions :: low level + // ---------------------------------------------------------------------- + + /** + * make an http(s) request to foreman and scan result for http code and + * content in response json; method returns an array with subkeys + * - info: curl info array + * - error: curl error message + * - header: http response headers + * - body: http response body + * - _json: parsed json data from response body + * - _OK: flag if result is OK and complete + * - _status: info + * + * * $aRequest contains subkeys + * - function --> to extract method and generate url + * - method; one of GET|POST|PUT|DELETE + * - postdata; for POST only + + * @param array $aRequest arrayurl for Foreman API + * @return array + */ + public function makeRequest($aRequest=false) { + if ($aRequest){ + $this->_aRequest=$aRequest; + } + $sStatus='unknown'; + $bOk=false; + + + // prevent missing data because of paging + if ($this->_aRequest['method']==='GET' && !array_key_exists('per_page', $this->_aRequest['filter'])){ + $this->_aRequest['filter']['per_page']=1000; + } + // TODO check postdata + if ($this->_aRequest['method']==='POST' && (!array_key_exists('postdata',$this->_aRequest) || !count($this->_aRequest['postdata']))){ + die("ERROR in ".__CLASS__."::".__FUNCTION__.": missing data to make a POST request"); + } + if (!array_key_exists('url', $this->_aRequest)){ + $this->_aRequest['url']=$this->_generateUrl().$this->_generateParams(); + } + + // ----- request + $this->_writeDebug(__FUNCTION__ . ' start request <pre>'.print_r($this->_aRequest,1).'</pre>'); + $aReturn=$this->_httpCall(); + + // ----- check result + // check status + $iStatuscode=$aReturn['info']['http_code']; + if ($iStatuscode===0){ + $sStatus='wrong host'; + } + if ($iStatuscode>=200 && $iStatuscode<400){ + $sStatus='OK'; + $bOk=true; + } + if ($iStatuscode>=400 && $iStatuscode<500){ + $sStatus='error'; + } + if ($iStatuscode===404){ + $sStatus='wrong url'; + } + + // check result json + if($bOk){ + $aJson=json_decode($aReturn['body'], 1); + if (is_array($aJson)){ + if (array_key_exists('total', $aJson) && $aJson['total'] > $aJson['per_page']){ + $bOk=false; + $sStatus='Http OK, but incomplete results (paging)'; + } + $aReturn['_json']=$aJson; + } else { + $bOk=false; + $sStatus='Http OK, but wrong response'; + } + } + $aReturn['_OK']=$bOk; + $aReturn['_status']=$sStatus; + $this->_writeDebug(__FUNCTION__ . ' result of request <pre>'.print_r($aReturn,1).'</pre>'); + + return $aReturn; + } + + /** + * filter output for the response based on values $this->_aRequest['response'] + * @param array $aData response of $this->makeRequest(); + * @return array + */ + protected function _filterOutput($aData){ + if(!array_key_exists('response', $this->_aRequest)){ + return $aData; + } + $bIsList=array_key_exists('results', $aData['_json']); + $aTmp=$bIsList ? $aData['_json']['results'] : array($aData['_json']); + if(!count($aTmp)){ + return array(); + } + $aReturn=array(); + foreach($aTmp as $aItem){ + $aReturn[] = array_filter($aItem, function($key) { + return array_search($key, $this->_aRequest['response']) !==false; + }, ARRAY_FILTER_USE_KEY + ); + } + return ($bIsList==1) + ? $aReturn + : (count($aReturn) ? $aReturn[0] : array()); + } + + + // ---------------------------------------------------------------------- + // public foreman functions + // ---------------------------------------------------------------------- + + /** + * enable/ disable debugging + * @param boolean $bNewDebugFlag new value; true|false + * @return boolean + */ + public function setDebug($bNewDebugFlag){ + return $this->_bDebug=$bNewDebugFlag; + } + + /** + * check for missing config entries + * @return type + */ + public function selfcheck() { + $sOut=''; + $sWarning=''; + $sOut.="<h1>selfcheck</h1>"; + $aApi=$this->read(array('request'=>array(array('')))); + if($aApi['_OK']){ + foreach($aApi['_json']['links'] as $sKey=>$aCalls){ + $sOut.="<h2>$sKey</h2><ul>"; + foreach ($aCalls as $sLabel=>$sUrl){ + $sOut.="<li>$sLabel .. $sUrl "; + $aTmp=preg_split('#\/#', $sUrl); + $sDir2=count($aTmp)>2 ? $aTmp[2] : '??'; + $sDir3=count($aTmp)>3 ? $aTmp[3] : '??'; + $sOut.="..... " + . ($this->_guessPrefixUrl($sDir2).$this->_guessPrefixUrl($sDir3) + ?'<span style="background:#cfc">OK</span>' + :'<span style="background:#fcc">miss</span>' + ) . ' ' . $sDir2.', '.$sDir3 . "</li>\n"; + if (!($this->_guessPrefixUrl($sDir2).$this->_guessPrefixUrl($sDir3))){ + $sWarning.="<li>$sKey - $sLabel - $sUrl</li>"; + } + } + $sOut.="</ul>"; + } + } else { + $sOut.='ERROR: unable to connect to foreman or missing permissions.<br>'; + } + if ($sWarning){ + echo 'WARNINGS:<ol>'.$sWarning.'</ol>'; + } + echo $sOut; + return true; + } + + // ---------------------------------------------------------------------- + // public foreman API CRUD functions + // ---------------------------------------------------------------------- + + /** + * TODO: create + * @param array $aOptions + */ + public function create($aOptions){ + /* + $this->_aRequest=$aOptions; + $this->_aRequest['method']='POST'; + return $this->makeRequest(); + */ + } + /** + * GETTER + * $aOptions ... can contain optional subkeys + * - request + * [] list of array(keyword [,id]) + * - filter (array) + * - search (string) + * - page (string) + * - per_page (string) + * - any attribute in the return resultset + * - response (array) + * - list of keys, i.e. array('id', 'title') + * + * @param array $aOptions + * @return array + */ + public function read($aOptions){ + $this->_aRequest=$aOptions; + $this->_aRequest['method']='GET'; + $aData=$this->makeRequest(); + if (!$aData['_OK']){ + return false; + } + return $this->_filterOutput($aData); + } + + /** + * TODO + * @param type $aOptions + */ + public function update($aOptions){ + /* + $this->_aRequest=$aOptions; + $this->_aRequest['method']='PUT'; + return $this->makeRequest(); + */ + } + + /** + * TODO + * @param type $aOptions + */ + public function delete($aOptions){ + /* + $this->_aRequest=$aOptions; + $this->_aRequest['method']='DELETE'; + return $this->makeRequest(); + */ + } + +} diff --git a/public_html/deployment/classes/htmlguielements.class.php b/public_html/deployment/classes/htmlguielements.class.php index 8c9e7afe99eee9f33b78b2b3a41207d762c47acf..35de3547ca79758062f0af3d2fb2f1c7b07790a5 100644 --- a/public_html/deployment/classes/htmlguielements.class.php +++ b/public_html/deployment/classes/htmlguielements.class.php @@ -55,10 +55,23 @@ class htmlguielements{ ), 'icons'=>array( + 'workflow'=>'fa-angle-double-right', + 'repository'=>'fa-database', + 'phase'=>'fa-flag', + 'package'=>'fa-cubes', + 'version'=>'fa-tag', + 'list'=>'fa-list', + 'branch'=>'glyphicon-bookmark', 'calendar'=>'glyphicon-calendar', 'comment'=>'glyphicon-comment', 'revision'=>'glyphicon-tag', + + 'host'=>'fa-hdd-o', + 'hostgroup'=>'fa-sitemap', + 'templatefile'=>'fa-file-code-o', + 'targetfile'=>'fa-file-o', + 'replace'=>'fa-random', ), ); @@ -94,6 +107,20 @@ class htmlguielements{ return ($sValue ? ' '.$sAttribute.'="'.$sValue.'"' : '' ); } + /** + * get html attributes as string from all keys of given hash + * + * @param array $aItem + * @return string + */ + public function addAllAttributes($aItem){ + $sReturn=''; + foreach (array_keys($aItem) as $sKey){ + $sReturn.=$this->addAttributeFromKey($sKey, $aItem); + } + return $sReturn; + } + // ---------------------------------------------------------------------- // low level // ---------------------------------------------------------------------- @@ -113,7 +140,7 @@ class htmlguielements{ : ( strpos($sLabel, 'fa-')===0 ? 'fa' : '') ); if(!$sPrefix){ - return ''; + return $this->getIconByType($sLabel); } return '<i'.$this->addAttribute('class', ($sPrefix ? $sPrefix . ' ' : '').$sLabel).'></i> '; } @@ -149,9 +176,7 @@ class htmlguielements{ } $sReturn='<a'.$sHref; - foreach (array_keys($aItem) as $sKey){ - $sReturn.=$this->addAttributeFromKey($sKey, $aItem); - } + $sReturn.=$this->addAllAttributes($aItem); $sReturn.='>' .$sLabel .'</a>'; @@ -159,7 +184,9 @@ class htmlguielements{ } /** - * add default css classes and colors based on $aItem['type'] + * add default css classes and colors based on $aItem['type'] and the + * local default settings in $this->aCfg + * * @param array $aItem * @return array */ @@ -269,5 +296,35 @@ class htmlguielements{ . '<div class="tab-content">'.$sContent.'</div>' . '</div>'; } + + /** + * get html code for a table + * + * @param array $aTabledata array with subkeys "header" and "body" + * @return string + */ + public function getTable($aTabledata) { + $sTHead=''; + $sTBody=''; + if (array_key_exists('body', $aTabledata)){ + foreach ($aTabledata['body'] as $aRow){ + $sTBody.='<tr>'; + foreach ($aRow as $sItem){ + $sTBody.='<td>'.$sItem.'</td>'; + } + $sTBody.='</tr>'; + } + } + if (array_key_exists('header', $aTabledata)){ + foreach ($aTabledata['header'] as $sItem){ + $sTHead.='<th>'.$sItem.'</th>'; + } + } + return '<table class="table" style="width: auto;">' + .($sTHead ? '<thead>'.$sTHead.'</thead>' : '') + .($sTBody ? '<tbody>'.$sTBody.'</tbody>' : '') + .'</table>' + ; + } } diff --git a/public_html/deployment/classes/project.class.php b/public_html/deployment/classes/project.class.php index 03a6e5d27b5db964d207875dae9c40907b39ec57..e84a6c89c502aadba514e03cb2b91def243365c4 100644 --- a/public_html/deployment/classes/project.class.php +++ b/public_html/deployment/classes/project.class.php @@ -1,5 +1,8 @@ <?php +define("OPTION_DEFAULT", -999); +define("OPTION_NONE", -1); + require_once 'base.class.php'; require_once 'htmlguielements.class.php'; @@ -2291,6 +2294,8 @@ class project extends base { private function _renderHostsData($aData) { $sReturn = ''; if (array_key_exists('_hosts', $aData)) { + $oHtml = new htmlguielements(); + // $sReturn.= print_r($aData['_hosts'], 1); $sReturn.= '<div class="hosts">' . '<br><strong>' . t('hosts') . ':</strong><br>' @@ -2302,7 +2307,7 @@ class project extends base { $sReturn.= '<div class="host">' . $this->_getChecksumDiv($aHostinfos['_data']['revision']) - . '<i class="fa fa-cube"></i><br>' + . $oHtml->getIcon('host').'<br>' . $sHostname . "<br>($sAge) " . '</div>' ; @@ -2942,19 +2947,14 @@ class project extends base { $sMessages = ''; require_once ("formgen.class.php"); - require_once("./classes/config-replacement.class.php"); - $oConfig = new configreplacement(); - $oConfig->setProject($this->_aConfig["id"]); - - $aForemanHostgroups = false; $iForemanHostgroupDefault = false; $sForemanHostgroupDefault = false; if (array_key_exists('foreman', $this->_aConfig)) { // echo '<pre>' . print_r($this->_aPrjConfig, 1) . '</pre>'; $iForemanHostgroupDefault = (int) $this->_aPrjConfig['deploy']['foreman']['hostgroup']; - require_once('deploy-foreman.class.php'); - $oForeman = new deployForeman($this->_aConfig['foreman']); + require_once('foremanapi.class.php'); + $oForeman = new ForemanApi($this->_aConfig['foreman']); // $oForeman->setDebug(1); // $oForeman->selfcheck(); die(__FUNCTION__); @@ -2972,7 +2972,7 @@ class project extends base { 'name' => 'deploy[foreman][hostgroup]', 'label' => t("foreman-hostgroup"), 'options' => array( - '-1' => array( + OPTION_NONE => array( 'label' => t('none'), ), '' => array( @@ -3053,9 +3053,9 @@ class project extends base { 'type' => 'markup', 'value' => '<div class="tabbable"> <ul class="nav nav-tabs"> - <li class="active"><a href="#tab1" data-toggle="tab">' . t('setup-metadata') . '</a></li> - <li><a href="#tab2" data-toggle="tab">' . t('build') . '</a></li> - <li><a href="#tab3" data-toggle="tab">' . t('phases') . '</a></li> + <li class="active"><a href="#tab1" data-toggle="tab">' . $oHtml->getIcon('list').t('setup-metadata') . '</a></li> + <li><a href="#tab2" data-toggle="tab">' . $oHtml->getIcon('repository').t('repositoryinfos') . '</a></li> + <li><a href="#tab3" data-toggle="tab">' . $oHtml->getIcon('phase').t('phases') . '</a></li> <!-- <li><a href="#tab3" data-toggle="tab">' . t('deployment') . '</a></li> --> @@ -3165,7 +3165,7 @@ class project extends base { if ($aSelectForemanGroups) { $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', - 'value' => '<h3>Default</h3>', + 'value' => '<strong>'.t("defaults-all-phases").'</strong><br><br>', ); $aForms["setup"]["form"]['input' . $i++] = $aSelectForemanGroups; $aForms["setup"]["form"]['input' . $i++] = array( @@ -3191,13 +3191,13 @@ class project extends base { 'name' => 'phases[' . $sPhase . '][foreman-hostgroup]', 'label' => t("foreman-hostgroup"), 'options' => array( - '-999' => array( + OPTION_DEFAULT => array( 'label' => t('default') . ' (' . $sForemanHostgroupDefault . ')', - 'selected' => $iForemanHostgroup === -999 ? 'selected' : false, + 'selected' => $iForemanHostgroup === OPTION_DEFAULT ? 'selected' : false, ), - '-1' => array( + OPTION_NONE => array( 'label' => t('none'), - 'selected' => $iForemanHostgroup === -1 ? 'selected' : false, + 'selected' => $iForemanHostgroup === OPTION_NONE ? 'selected' : false, ), '' => array( 'label' => '- - - - - - - - - - - - - - - - - - - - ', @@ -3251,10 +3251,6 @@ class project extends base { 'value' => '' . '<div id="' . $sDivId4PhaseSettings . '" ' . ($bActivePhase ? '' : ' style="display: none;"') . '">' ); - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '<div style="clear: both"></div><div class="form-group"><h3>' . t("deploy-settings") . '</h3></div>' - ); $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'text', 'name' => 'phases[' . $sPhase . '][url]', @@ -3359,83 +3355,6 @@ class project extends base { if ($aSelectForemanGroups) { $aForms["setup"]["form"]['input' . $i++] = $aSelectForemanHostGroup; } - $aReplacements = $oConfig->getReplacements($sPhase); - $sDivIdReplacement = 'divreplacements-' . $sPhase; - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '<div style="clear: both; height: 2em;"></div>' - . '<div class="form-group">' - . ($aReplacements ? '<a class="expandable closed" href="#" onclick="$(\'#' . $sDivIdReplacement . '\').slideToggle(); $(this).toggleClass(\'closed\'); return false;">' : '' - ) - . '<h3>' . t("replacements") . ' (' . ($aReplacements ? count($aReplacements) : 0) . ')</h3>' - . ($aReplacements ? '</a>' : '') - . t('replacements-info') - . '</div>' - ); - - - if ($aReplacements) { - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '<div id="' . $sDivIdReplacement . '" style="display: none;">' - ); - foreach ($aReplacements as $sFile => $aFields) { - $tTplFile = basename($sFile); - $aValues = (array_key_exists("replace", $this->_aPrjConfig["phases"][$sPhase]) && array_key_exists($tTplFile, $this->_aPrjConfig["phases"][$sPhase]["replace"]) - ) ? $this->_aPrjConfig["phases"][$sPhase]["replace"][$tTplFile] : false; - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '<div class="form-group"><br><h4><i class="fa fa-file-code-o"></i> ' . $tTplFile . '</h4>' - // . '<textarea cols="100" rows="7" >'.file_get_contents($sFile).'</textarea>' - . '</div>' - ); - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'text', - 'name' => 'phases[' . $sPhase . '][replace][' . $tTplFile . '][targetfile]', - 'label' => t("replacement-targetfile"), - 'value' => $aValues && array_key_exists('targetfile', $aValues) ? $aValues['targetfile'] : '', - // 'required' => 'required', - 'validate' => 'isastring', - 'size' => 100, - 'placeholder' => strip_tags(t("replacement-targetfile")), - ); - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '<br>' . t("replacement-fields") - ); - if (count($aFields)) { - foreach ($aFields as $sField) { - - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'text', - 'disabled' => $this->oUser->hasPermission("project-action-setup-edit-replacements") ? '' : 'disabled', - 'name' => 'phases[' . $sPhase . '][replace][' . $tTplFile . '][' . $sField . ']', - 'label' => $sField, - 'value' => $aValues && array_key_exists($sField, $aValues) ? $aValues[$sField] : '', - // 'required' => 'required', - 'validate' => 'isastring', - 'size' => 100, - 'placeholder' => $sField, - ); - } - } else { - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '<br>' . $oHtml->getBox("error", t("replacement-fields-not-found")) - // . '<pre>'.print_r($aValues, 1).'</pre>' - ); - } - } - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '</div>' - ); - } else { - $aForms["setup"]["form"]['input' . $i++] = array( - 'type' => 'markup', - 'value' => '<div class="form-group"><h4>' . t("none") . '</h4></div>' - ); - } $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', diff --git a/public_html/deployment/main.css b/public_html/deployment/main.css index 342dc40172d9fd7b0910a0f25b699b68aef26434..4ba3bc08a7b8c749a02e261b64e967a0e74c6741 100644 --- a/public_html/deployment/main.css +++ b/public_html/deployment/main.css @@ -238,6 +238,8 @@ input[type="radio"]:checked+label, input[type="checkbox"]:checked+label{ .visualprocess .process .title{margin-bottom: 2em; font-weight: bold; font-size: 150%; color:#aaa;} .visualprocess .process .details{background: #fff; min-height: 6em;} +/* ----- replacemets with templates ----- */ +span.replace{background:#fea; font-weight: bold;} /* ----- log table ----- */ .loglevel-info{} diff --git a/public_html/deployment/pages/act_build.php b/public_html/deployment/pages/act_build.php index 9fbeee728a192211fbdbf7502b53fbeaa5eb5ee0..996de19faa9b3eb9f29caa7529950ef6a86ec2ea 100644 --- a/public_html/deployment/pages/act_build.php +++ b/public_html/deployment/pages/act_build.php @@ -57,9 +57,9 @@ if (!array_key_exists("confirm", $aParams)) { <table> <thead> <tr> - <th class="versioncontrol" colspan="2">' . t("versioncontrol") . '</th> + <th class="versioncontrol" colspan="2">' . $oHtml->getIcon('repository').t("versioncontrol") . '<br></th> <th> </th> - <th class="' . $sNext . '" colspan="2">' . $sNext . '</th> + <th class="' . $sNext . '" colspan="2">' . $oHtml->getIcon('phase').$sNext . '</th> </tr> </thead> <tbody> diff --git a/public_html/deployment/pages/act_htmltest.php b/public_html/deployment/pages/act_htmltest.php index 51ac13853360d4c169b5862836f018321b043d94..3188f31ceaee2d260204a4d9c95bd33f22e6ee99 100644 --- a/public_html/deployment/pages/act_htmltest.php +++ b/public_html/deployment/pages/act_htmltest.php @@ -96,6 +96,16 @@ $sRows='' ), ) );") + . addHtmltestTest("Tabelle", "\$oHtml->getTable( + array( + 'header'=>array('A', 'B'), + 'body'=>array( + array('Zelle A 1', 'Zelle B 1'), + array('Zelle A 2', 'Zelle B 2'), + array('Zelle A 3', 'Zelle B 3'), + ), +));" +) ; // ---------------------------------------------------------------------- diff --git a/public_html/deployment/pages/act_overview.php b/public_html/deployment/pages/act_overview.php index 48ccb45ac70c8f4ef775d7f6f43a3a013e760f0c..8bff0dd4ec78ddbfade22fbce36ca92fc5e0062b 100644 --- a/public_html/deployment/pages/act_overview.php +++ b/public_html/deployment/pages/act_overview.php @@ -47,20 +47,20 @@ if (!array_key_exists("prj", $aParams)) { <br> <ul class="nav nav-tabs"> - <li class="active"><a href="#tab1" data-toggle="tab">' . t("way-of-packages") . '</a></li> - <li><a href="#tab2" data-toggle="tab">' . t("repositoryinfos") . '</a></li> - <li><a href="#tab3" data-toggle="tab">' . t("packages") . '</a></li> - <li><a href="#tab4" data-toggle="tab">' . t('phases') . '</a></li> + <li class="active"><a href="#tab1" data-toggle="tab">' . $oHtml->getIcon('workflow') . t("way-of-packages") . '</a></li> + <li><a href="#tab2" data-toggle="tab">' . $oHtml->getIcon('repository') . t("repositoryinfos") . '</a></li> + <li><a href="#tab3" data-toggle="tab">' . $oHtml->getIcon('package') . t("packages") . '</a></li> + <li><a href="#tab4" data-toggle="tab">' . $oHtml->getIcon('phase') . t('phases') . '</a></li> </ul> <br> <div class="tab-content"> <div class="tab-pane active" id="tab1"> ' - . '<h3 id="h3visual">' . t("way-of-packages") . '</h3> + . '<h3 id="h3visual">' . $oHtml->getIcon('workflow') . t("way-of-packages") . '</h3> ' . $oPrj->renderVisual() . ' </div> <div class="tab-pane" id="tab2"> - <h3 id="h3repo">' . t("repositoryinfos") . '</h3> + <h3 id="h3repo">' . $oHtml->getIcon('repository') . t("repositoryinfos") . '</h3> <div style="max-width: 40em;"> ' . $oPrj->renderRepoInfo() . ' ' . $sListOfBranches . ' @@ -69,13 +69,13 @@ if (!array_key_exists("prj", $aParams)) { </div> <div class="tab-pane" id="tab3"> - <h3 id="h3versions">' . t("packages") . '</h3> + <h3 id="h3versions">' . $oHtml->getIcon('package') . t("packages") . '</h3> ' . $oPrj->renderVersionUsage() .' </div> <div class="tab-pane" id="tab4"> - <h3 id="h3phases">' . t("phases") . '</h3> + <h3 id="h3phases">' . $oHtml->getIcon('phase') . t("phases") . '</h3> '.($oPrj->getActivePhases() ? '<p>' . t("page-overview-phase-infos") diff --git a/public_html/deployment/pages/act_phase.php b/public_html/deployment/pages/act_phase.php index c93965e3c38f98e5187a7143348b80cccccb75ac..24d0013dcbe3c697710a12e1068a0052b1ed24b4 100644 --- a/public_html/deployment/pages/act_phase.php +++ b/public_html/deployment/pages/act_phase.php @@ -13,6 +13,7 @@ require_once("./classes/project.class.php"); require_once("./inc_functions.php"); +require_once("./classes/config-replacement.class.php"); // --- Checks @@ -24,11 +25,201 @@ if (array_key_exists("par3", $aParams)) { } if ($sPhase) { - $sPhase = $aParams["par3"]; - // $sFirst = $oPrj->getNextPhase(); + $aWarnings=array(); + $sOutReplace=''; + + // ---------------------------------------------------------------------- + // replacement + // ---------------------------------------------------------------------- + + $oConfig = new configreplacement(); + $oConfig->setProject($aParams["prj"], $sPhase); + + $aReplacements=$oConfig->getReplacements(); + + $sOut.='<h3>' . $oHtml->getIcon('replace') . t("replacements") . '</h3>'; + + // ---------------------------------------------------------------------- + // Links to foreman + // ---------------------------------------------------------------------- + $aForeman=$oConfig->getForemanReplacements(); + $aReplacementsForeman=$aForeman ? $aForeman['rules'] : false; + + // echo '<pre>$aReplacements = '.print_r($aReplacements, 1) . '</pre>'; + // echo '<pre>$aForeman = '.print_r($aForeman, 1) . '</pre>'; + + $sOut.=($aReplacementsForeman + ? $oConfig->getForemanlink2Hostgroup().' ' + .$oConfig->getForemanlink2Host() + : t('foreman-no-host') + ).'<br><br>' + ; + + // ---------------------------------------------------------------------- + // Loop over files + // ---------------------------------------------------------------------- + + if ($aReplacements) { + + // open all/ close all + if(count($aReplacements)>1){ + $sOutReplace.=$oHtml->getLinkButton(array( + 'onclick'=>'$(\'.divfileinfos\').slideDown(); $(\'.expandable\').removeClass(\'closed\'); this.blur(); return false;', + 'icon'=>'fa-chevron-down', + )) + .$oHtml->getLinkButton(array( + 'onclick'=>'$(\'.divfileinfos\').slideUp(); $(\'.expandable\').addClass(\'closed\'); this.blur(); return false;', + 'icon'=>'fa-chevron-up', + )) + ; + } + + foreach ($aReplacements as $sFile => $aFields) { + $tTplFile = basename($sFile); + $bFileInForeman = $aReplacementsForeman && array_key_exists($tTplFile, $aReplacementsForeman); + $sDivIdFile='div4file-'.md5($sFile); + + $sOutReplace.='<h4>' . + $oHtml->getLink(array( + 'onclick'=>'$(\'#'.$sDivIdFile.'\').slideToggle(); $(this).toggleClass(\'closed\'); return false;', + 'class'=>'expandable closed', + 'icon'=>'templatefile', + 'label'=>$tTplFile, + )) . '</h4>' + . '<div id="'.$sDivIdFile.'" class="divfileinfos" style="display: none;">' + ; + + // --- check: does this file in foreman exist? + if (!array_key_exists($tTplFile, $aReplacementsForeman)){ + if ($aReplacementsForeman){ + $sOutReplace.=$oHtml->getBox('error', t('foreman-error-template-unknown')); + $aWarnings[]=$tTplFile.': '.t('foreman-error-template-unknown'); + } + } + + + // --- check: target file was set? + $aTable=array('header'=>array(), 'body'=>array()); + if ($bFileInForeman){ + if ($aReplacementsForeman && array_key_exists('target', $aReplacementsForeman[$tTplFile])){ + $sTd='<strong>'.$oHtml->getIcon('targetfile') . $aReplacementsForeman[$tTplFile]['target'].'</strong>'; + } else { + if ($bFileInForeman){ + $sTd=t('foreman-error-no-target'); + $aWarnings[]=$tTplFile.': '.t('foreman-error-no-target'); + } else { + $sTd='-'; + } + } + $aTable['body'][]=array( + $oHtml->getIcon('templatefile') . t('foreman-targetfile'), + $sTd + ); + } + + // --- loop over all replacement items of template file + // and check if they exist in foreman + foreach ($aFields as $sField){ + if ($aReplacementsForeman && array_key_exists($sField, $aReplacementsForeman[$tTplFile]['replace'])){ + $sTd=$aReplacementsForeman[$tTplFile]['replace'][$sField]; + } else { + if ($bFileInForeman){ + $sTd=$oHtml->getBox('error', sprintf(t('foreman-error-no-replacement-for-id'), $sField)); + $aWarnings[]=$tTplFile.': '.sprintf(t('foreman-error-no-replacement-for-id'), $sField); + } else { + $sTd='-'; + } + + } + $aTable['body'][]=array( + $oHtml->getIcon('replace') . $sField, + $sTd + ); + } + + // --- reverse check ... loop over all replacement items of foreman + // and check if they exist in template + if ($aReplacementsForeman){ + // for testing: create a testvalue + // $aReplacementsForeman[$tTplFile]['replace']['testentry']='dummy value'; + foreach ($aReplacementsForeman[$tTplFile]['replace'] as $sField=>$sValue){ + if (array_search($sField, $aFields)===false){ + $aTable['body'][]=array( + $oHtml->getIcon('replace') . + $oHtml->getBox('error', sprintf(t('foreman-error-replacement-unknown'), $sField)), + $sValue + ); + $aWarnings[]=$tTplFile.': '. sprintf(t('foreman-error-replacement-unknown'), $sField); + } + } + } + + // --- filecontent of template + $ContentFile='<br>'.$sFile.'<br><br>' + .'<pre style="max-height:35em;">' + . preg_replace('/(@replace\[.*\])/U', '<span class="replace">${1}</span>' , htmlentities(file_get_contents($sFile))) + .'</pre>'; + + // --- output with tabs for a template file + $sOutReplace.=$oHtml->getNav( + array( + 'options'=>array('type'=>'tabs'), + 'tabs'=>array( + $oHtml->getIcon('list') . t('replacement-fields') => '<br>'.$oHtml->getTable($aTable), + $oHtml->getIcon('templatefile') . $tTplFile => $ContentFile, + ) + ) + ) + .'<br></div>'; + } + } else { + $sOutReplace.=t('none'); + } + + + // --- reverse check - do templates in foreman physically exist? + // for testing: create a testfile + // $aReplacementsForeman['dummytemplate.erb']=array(); + if($aReplacementsForeman){ + foreach(array_keys($aReplacementsForeman) as $sForemanTemplate){ + + $bFound=false; + foreach (array_keys($aReplacements) as $sFile) { + if (basename($sFile)===$sForemanTemplate){ + $bFound=true; + continue; + } + } + if (!$bFound){ + $aWarnings[]=sprintf(t('foreman-error-missing-template'), $sForemanTemplate); + $sDivIdFile='div4file-'.md5($sForemanTemplate); + + $sOutReplace.='<h4>' . + $oHtml->getLink(array( + 'onclick'=>'$(\'#'.$sDivIdFile.'\').slideToggle(); $(this).toggleClass(\'closed\'); return false;', + 'class'=>'expandable closed', + 'icon'=>'fa-warning', + 'label'=>$sForemanTemplate, + )) . '</h4>' + . '<div id="'.$sDivIdFile.'" class="divfileinfos" style="display: none;">' + .$oHtml->getBox('error', sprintf(t('foreman-error-missing-template'), $sForemanTemplate)) + . '</div>' + ; + + } + } + } + if (count($aWarnings)){ + $sOut.=$oHtml->getBox('warning', '<ul><li>'.implode('<li>', $aWarnings).'</ul>'); + } + $sOut.=$sOutReplace; + + // ---------------------------------------------------------------------- + // versions + // ---------------------------------------------------------------------- $sOut.=' - <h3>' . t("versions") . '</h3> + <h3>' . $oHtml->getIcon('version').t("versions") . '</h3> <table> <thead> <tr> @@ -42,10 +233,15 @@ if ($sPhase) { </tr> </tbody> </table> - ' + '; + + // ---------------------------------------------------------------------- + // list of phases + // ---------------------------------------------------------------------- + // show all phases if there are more than one - . (count($oPrj->getActivePhases())>1 - ? '<h3>' . t("phases") . '</h3>' . $oPrj->renderPhaseInfo() + $sOut.=(count($oPrj->getActivePhases())>1 + ? '<h3>' . $oHtml->getIcon('phase').t("phases") . '</h3>' . $oPrj->renderPhaseInfo() : '' ) ;