<?php define("OPTION_DEFAULT", -999); define("OPTION_NONE", -1); require_once 'base.class.php'; require_once 'htmlguielements.class.php'; require_once 'messenger.class.php'; /* ###################################################################### IML DEPLOYMENT class project for all actions for single project * actions * rendering html --------------------------------------------------------------------- 2013-11-08 Axel <axel.hahn@iml.unibe.ch> ###################################################################### */ /** * class for single project */ // class project { class project extends base { // ---------------------------------------------------------------------- // CONFIG // ---------------------------------------------------------------------- /** * configuration ($aConfig in the config file) * @var array */ private $_aConfig = array(); /** * configuration of the project (= $aProjects[ID] in the config file) * @var array */ private $_aPrjConfig = array(); /** * version infos of all phases * @var array */ private $_aData = array(); /** * existing branches and tags * @var array */ private $_aVcsBranches = array(); /** * existing versions in the archive dir * @var array */ private $_aVersions = array(); /** * output file to fetch processing content with ajax request * @var string */ private $_sProcessTempOut = false; /** * places of version infos in each deployment phase * @var array */ private $_aPlaces = array( "onhold" => "Queue", "ready2install" => "Puppet", "deployed" => "Installiert", ); /** * collector for returncodes of multiple exec calls * @var int */ private $_iRcAll = 0; /** * object to access a version control, .e. git * @var type */ private $_oVcs = false; private $_sBranchname = false; /** * send messages * @var messengerobject */ protected $oMessenger = false; // ---------------------------------------------------------------------- // constructor // ---------------------------------------------------------------------- /** * constructor * @param string $sId id of the project */ public function __construct($sId = false) { $this->oUser = new user(); $this->_readConfig(); if ($sId) { $this->setProjectById($sId); } } // ---------------------------------------------------------------------- // private functions // ---------------------------------------------------------------------- /** * add a log messsage * @global object $oLog * @param string $sMessage messeage text * @param string $sLevel warnlevel of the given message * @return bool */ private function log($sMessage, $sLevel = "info") { global $oCLog; return $oCLog->add(basename(__FILE__) . " class " . __CLASS__ . " - " . $sMessage, $sLevel); } /** * send info messages to project specific targets (Slack, E-Mail) * @param string $sMessage * @return boolean */ private function _sendMessage($sMessage){ $aConfig=array(); if (array_key_exists('messenger', $this->_aPrjConfig) && array_key_exists('slack', $this->_aPrjConfig['messenger']) ){ $sSlack=$this->_aPrjConfig['messenger']['slack']; $aConfig['slack']=array('incomingurl'=>$sSlack); foreach(array('user', 'icon') as $sKey){ if (array_key_exists($sKey, $this->_aConfig['messenger']['slack']['presets'][$sSlack])){ $aConfig['slack'][$sKey]=$this->_aConfig['messenger']['slack']['presets'][$sSlack][$sKey]; } } } if (array_key_exists('messenger', $this->_aPrjConfig) && array_key_exists('email', $this->_aPrjConfig['messenger']) && $this->_aPrjConfig['messenger']['email'] ){ $aConfig['email']=$this->_aConfig['messenger']['email']; $aConfig['email']['to']=$this->_aPrjConfig['messenger']['email']; } if(!count($aConfig)){ return false; } // init on first usage if (!$this->oMessenger){ $this->oMessenger=new messenger($aConfig); } // add some metadata to the message body $sText=$this->getLabel().': '.html_entity_decode($sMessage)."\n" . t('page-login-username'). ": ".$this->oUser->getUsername()."\n" ; if (isset($_SERVER) && is_array($_SERVER)) { if(array_key_exists('HTTP_HOST', $_SERVER)){ $sText.= t('project-home').': '.$_SERVER['REQUEST_SCHEME'].'://'. $_SERVER['HTTP_HOST'].'/deployment/'.$this->getId()."\n"; } /* if(array_key_exists('HTTP_ORIGIN', $_SERVER)){ $sText.= t('project-home').": <".$_SERVER['HTTP_ORIGIN'].'/deployment/'.$this->getId()."/>\n"; } */ } return $this->oMessenger->sendMessage($sText); } /** * read default config file * @return boolean */ private function _readConfig() { global $aConfig; $this->_aConfig = $aConfig; return true; } /** * validate config data * @return boolean */ private function _verifyConfig() { if (!count($this->_aPrjConfig)) die(t("class-project-error-no-config")); if (!array_key_exists("packageDir", $this->_aConfig)) { die(t("class-project-error-no-packagedir")); } if (!$this->_aConfig["packageDir"]) { die(t("class-project-error-packagedir-empty")); } if (!file_exists($this->_aConfig["packageDir"])) { die(sprintf(t("class-project-error-packagedir-does-not-exist"), $this->_aConfig['packageDir'])); } if (!array_key_exists("archiveDir", $this->_aConfig)) { die(t("class-project-error-no-archivedir")); } if (!$this->_aConfig["archiveDir"]) { die(t("class-project-error-archivedir-empty")); } if (!file_exists($this->_aConfig["archiveDir"])) { die(sprintf(t("class-project-error-packagedir-does-not-exist"), $this->_aConfig['archiveDir'])); } foreach (array("fileprefix", "build", "phases") as $sKey) { if (!array_key_exists($sKey, $this->_aPrjConfig)) { die(sprintf(t("class-project-error-missing-prjkey"), $sKey, print_r($this->_aPrjConfig, true))); } } // TODO: verify ausbauen /* if (!$this->_aConfig["dataDir"]) { die(t("class-project-error-datadir-empty")); } if (!file_exists($this->_aConfig["dataDir"])) { die(sprintf(t("class-project-error-data-does-not-exist"), $this->_aConfig['dataDir'])); } foreach (array("database", "projects", "sshkeys") as $sKey) { $sTestDir=$this->_aConfig["dataDir"]."/$sKey"; if (!file_exists($sTestDir)) { mkdir($sTestDir); // die(sprintf(t("class-project-error-missing-prjkey"), $sKey, print_r($this->_aPrjConfig, true))); } } */ return true; } /** * execute a commandline; returns a string of output of timestamp, command, output and returncode * @param string $sCommand * @return string */ private function _execAndSend($sCommand, $bFlush = false) { $this->log(__FUNCTION__ . " start"); $sReturn = ''; $bUseHtml = $_SERVER ? true : false; if ($bFlush) { ob_implicit_flush(true); } // ob_end_flush(); // stderr ausgeben $sCommand.=' 2>&1'; $sReturn.="[" . date("H:i:s d.m.Y") . "] "; $sReturn.=$bUseHtml ? "<strong>$sCommand</strong>" : "$sCommand"; $sReturn.=$bUseHtml ? "<br>" : "\n"; $sOutput = false; $this->log(__FUNCTION__ . " start $sCommand"); exec($sCommand, $aOutput, $iRc); $this->log(__FUNCTION__ . " ended command $sCommand"); $sReturn.=(count($aOutput)) ? htmlentities(implode("\n", $aOutput)) . "\n" : ""; /* $descriptorspec = array( 0 => array("pipe", "r"), // stdin is a pipe that the child will read from 1 => array("pipe", "w"), // stdout is a pipe that the child will write to 2 => array("pipe", "w") // stderr is a pipe that the child will write to ); if ($bFlush) { flush(); } $process = proc_open($sCommand, $descriptorspec, $pipes, realpath('./'), array()); $sErrors = false; if (is_resource($process)) { while ($s = fgets($pipes[1])) { $sReturn.=$s; if ($bFlush) { flush(); } } while ($s = fgets($pipes[2])) { $sErrors.=$s; if ($bFlush) { flush(); } } } if ($sErrors) { $sReturn.="STDERR:\n" . $sErrors; } $oStatus = proc_get_status($process); $iRc = $oStatus['exitcode']; */ if ($iRc != 0) { $this->_logaction("command failed: $sCommand - rc=" . $iRc, __FUNCTION__, "error"); } $this->_iRcAll += $iRc; $sReturn.="[" . date("H:i:s d.m.Y") . "] " . t("exitcode") . " " . $iRc; if ($bUseHtml) { if ($iRc == 0) { $sReturn = '<pre class="cli">' . $sReturn; } else { $sReturn = '<pre class="cli error">' . $sReturn; } $sReturn.='</pre>'; } if ($bFlush) { flush(); } return $sReturn; } /** * add an action log message * @param string $sMessage message * @param string $sAction project action * @param string $sLoglevel loglevel */ private function _logaction($sMessage, $sAction = "", $sLoglevel = "info") { require_once("actionlog.class.php"); $oLog = new Actionlog($this->_aConfig["id"]); $oLog->add($sMessage, (__CLASS__ . "->" . $sAction), $sLoglevel); } // ---------------------------------------------------------------------- // GETTER // ---------------------------------------------------------------------- private function _getConfigFile($sId) { if (!$sId) { die(t("class-project-error-_getConfigFile-requires-id")); } return $this->_aConfig["dataDir"] . '/projects/' . $sId . ".json"; } /** * get a full ath for temp directory (for a build) * @return string */ private function _getTempDir() { return $s = $this->_getBuildDir() . '/' . $this->_aPrjConfig["fileprefix"] . "_" . date("Ymd-His"); } /** * get full path where the project builds are (a build setes a subdir) * @return string */ private function _getBuildDir() { return $this->_aConfig['buildDir'] . '/' . $this->_aConfig["id"]; } /** * get full path where the project default files are * @return type */ private function _getDefaultsDir() { $s = $this->_aConfig['buildDefaultsDir'] . '/' . $this->_aConfig["id"]; return file_exists($s) ? $s : false; } /** * get directory for infofile and package (without extension) * @param string $sPhase one of preview|stage|live ... * @param string $sPlace one of onhold|ready2install|deployed * @return string */ private function _getFileBase($sPhase, $sPlace) { if (!array_key_exists($sPhase, $this->_aConfig["phases"])) { die(sprintf(t("class-project-error-wrong-phase"), $sPhase)); } if (!array_key_exists($sPlace, $this->_aPlaces)) { die(sprintf(t("class-project-error-wrong-place"), $sPlace)); } // local file for onhold|ready2install $sBase = $this->_aConfig['packageDir'] . "/" . $sPhase . "/" . $this->_aPrjConfig["fileprefix"]; if (!file_exists($this->_aConfig['packageDir'] . "/" . $sPhase)) { mkdir($this->_aConfig['packageDir'] . "/" . $sPhase); } if ($sPlace == "onhold") $sBase.="_onhold"; // $sBase .= "/" . $this->_aPrjConfig["fileprefix"]; // url for deployed if ($sPlace == "deployed") { if ($this->isActivePhase($sPhase) && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase])) { // $sBase = $this->_aPrjConfig["phases"][$sPhase]["url"] . $this->_aPrjConfig["fileprefix"]; $sBase = $this->_aPrjConfig["phases"][$sPhase]["url"]; } else { $sBase = ''; } } return $sBase; } /** * get filename for info/ meta file (.json file) * @param string $sPhase one of preview|stage|live ... * @param string $sPlace one of onhold|ready2install|deployed * @return string */ private function _getInfofile($sPhase, $sPlace) { $sBase = $this->_getFileBase($sPhase, $sPlace); return $sBase ? $sBase . '/' . $this->_aPrjConfig["fileprefix"] . '.json' : false; } /** * get filename for package file (.tgz file) * @param string $sPhase one of preview|stage|live ... * @param string $sPlace one of onhold|ready2install|deployed * @return string */ private function _getPackagefile($sPhase, $sPlace) { $sBase = $this->_getFileBase($sPhase, $sPlace); return $sBase ? $sBase . '/' . $this->_aPrjConfig["fileprefix"] . '.tgz' : false; } /** * list of files of a given phase and place * @param string $sPhase one of preview|stage|live ... * @param string $sPlace one of onhold|ready2install|deployed * @return array */ public function _getBuildfilesByDir($sBase) { $aReturn = array(); if (!$sBase || !is_dir($sBase)) { return false; } $iTotalSize = 0; $aReturn = array( 'dir' => $sBase, 'filecount' => false, 'totalsize' => false, 'totalsize-hr' => false, ); foreach (glob($sBase . '/*') as $sFile) { $sFileBase = basename($sFile); $sExt = pathinfo($sFile, PATHINFO_EXTENSION); $aStat = stat($sFile); switch ($sExt) { case 'erb': $sType = 'templates'; $sIcon = 'fa fa-file-code-o'; break; case 'tgz': $sType = 'package'; $sIcon = 'fa fa-file-archive-o'; break; case 'json': $sType = 'metadata'; $sIcon = 'fa fa-file-text-o'; break; default: $sType = 'any'; $sIcon = 'fa fa-file-o'; break; } $iTotalSize+=$aStat['size']; $aReturn['files'][$sFileBase] = array( 'type' => $sType, 'icon' => $sIcon ? '<i class="' . $sIcon . '"></i> ' : '', 'extension' => $sExt, 'size' => $aStat['size'], ); $aReturn['types'][$sType][] = $sFileBase; } $aReturn['totalsize'] = $iTotalSize; $aReturn['totalsize-hr'] = (round($iTotalSize / 1024 / 102.4) / 10) . ' MB'; $aReturn['filecount'] = count($aReturn['files']); return $aReturn; } /** * list of files of a given phase and place * @param string $sPhase one of preview|stage|live ... * @param string $sPlace one of onhold|ready2install|deployed * @return array */ public function getBuildfilesByPlace($sPhase, $sPlace) { $sBase = $this->_getFileBase($sPhase, $sPlace); return $this->_getBuildfilesByDir($sBase); } /** * list of files of a given version number * @param string $sVersion name of version * @return array */ public function getBuildfilesByVersion($sVersion) { return $this->_getBuildfilesByDir($this->_getProjectArchiveDir() . '/' . $sVersion); } /** * get full path of a packed project archive * @param string $sVersion version number of the build * @return string */ private function _getArchiveDir($sVersion) { if (!$sVersion) { die(t("class-project-error-_getArchiveDir-requires-id")); } return $this->_getProjectArchiveDir() . '/' . $sVersion; } /** * get array of metadata of a given version. it returns * - key "ok" anddata * or * - key "error" with the message * @param type $sTimestamp * @return array */ private function _getArchiveInfos($sTimestamp) { if (!$sTimestamp) { die(t("class-project-error-_getArchiveInfos-requires-id")); } $sInfoFile = $this->_getArchiveDir($sTimestamp) . '/' . $this->_aPrjConfig["fileprefix"] . '.json'; $aReturn['infofile'] = $sInfoFile; $sPackageFile = $this->_getArchiveDir($sTimestamp) . '/' . $this->_aPrjConfig["fileprefix"] . '.tgz'; $aReturn['packagefile'] = $sPackageFile; if (!file_exists($sInfoFile)) { $aReturn['error'] = sprintf(t("class-project-error-metafile-does-not-exist"), $sInfoFile); return $aReturn; } $aJson = json_decode(file_get_contents($sInfoFile), true); if (is_array($aJson) && array_key_exists("version", $aJson)) { $aReturn = array_merge($aReturn, $aJson); $aReturn['ok'] = 1; /* if (!file_exists($sPackageFile)) { $aReturn['error'] = sprintf(t("class-project-error-datafile-does-not-exist"), $sInfoFile); } else { $aReturn['filesizes']['packagefile']=filesize($sPackageFile); } * */ return $aReturn; } $aReturn['error'] = sprintf(t("class-project-error-metafile-wrong-format"), $sInfoFile); return $aReturn; } /** * get the directory for archive files of this project * @return string */ public function _getProjectArchiveDir() { return $this->_aConfig["archiveDir"] . '/' . $this->_aConfig["id"]; } /** * make an http get request and return the response body * @param string $url * @return string */ private function _httpGet($url, $iTimeout = 5) { $this->log(__FUNCTION__ . " start"); if (!function_exists("curl_init")) { die("ERROR: PHP CURL module is not installed."); } $this->log(__FUNCTION__ . " url: $url"); $ch = curl_init($url); 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'); $res = curl_exec($ch); curl_close($ch); $this->log(__FUNCTION__ . " done url: $url"); return $res; } /** * get all existing versions in archive and its usage * versions are array keys; where they are used is written in values * @return array */ public function getVersions() { // --- read all file entries $aReturn = array(); $sDir = $this->_getProjectArchiveDir(); if (is_dir($sDir)) { foreach (scandir($sDir) as $sEntry) { if (is_dir($sDir . '/' . $sEntry) && $sEntry != '.' && $sEntry != '..') $aReturn[$sEntry] = false; } } $this->_aVersions = $aReturn; // --- check version in all phases $this->getAllPhaseInfos(); foreach ($this->_aData["phases"] as $sPhase => $aData) { foreach (array_keys($this->_aPlaces) as $sPlace) { if (array_key_exists($sPlace, $aData) && array_key_exists("version", $aData[$sPlace])) { $this->_aVersions[$aData[$sPlace]["version"]][] = array('phase' => $sPhase, 'place' => $sPlace); } } } ksort($this->_aVersions); return $this->_aVersions; } /** * get an array with all existing build error output files (html) * @return array */ public function getBuildErrors($sProject=false) { // --- read all file entries $aReturn = array(); if(!$sProject){ $sProject=$this->_aPrjConfig["fileprefix"].'_*'; } foreach( glob($this->_getBuildDir() . '/' . $sProject . "/_output.html" ) as $sBuildDir){ $aReturn[] = basename(dirname($sBuildDir)).'/'.basename($sBuildDir); } return $aReturn; } /** * get an array with all existing build error output files (html) * @return array */ public function getBuildErrorContent($sLogfile) { if (!strpos('..', $sLogfile)===false){ return false; } $sFilename=$this->_getBuildDir() . '/' . $sLogfile; if(file_exists($sFilename)){ return file_get_contents($sFilename); } return false; } /** * get Array of all versions, metainfos, in which phases they are in use * and a rollback ist possible or not * return array */ private function _getVersionUsage() { $aVersionData = array(); $sLastVersion = false; if (!count($this->getVersions())) { return array(); } foreach ($this->getVersions() as $sVersion => $aData) { $aVersionData[$sVersion]["info"] = $this->_getArchiveInfos($sVersion); foreach ($this->getActivePhases() as $sPhase) { $bCanRollback = false; foreach (array_keys($this->_aPlaces) as $sPlace) { $bFound = false; if (is_array($aData) && count($aData)) { foreach ($aData as $i => $aPhaseUsage) { if ($aPhaseUsage["phase"] == $sPhase && $aPhaseUsage["place"] == $sPlace ) $bFound = true; } } $aVersionData[$sVersion]["usage"][$sPhase][$sPlace] = $bFound; if ($bFound) { $bCanRollback = false; } else { $bCanRollback = true; if ( $sLastVersion && !$aVersionData[$sLastVersion]["rollback"][$sPhase] ) { $bCanRollback = false; } /* if (!array_key_exists("ok", $aVersionData[$sVersion]["info"])){ $bCanRollback = false; } */ } $aVersionData[$sVersion]["rollback"][$sPhase] = $bCanRollback; } } $sLastVersion = $sVersion; } return $aVersionData; } /** * recursive delete * @param type $dir * @return type */ private function _rmdir($dir) { foreach (scandir($dir) as $sEntry) { if (is_dir($dir . '/' . $sEntry) && $sEntry != '.' && $sEntry != '..') { $this->_rmdir($dir . '/' . $sEntry); } elseif (is_file($dir . '/' . $sEntry) || is_link($dir . '/' . $sEntry)) unlink($dir . '/' . $sEntry); } return rmdir($dir); } /** * cleanup of archive directory; it returns the list of deleted * directories as array * @return array */ public function cleanupArchive($bDeleteAll = false) { if (!$this->oUser->hasPermission("project-action-cleanup")) { return $this->oUser->showDenied(); } $aDelete = array(); $aUnused = array(); $sDir = $this->_getProjectArchiveDir(); $this->getVersions(); if (!$this->_aVersions) { return $aDelete; } // find unused versions foreach ($this->_aVersions as $sVersion => $aUsage) { if (!$aUsage || count($aUsage) == 0) { $aUnused[] = $sVersion; } } // keep a few $iKeep = $bDeleteAll ? 0 : $this->_aConfig["versionsToKeep"]; while (count($aUnused) && count($aUnused) > $iKeep) { $sVersion = array_shift($aUnused); $sDir2 = $sDir . '/' . $sVersion; if (is_dir($sDir2)) { if ($this->_rmdir($sDir2)) { $aDelete[] = $sDir2; echo t('ok') . ': ' . $sDir2; } else { echo sprintf(t("class-project-warning-cannot-delete-archive-dir"), $sDir2); } } else { echo t('skip') . ': ' . $sDir2; } echo '<br>'; } // rescan versions if (count($aDelete)) { $this->getVersions(); } return $aDelete; } /** * cleanup of archive directory; it returns the list of deleted * directories as array * @return array */ public function cleanupBuilds() { $this->log(__FUNCTION__ . " start"); if (!$this->oUser->hasPermission("project-action-cleanup")) { return $this->oUser->showDenied(); } $sDir = $this->_getBuildDir(); $aDirlist = array(); $aDelete = array(); if (is_dir($sDir)) { foreach (scandir($sDir) as $sEntry) { if (is_dir($sDir . '/' . $sEntry) && $sEntry != '.' && $sEntry != '..') $aDirlist[] = $sEntry; } } // keep a few while (count($aDirlist) >= $this->_aConfig["builtsToKeep"]) { $sVersion = array_shift($aDirlist); $sDir2 = $sDir . '/' . $sVersion; if ($this->_rmdir($sDir2)) { $aDelete[] = $sDir2; } else { echo t("class-project-warning-cannot-delete-build-dir", $sDir2); }; } return $aDelete; } /** * cleanup cache of vcs * @param type $iAge */ public function cleanupVcsCache($iAge = 0) { $this->log(__FUNCTION__ . " start"); if (!$this->oUser->hasPermission("project-action-cleanup")) { return $this->oUser->showDenied(); } $this->_initVcs(); if ($this->_oVcs) { if (!method_exists($this->_oVcs, "cleanupCache")) { // the version control class does not have this method $this->log(__FUNCTION__ . " soory, Methos cleanupCache does not exist in this VCS class."); return ''; } return $this->_oVcs->cleanupCache($iAge); } } /** * get conmplete config of the project * @return array */ public function getConfig() { return $this->_aPrjConfig; } /** * get name/ label of the project * @return string */ public function getLabel() { return $this->_aPrjConfig["label"]; } /** * get description of the project * @return string */ public function getDescription() { return $this->_aPrjConfig["description"]; } /** * get the id of the current project * @return string */ public function getId(){ return $this->_aConfig["id"]; } /** * get deploy and queue infos for all phases * @return type */ public function getAllPhaseInfos() { $bHasQueue=false; $bHasDifferentVersions=false; $bFirstVersion=false; if (!array_key_exists("phases", $this->_aData)){ $this->_aData["phases"] = array(); } if (!array_key_exists("progress", $this->_aData)){ $this->_aData["progress"] = array(); } foreach (array_keys($this->_aConfig["phases"]) as $sPhase) { if (!array_key_exists($sPhase, $this->_aData["phases"])) { $this->getPhaseInfos($sPhase); } // detect progress $aDataPhase = $this->_aData["phases"][$sPhase]; foreach (array_keys($this->getPlaces()) as $sPlace) { if ( array_key_exists($sPlace, $aDataPhase) && array_key_exists('version', $aDataPhase[$sPlace]) ) { if($bFirstVersion && !$bHasDifferentVersions && $bFirstVersion!==$aDataPhase[$sPlace]['version']){ $bHasDifferentVersions=true; } if (!$bFirstVersion){ $bFirstVersion = $aDataPhase[$sPlace]['version']; } } } // check queue if (!$bHasQueue && array_key_exists('onhold', $aDataPhase) && $aDataPhase['onhold']['version']){ $bHasQueue=true; } } $this->_aData["progress"]=array( 'inprogress'=>$bHasDifferentVersions, 'hasQueue'=>$bHasQueue, ); return $this->_aData["phases"]; } /** * get statusinfos of a named phase * @param string $sPhase name of the phase; one of preview|stage|live * @return array */ public function getPhaseInfos($sPhase) { if (!$sPhase) { die(t("class-project-error-getPhaseInfos-requires-phase")); } if (!array_key_exists("phases", $this->_aData)) $this->_aData["phases"] = array(); if (!array_key_exists($sPhase, $this->_aData["phases"])) { if ($this->isActivePhase($sPhase)) { $this->_aData["phases"][$sPhase] = array(); $aTmp = array(); // a blocked package is waiting for deployment timeslot? $sKey = "onhold"; $sJsonfile = $this->_getInfofile($sPhase, $sKey); $aTmp[$sKey] = array(); if (file_exists($sJsonfile)) { $aJson = json_decode(file_get_contents($sJsonfile), true); if (array_key_exists("version", $aJson)) { $aTmp[$sKey] = $aJson; $aTmp[$sKey]["infofile"] = $sJsonfile; $aTmp[$sKey]["ok"] = 1; } else { $aTmp[$sKey]["error"] = sprintf(t("class-project-error-metafile-has-no-version"), $sJsonfile, print_r($aJson, true)); } } else { $aTmp[$sKey]["info"] = t("class-project-info-no-package-in-queue"); $aTmp[$sKey]["ok"] = 1; } // package for puppet $sKey = "ready2install"; $sJsonfile = $this->_getInfofile($sPhase, $sKey); $aTmp[$sKey] = array(); if (file_exists($sJsonfile)) { $sPkgfile = $this->_getPackagefile($sPhase, $sKey); if (file_exists($sPkgfile)) { $aJson = json_decode(file_get_contents($sJsonfile), true); if (is_array($aJson) && array_key_exists("version", $aJson)) { $aTmp[$sKey] = $aJson; $aTmp[$sKey]["infofile"] = $sJsonfile; $aTmp[$sKey]["packagefile"] = $sPkgfile; $aTmp[$sKey]["ok"] = 1; } else { $aTmp[$sKey]["error"] = sprintf(t("class-project-error-metafile-has-no-version"), $sJsonfile, print_r($aJson, true)); } } else { $aTmp[$sKey]["error"] = sprintf(t("class-project-error-getPhaseInfos-package-not-found"), $sPkgfile); } } else { $aTmp[$sKey]["error"] = sprintf(t("class-project-error-metafile-does-not-exist"), $sJsonfile); } // published data $sKey = "deployed"; $sJsonfile = $this->_getInfofile($sPhase, $sKey); $aTmp[$sKey] = array(); // use version cache require_once(__DIR__ . '/../../valuestore/classes/valuestore.class.php'); $oVersion = new valuestore(); $oVersion->setProject("", $this->_aPrjConfig["fileprefix"], $sPhase, $sKey); $aVersions = $oVersion->getVersion(); // echo "Place: <pre>" . print_r($oVersion->whereiam(), 1) . "</pre>"; // echo "Versionen: <pre>" . print_r($aVersions, 1) . "</pre>"; if (count($aVersions)){ $aTmp[$sKey] = array(); $aTmp[$sKey] = $aVersions[0]['_data']; $aTmp[$sKey]["infofile"] = '[versioncache]'; $aTmp[$sKey]['_hosts'] = array(); foreach ($aVersions as $sHostname => $aHostdata) { $aTmp[$sKey]['_hosts'][$aHostdata['host']] = $aHostdata; } $aTmp[$sKey]["ok"] = 1; $aTmp[$sKey]["infofile"] = '[versioncache]'; } /* $sJsonData = $this->_httpGet($sJsonfile); if ($sJsonData) { $aJson = json_decode($sJsonData, true); if (is_array($aJson) && array_key_exists("version", $aJson)) { $aTmp[$sKey] = $aJson; $aTmp[$sKey]["infofile"] = $sJsonfile; $aTmp[$sKey]["ok"] = 1; } else { $aTmp[$sKey]["error"] = sprintf(t("class-project-error-metafile-has-no-version"), $sJsonfile, print_r($aJson, true)); } } else { $aTmp[$sKey]["error"] = sprintf(t("class-project-error-metafile-wrong-format"), $sJsonfile); } * */ } else { $aTmp['onhold']["warning"] = sprintf(t("class-project-warning-phase-not-active"), $sPhase); $aTmp['ready2install']["warning"] = sprintf(t("class-project-warning-phase-not-active"), $sPhase); $aTmp['deployed']["warning"] = sprintf(t("class-project-warning-phase-not-active"), $sPhase); } $this->_aData["phases"][$sPhase] = $aTmp; } return $this->_aData["phases"][$sPhase]; } /** * get a list of all existing projects as a flat array * <code> * print_r($oPrj->getProjects()); * </code> * returns<br> * Array ( [0] => project1 [1] => project2 ) * @return array */ public function getProjects() { $aReturn = array(); foreach (glob(dirname($this->_getConfigFile("dummy")) . "/*.json") as $filename) { $aReturn[] = str_replace(".json", "", basename($filename)); } sort($aReturn); return $aReturn; } /** * check if the given phase is active for this project * @param type $sPhase * @return type */ public function isActivePhase($sPhase) { return ( array_key_exists("active", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["active"][0] : false ); } /** * return array of all (active and inactive) phases * @return type */ public function getPhases() { return $this->_aConfig["phases"]; } /** * return array of all (active and inactive) phases * @return type */ public function getPlaces() { return $this->_aPlaces; } /** * get a flat array with active phases of the project * @return array */ public function getActivePhases() { $aReturn = array(); foreach (array_keys($this->_aConfig["phases"]) as $s) { if ($this->isActivePhase($s)) { $aReturn[] = $s; } } return $aReturn; } /** * find the next active phase of a project * @param string $sPhase current phase; if empty the function sends back the first phase */ public function getNextPhase($sPhase = false) { if ($sPhase) { if (!array_key_exists($sPhase, $this->_aConfig["phases"])) { die(sprintf(t("class-project-error-wrong-phase"), $sPhase)); } } $sNextPhase = false; $bUseNextPhase = $sPhase ? false : true; foreach (array_keys($this->_aConfig["phases"]) as $s) { if ($bUseNextPhase) { if ($this->isActivePhase($s)) { $sNextPhase = $s; $bUseNextPhase = false; continue; } } if ($sPhase == $s) { $bUseNextPhase = true; } } return $sNextPhase; } public function getProgress(){ $this->getAllPhaseInfos(); return $this->_aData['progress']; } /** * check: is the deployment to the next phase enabled for this phase? * @param type $sPhase current phase */ public function canAcceptPhase($sPhase = false) { if (!$this->oUser->hasPermission("project-action-accept") && !$this->oUser->hasPermission("project-action-accept-$sPhase") ) { // echo $this->oUser->showDenied(); return false; } if (!$sPhase) { // for better performance: skip check on overview page /* $aRepodata = $this->getRepoRevision(); if (!array_key_exists("revision", $aRepodata)) { return false; } */ $sNext = $this->getNextPhase($sPhase); return $sNext > ''; } if (!array_key_exists($sPhase, $this->_aConfig["phases"])) { die(sprintf(t("class-project-error-wrong-phase"), $sPhase)); } if (!$this->isActivePhase($sPhase)) { // die("ERROR: the phase $sPhase is not active in this project."); return false; } $sNext = $this->getNextPhase($sPhase); if (!$sNext) { return false; } // ensure that _aData is filled $this->getPhaseInfos($sPhase); // array key "ok" must be in the ready2install and deployed info // and a version must be installed if ( array_key_exists($sPhase, $this->_aData["phases"]) && array_key_exists("onhold", $this->_aData["phases"][$sPhase]) && array_key_exists("ready2install", $this->_aData["phases"][$sPhase]) && array_key_exists("deployed", $this->_aData["phases"][$sPhase]) && array_key_exists("ok", $this->_aData["phases"][$sPhase]["onhold"]) && array_key_exists("ok", $this->_aData["phases"][$sPhase]["ready2install"]) && array_key_exists("ok", $this->_aData["phases"][$sPhase]["deployed"]) && array_key_exists("version", $this->_aData["phases"][$sPhase]["deployed"]) ) { return true; } return false; } /** * get list of remote branches and tags * @param type $sActiveBranchname * @return string|boolean */ public function getRemoteBranches($sActiveBranchname = false) { $this->log(__FUNCTION__ . " start"); $this->_initVcs(); if ($this->_oVcs) { if (!method_exists($this->_oVcs, "getRemoteBranches")) { // the version control class does not have this method return ''; } return $this->_oVcs->getRemoteBranches(); } return false; } /** } * get html form with selectr for remote branches * @param string $sActiveBranchname force active branch name * @return string */ public function renderSelectRemoteBranches($sActiveBranchname = false) { $aReturn = array(); $aRadios = array(); $bFoundActive = false; $i = 0; if (!$this->_oVcs) { $this->_initVcs(); } require_once("formgen.class.php"); if (!$sActiveBranchname) { $sActiveBranchname = $this->_sBranchname; } if ($this->_oVcs) { if (!method_exists($this->_oVcs, "getRemoteBranches")) { // the version control class does not have this method return ''; } foreach ($this->_oVcs->getRemoteBranches() as $aBranch) { $sBranch = $aBranch['name']; $aRadios[$sBranch] = array( 'value' => $sBranch, 'label' => $aBranch['label'], ); // if no param was given the first branch will be marked if (!$sActiveBranchname) { $sActiveBranchname = $sBranch; } if ($sBranch == $sActiveBranchname) { $bFoundActive = true; // $aRadios[$sBranch]['checked'] = 'checked'; $aRadios[$sBranch]['selected'] = 'selected'; } else { // for SELECT we need the onclick even on select element // not on the option (Chrome) // $aRadios[$sBranch]['onclick'] = 'document.getElementById(\'submitBranch\').click()'; } }; } // no branches were found if (count($aRadios) == 0) { return ''; } $aForms = array( 'frmSelectBranch' => array( 'meta' => array( 'method' => 'POST', 'action' => '?', 'id' => 'frmSelectBranch', ), 'validate' => array(), 'form' => array( 'branchname' => array( 'inline' => true, 'type' => 'select', 'onchange' => 'document.getElementById(\'submitBranch\').click()', 'name' => 'branchname', 'label' => '<strong>' . t('branch-select') . '</strong>', 'validate' => 'isastring', 'options' => $aRadios, ), ), ), ); // submit to switch branches - only if a selection is available if (count($aRadios) > 1 || !$bFoundActive) { $aForms['frmSelectBranch']['form']['submitBranch'] = array( 'type' => 'submit', 'name' => 'btnsave', 'onclick' => 'showModalMessage(\'' . t('branch-switch') . '\'); ', 'label' => t("change"), 'value' => '<i class="glyphicon glyphicon-ok"></i> ' . t("change"), ); } $oFrm = new formgen($aForms); return $oFrm->renderHtml('frmSelectBranch') . '<script>$("#submitBranch").hide();</script>'; // return $oFrm->renderHtmlElement('dummy',$aFormData); } /** * get current revision and log message from remote repo * @param boolean $bRefresh optional: refresh data; default: use cache * @return array */ public function getRepoRevision($bRefresh = false) { $this->log(__FUNCTION__ . " start"); if (!$this->_aPrjConfig["build"]["type"]) { $this->_aData["phases"]["source"] = array("error" => t("class-project-error-repo-type-not-set"),); } else { $this->_initVcs(); if ($this->_oVcs) { $this->_aData["phases"]["source"] = $this->_oVcs->getRepoRevision($bRefresh); } else { $this->_aData["phases"]["source"] = array( "error" => sprintf(t("class-project-error-repo-type-not-supported"), $this->_aPrjConfig["build"]["type"]), ); } } return $this->_aData["phases"]["source"]; } /** * init version control system (git, ...) * @return vcs-object */ private function _initVcs() { $this->log(__FUNCTION__ . " start"); if (!$this->_oVcs) { if (!$this->_aPrjConfig["build"]["type"]) { $this->_aData["phases"]["source"] = array("error" => t("class-project-error-repo-type-not-set"),); } else { if (!@include_once("vcs." . $this->_aPrjConfig["build"]["type"] . ".class.php")) { $this->_aData["phases"]["source"] = array( "error" => sprintf(t("class-project-error-repo-type-not-supported"), $this->_aPrjConfig["build"]["type"]), ); } else { $aConfig = $this->_aPrjConfig["build"]; // for vcs classes $aConfig["appRootDir"] = $this->_aConfig["appRootDir"]; $aConfig["dataDir"] = $this->_aConfig["dataDir"]; $aConfig["tmpDir"] = $this->_aConfig["tmpDir"]; $this->_oVcs = new vcs($aConfig); if ($this->_sBranchname) { if (method_exists($this->_oVcs, "setCurrentBranch")) { $this->_oVcs->setCurrentBranch($this->_sBranchname); } } } } } return $this->_oVcs; } /** * get a flat array of all existing ssh keys * @return array */ private function _getSshKeys() { $aReturn = array(); foreach (glob($this->_aConfig["dataDir"] . "/sshkeys/*.pub") as $filename) { $aReturn[] = str_replace(".pub", "", basename($filename)); } sort($aReturn); return $aReturn; } /** * get a flat array with regexes of deploy times * @param string $sPhase phase * @return array */ private function _getDeploytimes($sPhase) { if (!$this->isActivePhase($sPhase)) { $sError = sprintf(t("class-project-warning-phase-not-active"), $sPhase); $this->_logaction($sError, __FUNCTION__, "error"); return false; } $aDeploytimes = array(); if (array_key_exists("deploytimes", $this->_aConfig["phases"][$sPhase])) { $aDeploytimes = $this->_aConfig["phases"][$sPhase]["deploytimes"]; } if (array_key_exists("deploytimes", $this->_aPrjConfig["phases"][$sPhase]) && $this->_aPrjConfig["phases"][$sPhase]["deploytimes"] ) { $aDeploytimes = array($this->_aPrjConfig["phases"][$sPhase]["deploytimes"]); } return $aDeploytimes; } // ---------------------------------------------------------------------- // SETTER // ---------------------------------------------------------------------- private function _setProcessOutFile($sNewTempfile = false) { if ($this->_sProcessTempOut && file_exists($this->_sProcessTempOut)) { unlink($this->_sProcessTempOut); } $sNewTempfile = sys_get_temp_dir() . "/" . basename($sNewTempfile); $this->_sProcessTempOut = $sNewTempfile; return $this->_sProcessTempOut; } /** * get projects from ldap; it returns ldap search items with cn as * array key. * * @return array */ private function _ldapProjectSearch($sSearchFilter) { $aReturn = array(); require_once("ldap.class.php"); $oLdapIML = new imlldap($this->_aConfig['projects']['ldap']); // $oLdapIML->debugOn(); $aResultsIml = $oLdapIML->searchDn( $this->_aConfig['projects']['ldap']['DnProjects'], $sSearchFilter, array("*") ); if (!$aResultsIml['count']) { return false; } $oLdapIML->close(); /* unset($aResultsIml['count']); foreach ($aResultsIml as $aItem) { $aReturn[$aItem['cn'][0]] = array( 'dn' => $aItem['dn'], 'cn' => $aItem['cn'][0], '_description' => $aItem['description'][0], 'title' => $sTitle, 'description' => $sDescription, ); } $oLdapIML->close(); ksort($aReturn); return $aReturn; * */ return $aResultsIml; } /** * load config of a project * @return boolean */ public function setProjectById($sId) { if ($sId !== preg_replace('/[^a-z0-9\-\_]/i', '', $sId)) { echo "ERROR: invalid syntax in project ID: $sId<br>"; return false; } $this->_aPrjConfig = array(); $this->_aConfig["id"] = $sId; if ($this->_aConfig['projects']['json']['active']) { $this->_aPrjConfig = json_decode(file_get_contents($this->_getConfigFile($sId)), true); } if ($this->_aConfig['projects']['ldap']['active']) { // TODO: read project after saving it - @see $this->saveConfig() $sQuery = '(&(objectclass=hieraSource)(documentIdentifier=' . $sId . '))'; $aResult = $this->_ldapProjectSearch($sQuery); // echo '<pre>$aResult = ' . print_r($aResult, 1) . '</pre>'; if (is_array($aResult) && $aResult[0] && array_key_exists('hieradata', $aResult[0]) ) { foreach ($aResult[0]['hieradata'] as $sLine) { // echo $sLine.'<br>'; if (preg_match('/^cfg=/', $sLine)) { // echo $sLine.'<br>'; $this->_aPrjConfig = json_decode(preg_replace('/^cfg=/', '', $sLine), 1); } } } // return $this->objAdd($sDn, $aItem); } // $aData=json_decode(file_get_contents($this->_getConfigFile($sId)), true); // echo "<pre>" . print_r($aData, true) . "</pre>"; $this->_verifyConfig(); return true; } /** * set a branchname * @param string $sBranchname name of the branch, i.e. "origin/master" * @return bool */ public function setBranchname($sBranchname) { $this->_sBranchname = $sBranchname; if ($this->_oVcs) { if (method_exists($this->_oVcs, "setCurrentBranch")) { $this->_oVcs->setCurrentBranch($sBranchname); } } return $this->_sBranchname; } // ---------------------------------------------------------------------- // ACTIONS // ---------------------------------------------------------------------- /** * send data to a tempfile for ajax polling * @param type $sTmpFile * @param type $sData * @return boolean */ private function _TempFill($sData, $aActions = array()) { if (!$this->_sProcessTempOut) { return false; } $sActions = ''; if (count($aActions)) { for ($i = 0; $i < count($aActions["actions"]); $i++) { $sActions.='<li'; if ($i == $aActions["iActive"]) { $sActions.=' class="active"'; } $sActions.='>' . $aActions["actions"][$i]['label'] . '</li>'; } if ($sActions) { $sData = '<div style="float: right; background: #f8f8f8; padding: 1em;">' . '<strong>' . $aActions["label"] . '</strong>' . '<ol class="actions">' . $sActions . '</ol></div>' . $sData; } } return file_put_contents($this->_sProcessTempOut, $sData); } /** * delete tempfile for ajax polling; if a directory is given as parameter * the tmp file will be moved there * @param string $sTempDir optional; target dir to copy; default=false (=delete file) * @return boolean */ private function _TempDelete($sTempDir=false) { if (!$this->_sProcessTempOut){ return false; } if (file_exists($this->_sProcessTempOut)) { if ($sTempDir && is_dir($sTempDir)){ $sKeepOutfile=$sTempDir.'/_output.html'; copy($this->_sProcessTempOut, $sKeepOutfile); } unlink($this->_sProcessTempOut); } return file_exists($this->_sProcessTempOut); } /** * get the name of the current branch (or default branch) * @return string */ public function getBranchname() { $this->log(__FUNCTION__ . " start"); $this->_initVcs(); if ($this->_oVcs) { if (method_exists($this->_oVcs, "getCurrentBranch")) { $this->setBranchname($this->_oVcs->getCurrentBranch()); return $this->_oVcs->getCurrentBranch(); } } return false; } /** * Build a new package for the deployment. It will be put to the queue * of the first active phase (i.e. preview). * If there is no deployment time range it will be deployed too. * @global type $aParams * @return boolean|string */ public function build($sTmpFile = false) { $this->log(__FUNCTION__ . " start"); if (!$this->oUser->hasPermission("project-action-build")) { return $this->oUser->showDenied(); } global $aParams; $sReturn = false; $oHtml = new htmlguielements(); $aActionList = array( 'iActive' => 0, 'label' => t('build'), 'actions' => array( array('label' => t('class-project-build-label-cleanup-builds')), array('label' => t('class-project-build-label-create-workdir')), array('label' => t('class-project-build-label-get-sources-from-version-control')), array('label' => t('class-project-build-label-execute-hook-postclone')), array('label' => t('class-project-build-label-copy-default-structure')), array('label' => t('class-project-build-label-execute-hook-precompress')), array('label' => t('class-project-build-label-cleanup-project')), array('label' => t('class-project-build-label-create-package')), array('label' => t('class-project-build-label-remove-workdir')), array('label' => t('class-project-build-label-queue-to-first-active-phase')), ), ); $this->_setProcessOutFile($sTmpFile); $this->_iRcAll = 0; // return $this->_execAndSend("bash --login -c 'ruby --version' " . $sTempDir); $this->_logaction(t('starting') . " build()", __FUNCTION__); $sReturn = "<h2>" . t("build") . " " . $this->getLabel() . "</h2>"; // -------------------------------------------------- // cleanup // -------------------------------------------------- $aDirs = $this->cleanupBuilds(); if (count($aDirs)) { $sReturn.='<h3>' . t('class-project-build-label-cleanup-builds') . '</h3><pre>' . print_r($aDirs, true) . '</pre>'; } $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); $this->_initVcs(); if (!$this->_oVcs) { $sError = sprintf(t('class-project-error-build-type-not-supported'), $this->_aPrjConfig["build"]["type"]); $this->_logaction($sError, __FUNCTION__, "error"); return $oHtml->getBox("error", $sError . $sReturn); } // -------------------------------------------------- // create workdir // -------------------------------------------------- $sTempBuildDir = $this->_getTempDir(); $sFirstLevel = $this->getNextPhase(); if (!$sFirstLevel) { $this->_TempDelete(); $this->_logaction(t('page-overview-no-phase'), __FUNCTION__, "error"); return false; } $sReturn.='<h3>' . t('class-project-build-label-create-workdir') . '</h3>'; if (!file_exists($sTempBuildDir)) { $sReturn.=$this->_execAndSend("mkdir -p " . $sTempBuildDir); } $sReturn.=$this->_execAndSend("ls -ld " . $sTempBuildDir); if (!file_exists($sTempBuildDir)) { $this->_TempDelete(); $sError = sprintf(t('"class-project-error-build-dir-was-not-created"'), $sTempBuildDir); $this->_logaction($sError, __FUNCTION__, "error"); return $oHtml->getBox("error", $sError . $sReturn); } // $this->_iRcAll = 0; $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); // -------------------------------------------------- // checkout // -------------------------------------------------- $sReturn.='<h3>' . t('class-project-build-label-get-sources-from-version-control') . '</h3>'; $sReturn.='<pre>' . $this->_oVcs->getSources($sTempBuildDir) . '</pre>'; $aVersion = $this->_oVcs->getRevision($sTempBuildDir); $sRevision = $aVersion["revision"]; $sCommitMsg = $aVersion["message"]; $sCommitMsg = str_replace("\n", "<br>", $sCommitMsg); $sCommitMsg = str_replace('"', """, $sCommitMsg); $sReturn.=$oHtml->getBox("info", $sCommitMsg); $sReturn.=$this->_execAndSend("ls -lisa $sTempBuildDir"); if (!$this->_iRcAll == 0) { $this->_TempDelete($sTempBuildDir); $sError = sprintf(t('class-project-error-command-failed'), $sTempBuildDir) . $sReturn; $this->_logaction($sError, __FUNCTION__, "error"); return $oHtml->getBox("error", $sError); } // -------------------------------------------------- foreach (glob($sTempBuildDir . '/hooks/on*') as $filename) { $sReturn.='chmod 755 ' . $filename . '<br>'; $sReturn.=$this->_execAndSend('chmod 755 ' . $filename); $sReturn.=$this->_execAndSend('ls -l ' . $filename); } $sReturn.=$oHtml->getBox("success", t('class-project-info-build-checkout-ok')); $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); // -------------------------------------------------- // execute hook postclone // -------------------------------------------------- // task#1726 - add environment $sSetEnv='' . 'export GIT_SSH="'.$this->_aConfig['appRootDir'].'/shellscripts/gitsshwrapper.sh";' . 'export DIR_SSH_KEYS="'.$this->_aConfig['dataDir'].'/sshkeys";' . 'export DIR_APPROOT="'.$sTempBuildDir.'";' . (isset($this->_aConfig['build']['env']) ? $this->_aConfig['build']['env'] : '') ; $sHookfile = $this->_aConfig['build']['hooks']['build-postclone']; $sReturn.='<h3>' . t('class-project-build-label-execute-hook-postclone') . ' (' . $sHookfile . ')</h3>'; if (file_exists($sTempBuildDir . '/' . $sHookfile)) { // $sReturn.=$this->_execAndSend('chmod 755 ' . $sTempDir . '/hooks/on*'); // $this->_iRcAll = 0; $sReturn.=$this->_execAndSend('bash --login -c \'' . $sSetEnv.' '.$sTempBuildDir . '/' . $sHookfile . '\''); if (!$this->_iRcAll == 0) { $sError = sprintf(t('class-project-error-command-failed'), $sTempBuildDir); $this->_logaction($sError, __FUNCTION__, "error"); $this->_TempDelete($sTempBuildDir); return $oHtml->getBox("error", $sError . $sReturn); } } else { $sReturn.=t('skip') . '<br>'; } $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); // -------------------------------------------------- // copy default structure // -------------------------------------------------- $sReturn.='<h3>' . t('class-project-build-label-copy-default-structure') . '</h3>'; if ($this->_getDefaultsDir()) { $sReturn.=$this->_execAndSend("find " . $this->_getDefaultsDir() . " | head -15"); $sReturn.=$this->_execAndSend("rsync -r " . $this->_getDefaultsDir() . "/* $sTempBuildDir"); // $sReturn.=$this->_execAndSend("find $sTempDir"); } else { $sReturn.=t('skip') . '<br>'; } $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); // -------------------------------------------------- // execute hook // -------------------------------------------------- $sHookfile = $this->_aConfig['build']['hooks']['build-precompress']; $sReturn.='<h3>' . t('class-project-build-label-execute-hook-precompress') . ' (' . $sHookfile . ')</h3>'; if (file_exists($sTempBuildDir . '/' . $sHookfile)) { // $sReturn.=$this->_execAndSend('chmod 755 ' . $sTempDir . '/hooks/on*'); // $this->_iRcAll = 0; $sReturn.=$this->_execAndSend('bash --login -c \'' . $sSetEnv.' '.$sTempBuildDir . '/' . $sHookfile . '\''); if (!$this->_iRcAll == 0) { $this->_TempDelete($sTempBuildDir); $sError = sprintf(t('class-project-error-command-failed'), $sTempBuildDir) . $sReturn; $this->_logaction($sError, __FUNCTION__, "error"); return $oHtml->getBox("error", $sError); } } else { $sReturn.=t('skip') . '<br>'; } $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); // -------------------------------------------------- // cleanup .git, .svn, ... // -------------------------------------------------- $sReturn.='<h3>' . t('class-project-build-label-cleanup-project') . '</h3>'; if ($this->_oVcs) { $this->_oVcs->cleanupWorkdir($sTempBuildDir); } // $sReturn.=$this->_execAndSend("cd $sTempDir && rm -rf .git"); // $sReturn.=$this->_execAndSend("cd $sTempDir && rm -rf .svn"); // $sReturn.=$this->_execAndSend("find $sTempDir -type d -name '.svn' -exec rm -rf {} \;"); $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); // -------------------------------------------------- // create package // -------------------------------------------------- $sReturn.='<h3>' . t('class-project-build-label-create-package') . '</h3>'; // public_html must exist if (array_key_exists('haspublic', $this->_aPrjConfig["build"]) && $this->_aPrjConfig["build"]["haspublic"][0] ){ $sWebroot = false; $sWebroot1 = $sTempBuildDir . '/public_html'; $sWebroot2 = $sTempBuildDir . '/public'; if (file_exists($sWebroot1)) { $sWebroot = $sWebroot1; } if (file_exists($sWebroot2)) { $sWebroot = $sWebroot2; } if (!$sWebroot) { $this->_TempDelete($sTempBuildDir); $sError = t('class-project-error-build-docroot-not-found'); $this->_logaction($sError, __FUNCTION__, "error"); return $oHtml->getBox("error", $sError . $sReturn . $sError); } } if (!$this->_iRcAll == 0) { $this->_TempDelete($sTempBuildDir); $sError = sprintf(t('class-project-error-command-failed'), $sTempBuildDir) . $sReturn; $this->_logaction($sError, __FUNCTION__, "error"); return $oHtml->getBox("error", $sError); } // $sReturn.=$oHtml->getBox("success", "preparations ok - directory is ready for packaging now."); // generate info file $sTs = date("Y-m-d H:i:s"); $sTs2 = date("Ymd_His"); $sBranch = ($this->_sBranchname ? $this->_sBranchname : t("defaultbranch")); $sInfoFileWebroot = $sTempBuildDir . '/' . basename($this->_getInfofile($sFirstLevel, "deployed")); $sInfoFileArchiv = $this->_getArchiveDir($sTs2) . '/' . basename($this->_getInfofile($sFirstLevel, "deployed")); $sPackageFileArchiv = $this->_getArchiveDir($sTs2) . '/' . basename($this->_getPackagefile($sFirstLevel, "deployed")); $aInfos = array( 'date' => $sTs, 'version' => $sTs2, 'branch' => $sBranch, 'revision' => $sRevision, 'message' => $sCommitMsg, ); /* $sInfos = '{ "date": "' . $sTs . '", "version": "' . $sTs2 . '", "branch": "' . $sBranch . '", "revision": "' . $sRevision . '", "message": "' . $sCommitMsg . '" }'; * */ /* "user": "' . $aParams["inputUser"] . '", "remark": "' . $aParams["inputComment"] . '" */ $sReturn.=t("class-project-info-build-write-meta-to-webroot") . "<br>"; // file_put_contents($sInfoFileWebroot, $sInfos); file_put_contents($sInfoFileWebroot, json_encode($aInfos)); $sReturn.=$this->_execAndSend("ls -l $sInfoFileWebroot"); $sReturn.=$this->_execAndSend("cat $sInfoFileWebroot"); if (!file_exists(dirname($sPackageFileArchiv))) { $sReturn.=sprintf(t("creating-directory"), dirname($sPackageFileArchiv)) . "<br>"; mkdir(dirname($sPackageFileArchiv), 0775, true); } $sReturn.=$this->_execAndSend("ls -ld " . dirname($sPackageFileArchiv)); if (!file_exists(dirname($sPackageFileArchiv))) { $this->_TempDelete($sTempBuildDir); $sError = sprintf(t('"class-project-error-build-dir-was-not-created"'), $sTempBuildDir); $this->_logaction($sError, __FUNCTION__, "error"); return $oHtml->getBox("error", $sError . $sReturn); } $this->_TempFill($sReturn, $aActionList); // create tgz archive $sReturn.=sprintf(t("creating-file"), $sPackageFileArchiv) . "<br>"; $sReturn.=$this->_execAndSend("cd $sTempBuildDir && tar -czf $sPackageFileArchiv ."); $this->_TempFill($sReturn, $aActionList); // write info file (.json) $sReturn.=sprintf(t("creating-file"), $sInfoFileArchiv) . "<br>"; // file_put_contents($sInfoFileArchiv, $sInfos); file_put_contents($sInfoFileArchiv, json_encode($aInfos)); // copy template files if (file_exists($sTempBuildDir . '/hooks/templates/')) { $sReturn.=t("class-project-info-build-write-templatefiles-to-archive") . "<br>"; $sReturn.=$this->_execAndSend("cp $sTempBuildDir/hooks/templates/* " . $this->_getArchiveDir($sTs2)); } else { $sReturn.=t("class-project-info-build-write-templatefiles-to-archive-skipped") . "<br>"; } $this->_TempFill($sReturn, $aActionList); $sReturn.="<br>" . t("info") . ":<br>"; $sReturn.=$this->_execAndSend("ls -l " . $this->_getArchiveDir($sTs2)); // TEST // $this->_iRcAll=1; if (!$this->_iRcAll == 0) { $this->_TempDelete($sTempBuildDir); $sError = t('class-project-error-build-packaging-failed'); $this->_logaction($sError, __FUNCTION__, "error"); return $oHtml->getBox("error", $sError . $sReturn); } $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); $sReturn.='<h3>' . t("class-project-build-label-remove-workdir") . '</h3>'; $sReturn.=$this->_execAndSend("rm -rf $sTempBuildDir"); $sReturn.=t("class-project-info-build-remove-oldest-archives"); $sReturn.='<pre>' . print_r($this->cleanupArchive(), true) . '</pre>'; $sInfo = t("class-project-info-build-successful"); $this->_logaction(t('finished') . ' ' . $sInfo, __FUNCTION__, "success"); $sReturn.=$oHtml->getBox("success", $sInfo); $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); $sReturn.=$this->queue($sFirstLevel, $sTs2); $this->_TempDelete(); $this->_setProcessOutFile(false); return $sReturn; } /** * put a packaged version into the queue of a specified phase * @param string $sPhase name of the phase * @param string $sVersion version * @return string */ public function queue($sPhase, $sVersion) { $aActionList = array( 'iActive' => 0, 'label' => t("queue"), 'actions' => array( array('label' => t("class-project-queue-label-checks")), array('label' => t("class-project-queue-label-remove-existing-version")), array('label' => t("class-project-queue-label-link-new-version")), array('label' => t("class-project-queue-label-deploy")), ), ); $this->_logaction(t('starting') . " queue($sPhase, $sVersion)", __FUNCTION__); $sReturn = "<h2> " . t("queue") . " " . $this->getLabel() . " :: $sPhase</h2>"; $this->_TempFill($sReturn, $aActionList); $oHtml = new htmlguielements(); if (!$this->isActivePhase($sPhase)) { $sError = sprintf(t("class-project-warning-phase-not-active"), $sPhase); $this->_logaction($sError, __FUNCTION__, "error"); return $oHtml->getBox("error", $sError . $sReturn); } $sPlace = "onhold"; $sLinkTarget = $this->_getArchiveDir($sVersion); $sLinkName = $this->_getFileBase($sPhase, $sPlace); // -------------------------------------------------- // Checks // -------------------------------------------------- if (!$sLinkName) { die(t("class-project-error-queue-sLinkName-is-empty")); } if (!$sLinkTarget) { die(t("class-project-error-queue-sLinkTarget-is-empty")); } if (!file_exists($sLinkTarget)) { die(sprintf(t("class-project-error-queue-wrong-version"), $sVersion, $sLinkTarget)); } $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); // -------------------------------------------------- // remove existing version // -------------------------------------------------- $this->_iRcAll = 0; if (file_exists($sLinkName)) { $sReturn.=t("class-project-queue-label-remove-existing-version") . "<br>"; $sReturn.=$this->_execAndSend("rm -f $sLinkName"); } $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); // -------------------------------------------------- // create the new link // -------------------------------------------------- $sReturn.=t("class-project-queue-label-link-new-version") . "<br>"; $sReturn.=$this->_execAndSend("ln -s $sLinkTarget $sLinkName"); $sReturn.=$this->_execAndSend("ls -l $sLinkName | fgrep $sLinkTarget"); $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); if (!$this->_iRcAll == 0) { $this->_TempDelete(); $sError = t("class-project-error-command-failed"); $this->_logaction($sError, __FUNCTION__, "error"); return $oHtml->getBox("error", $sError . $sReturn); } $this->_logaction(t('finished') . " queue($sPhase, $sVersion) " . t("class-project-info-queue-successful"), __FUNCTION__); $sReturn.=$oHtml->getBox("success", t("class-project-info-queue-successful")); $sReturn.=$this->deploy($sPhase); $this->_TempDelete(); return $sReturn; } /** * deploy a queued package - this moves the queue into the repo directory * and will be installed on server within 30 min. * This method checks the deploy times * @param string $sPhase which queue of which phase we want to install in server * @param bool $bIgnoreDeploytimes flag; if true it will override time windows * @return boolean|string */ public function deploy($sPhase, $bIgnoreDeploytimes = false) { $oHtml = new htmlguielements(); $this->log(__FUNCTION__ . " start"); if (!$this->oUser->hasPermission("project-action-deploy") && !$this->oUser->hasPermission("project-action-deploy-$sPhase") ) { return $this->oUser->showDenied(); } $aActionList = array( 'iActive' => 0, 'label' => t("deploy"), 'actions' => array( array('label' => t("class-project-deploy-label-checks")), array('label' => t("class-project-deploy-label-activate-queued-version")), array('label' => t("class-project-deploy-label-synch-packages")), array('label' => t("class-project-deploy-label-install-on-target")), ), ); $sReturn = "<h2>" . t("deploy") . " " . $this->getLabel() . " :: $sPhase</h2>"; $this->_TempFill($sReturn, $aActionList); if (!$this->isActivePhase($sPhase)) { $sError = sprintf(t("class-project-warning-phase-not-active"), $sPhase); $this->_logaction($sError, __FUNCTION__, "error"); return $sReturn . $oHtml->getBox("error", $sError); } $sQueueLink = $this->_getFileBase($sPhase, "onhold"); $sRepoLink = $this->_getFileBase($sPhase, "ready2install"); // -------------------------------------------------- // checks // -------------------------------------------------- $sReturn.="<h3>" . t("class-project-deploy-label-checks") . "</h3>"; $aDeploytimes = $this->_getDeploytimes($sPhase); if (count($aDeploytimes)) { // check if the a deploy time is reached $sNow = date("D H:i:s"); $sReturn.=sprintf(t("class-project-info-deploy-check-deployment-times"), $sNow) . "<br>"; $bCanDeploy = false; foreach ($aDeploytimes as $sRegex) { $sReturn.=sprintf(t("class-project-info-deploy-test-regex"), $sRegex); if (preg_match($sRegex, $sNow)) { $sReturn.=t("ok"); $bCanDeploy = true; } else { $sReturn.=t("no"); } $sReturn.="<br>"; } if (!$bCanDeploy) { if (!$bIgnoreDeploytimes) { $sError = t("class-project-info-deploy-time-not-reached"); // $this->_logaction($sError, __FUNCTION__); $sReturn.=$oHtml->getBox("info", $sError); $this->_TempDelete(); // removed: cronjob sends this message too // $this->_sendMessage($sError."\n".t('phase').': '.$sPhase); return $sReturn; } else { $sReturn.=t("class-project-info-deploy-time-not-reached-and-ignored") . "<br>"; } } else { $sReturn.=t("class-project-info-deploy-time-ok") . "<br>"; } // if () } $this->_logaction(t('starting') . " deploy($sPhase, $bIgnoreDeploytimes)", __FUNCTION__); if (!file_exists($sQueueLink)) { $sError = sprintf(t("class-project-info-deploy-nothing-in-queue"), $sQueueLink); $this->_logaction($sError, __FUNCTION__, "error"); $sReturn.=$oHtml->getBox("info", $sError); $this->_TempDelete(); $this->_sendMessage($sError."\n".t('phase').': '.$sPhase); return $sReturn; } $this->_TempFill($sReturn); $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); // -------------------------------------------------- // move the queue link to the repo name // -------------------------------------------------- $this->_iRcAll = 0; if (file_exists($sRepoLink)) { $sReturn.=t("class-project-info-deploy-removing-existing-version") . "<br>"; $sReturn.=$this->_execAndSend("rm -f $sRepoLink"); } $this->_TempFill($sReturn); $sReturn.=t("class-project-info-deploy-moving-queue-to-repo") . "<br>"; $sReturn.=$this->_execAndSend("mv $sQueueLink $sRepoLink"); if (!$this->_iRcAll == 0) { $this->_TempDelete(); $sError = t("class-project-error-command-failed"); $this->_logaction($sError, __FUNCTION__, "error"); $sReturn.=$oHtml->getBox("error", $sError . $sReturn); $this->_sendMessage($sError."\n".t('phase').': '.$sPhase); return $sReturn; } $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); // -------------------------------------------------- // synch packages // -------------------------------------------------- // $sReturn.=$this->_execAndSend("ln -s $sLinkTarget $sLinkName"); if (array_key_exists('mirrorPackages', $this->_aConfig) && count($this->_aConfig['mirrorPackages'])) { foreach ($this->_aConfig['mirrorPackages'] as $sLabel => $aTarget) { $sReturn.='<h3>' . sprintf(t("class-project-info-deploy-synching-package"), $sLabel) . "</h3>"; if (array_key_exists('type', $aTarget)) { $sCmd = false; // $sSource=$this->_aConfig["packageDir"]."/$sPhase/*"; $sSource = $sRepoLink; $sTarget = $aTarget['target'] . "/$sPhase"; switch ($aTarget['type']) { case 'rsync': $sCmd = "ls -l $sSource 2>/dev/null && /usr/bin/rsync --delete -rLv $sSource $sTarget"; break; default: $sReturn.=sprintf(t("class-project-info-deploy-skip-sync"), $aTarget['type']) . "<br>"; break; } // switch if ($sCmd) { /* if ($aTarget['runas']) { $sCmd="su - " . $aTarget['runas'] . " -c \"" . $sCmd . "\""; } * */ $sReturn.=$this->_execAndSend($sCmd); $this->_TempFill($sReturn); } } } // foreach } $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); // -------------------------------------------------- // run action to install // -------------------------------------------------- $sDeploymethod = array_key_exists("deploymethod", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["deploymethod"] : "none"; $sTargethosts = array_key_exists("hosts", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["hosts"] : ''; $sReturn.='<h3>' . t("class-project-info-deploy-start-by-method") . ' :: ' . $sDeploymethod . '</h3>' . '<p>' . t("deploymethod-$sDeploymethod") . '<br>' . t("phase-targethosts") . ': ' . ($sTargethosts ? $sTargethosts : t("none")) . '</p>' ; if ($sDeploymethod === "none" || !$sTargethosts) { $sReturn.=t("class-project-info-deploy-start-by-method-skip") . "<br>"; } else { $aTargethosts = explode(',', $sTargethosts); foreach ($aTargethosts as $sTargethost) { $sReturn.='<h4>' . $sDeploymethod . ' - ' . $sTargethost . '</h4>'; $sCmd = ''; switch ($sDeploymethod) { case 'puppet': $sCmd = 'ssh ' . $this->_aConfig["installPackages"]["user"] . '@' . $sTargethost . ' ' . $this->_aConfig["installPackages"]["command"]; break; ; // TODO: we don't have any proxy yet case 'sshproxy__AS_EXAMPLE_ONLY': $sCmd = 'ssh ' . $this->_aConfig["installPackages"]["sshproxy"]["user"] . '@' . $this->_aConfig["installPackages"]["sshproxy"]["host"] . ' ' . sprintf($this->_aConfig["installPackages"]["sshproxy"]["command"], $sTargethost); break; ; } if ($sCmd) { // $sReturn.=$sCmd.'<br>'; $sReturn.=$this->_execAndSend("$sCmd"); } } } $aActionList['iActive'] ++; $this->_TempFill($sReturn, $aActionList); $sReturn.="<br>"; $sReturn.=$oHtml->getBox("success", t("class-project-info-deploy-successful")); $this->_sendMessage(t("class-project-info-deploy-successful")."\nphase: ${sPhase}\n"); $this->_logaction(t('finished') . " deploy($sPhase, $bIgnoreDeploytimes) " . t("class-project-info-deploy-successful"), __FUNCTION__, "success"); $this->_TempDelete(); return $sReturn; } /** * accept a the installed version in a phase and put this version * to the queue of the next phase. * @param string $sPhase which queue of which phase we want to install in server * @return type */ public function accept($sPhase) { $oHtml = new htmlguielements(); $this->log(__FUNCTION__ . " start"); if (!$this->oUser->hasPermission("project-action-accept") && !$this->oUser->hasPermission("project-action-accept-$sPhase") ) { return $this->oUser->showDenied(); } $sReturn = "<h2>" . t("accept") . " " . $this->getLabel() . " :: $sPhase</h2>"; $this->_logaction(t('starting') . " accept($sPhase)", __FUNCTION__); if (!$this->canAcceptPhase($sPhase)) { $sError = sprintf(t("class-project-error-accept-impossible"), $sPhase); $this->_logaction($sError, __FUNCTION__, "error"); return $sReturn . $oHtml->getBox("error", $sError); } $sReturn.="<h3>" . sprintf(t("class-project-info-accept-overview"), $sPhase) . "</h3>"; $this->_TempFill($sReturn); $aInfos = $this->getPhaseInfos($sPhase); $sVersion = $aInfos["deployed"]["version"]; $sNext = $this->getNextPhase($sPhase); // $sReturn.='<pre>' . print_r($aInfos["deployed"], true) . '</pre>'; $sReturn.=$oHtml->getBox("info", sprintf(t("class-project-info-accept-version-and-next-phase"), $sVersion, $sNext)); $this->_logaction(t('finished') . " accept($sPhase) " . sprintf(t("class-project-info-accept-version-and-next-phase"), $sVersion, $sNext), __FUNCTION__, "success"); $sReturn.=$this->queue($sNext, $sVersion); $this->_TempFill($sReturn); $this->_TempDelete(); return $sReturn; } /** * save POSTed data as project config * @return boolean */ public function saveConfig($aData = false) { $this->log(__FUNCTION__ . " start"); if (!$this->oUser->hasPermission("project-action-setup")) { return $this->oUser->showDenied(); } $this->_logaction(t('starting') . " saveConfig(...)", __FUNCTION__); if (!$aData) { $aData = $_POST; } foreach (array('id', 'label', 'description', 'contact', 'build', 'fileprefix', 'phases') as $sKey) { if (!array_key_exists($sKey, $aData)) { $this->_logaction(t('abortet') . " missing key $sKey in savedata", __FUNCTION__, "error"); return false; } } $sId = $aData["id"]; // remove unwanted items foreach (array("setupaction", "prj", "id") as $s) { if (array_key_exists($s, $aData)) { unset($aData[$s]); } } // save json file if ($this->_aConfig['projects']['json']['active']) { // echo "IST <pre>" . print_r($this->_aPrjConfig, true) . "</pre>"; echo "NEU <pre>" . print_r($aData, true) . "</pre>"; die(); // make a backup of a working config $sCfgFile = $this->_getConfigFile($sId); $sBakFile = $this->_getConfigFile($sId) . ".ok"; copy($sCfgFile, $sBakFile); $bReturn = file_put_contents($sCfgFile, json_encode($aData)); $this->_aPrjConfig = json_decode(file_get_contents($this->_getConfigFile($sId)), true); } // save in ldap if ($this->_aConfig['projects']['ldap']['active']) { // TODO: echo "TODO: save in LDAP<br><pre>" . print_r($aData, 1) . "</pre>"; $sDn = 'documentIdentifier=' . $sId . ',' . $this->_aConfig['projects']['ldap']['DnProjects']; $aItem = array( 'objectClass' => array( 'document', 'hieraSource', 'top', ), 'hieraData' => array( 'cfg=' . json_encode($aData), 'updated=' . date("Y-m-d H:i:s") . ' by ' . $this->oUser->getUsername(), ) ); require_once("ldap.class.php"); $oLdapIML = new imlldap($this->_aConfig['projects']['ldap']); // $oLdapIML->debugOn(); if (!$oLdapIML->DnExists($sDn)) { if ($oLdapIML->objAdd($sDn, $aItem)) { echo 'OK, created in LDAP.<br>'; $bReturn = true; } else { echo 'ERROR, DN ' . $sDn . ' was not created in LDAP :-/<br>'; $bReturn = false; } } else { if ($oLdapIML->objUpdate($sDn, $aItem)) { echo 'OK, updated in LDAP.<br>'; $bReturn = true; } else { echo 'ERROR, DN ' . $sDn . ' was not updated in LDAP :-/<br>'; $bReturn = false; } } $oLdapIML->close(); } $this->_logaction(t('finished') . " saveConfig(...)", __FUNCTION__, "success"); $this->setProjectById($sId); $sMessage=($bReturn ? t("page-setup-info-settings-were-saved") : t("page-setup-error-settings-were-not-saved") ); $this->_sendMessage($sMessage); return $bReturn; } /** * create a new project; it returns the error message if it fails and * an empty string if it was successful. * @param string $sId id * @return string */ public function create($sId) { $this->log(__FUNCTION__ . " start"); if (!$this->oUser->hasPermission("project-action-create")) { return $this->oUser->showDenied(); } $this->_logaction(t('starting') . " create($sId)", __FUNCTION__); if (!$sId) { $sError = t("class-project-error-create-missing-id"); $this->_logaction(t('aborted') . " create($sId)" . $sError, __FUNCTION__, "error"); return $sError; } $s = preg_replace('/[a-z\-\_0-9]*/', "", $sId); if ($s) { $sError = sprintf(t("class-project-error-create-wrcng-chars-in-id"), $sId); $this->_logaction(t('aborted') . " create($sId)" . $sError, __FUNCTION__, "error"); return $sError; } if ($sId == "all") { $sError = sprintf(t("class-project-error-create-id-has-reserved-name"), $sId); $this->_logaction(t('aborted') . " create($sId)" . $sError, __FUNCTION__, "error"); return $sError; } if (array_search($sId, $this->getProjects()) !== false) { $sError = sprintf(t("class-project-error-create-id-exists"), $sId); $this->_logaction(t('aborted') . " create($sId)" . $sError, __FUNCTION__, "error"); return $sError; } // reset config and create a skeleton $this->_readConfig(); $this->_aConfig["id"] = $sId; $this->_aPrjConfig = array( "id" => $sId, // for saveConfig "label" => "$sId", "fileprefix" => "$sId", "description" => '', "contact" => '', "build" => array( "type" => "", "ssh" => "", "auth" => "", "webaccess" => "", ), "phases" => array( "preview" => array(), "stage" => array(), "live" => array(), ), ); $this->_verifyConfig(); // check skeleton $bReturn = $this->saveConfig($this->_aPrjConfig); if (!$bReturn) { $sError = t("class-project-error-create-save-failed"); $this->_logaction(t('aborted') . " create($sId)" . $sError, __FUNCTION__, "error"); return $sError; } // alles OK - dann leeren String $this->_logaction(t('finished') . " create($sId)", __FUNCTION__, "success"); return ""; } /** * delete a project; it returns a string with errormessage; false = no error * @param array $aOptions * @return boolean|string */ public function delete($aOptions = array()) { $this->log(__FUNCTION__ . " start"); if (!$this->oUser->hasPermission("project-action-delete")) { return $this->oUser->showDenied(); } $sCfgfile = $this->_getConfigFile($this->_aConfig["id"]); if (!file_exists($sCfgfile)) { return t("class-project-error-delete-project-no-configfile"); } $this->_logaction(t('starting') . " delete()", __FUNCTION__); // (array("bRemoveRepolinks", "bRemoveArchive", "bRemoveConfig") // --- remove links in phases directory to built archives if (array_key_exists("bRemoveRepolinks", $aOptions) && $aOptions["bRemoveRepolinks"]) { echo "DELETE Repo-Links ...<br>"; foreach (array_keys($this->getPhases()) as $sPhase) { foreach (array_keys($this->_aPlaces) as $sPlace) { $sLink = $this->_getFileBase($sPhase, $sPlace); if (file_exists($sLink)) { echo "Removing $sLink ($sPhase - $sPlace)...<br>"; if (!unlink($sLink)) { $sError = t("class-project-error-delete-project-deletion-failed-data"); $this->_logaction(t('aborted') . " " . $sError, __FUNCTION__); return $sError; } } } } } if (array_key_exists("bRemoveArchive", $aOptions) && $aOptions["bRemoveArchive"]) { echo "DELETE built Archives ...<br>"; $this->cleanupArchive(true); // true to delete all } if (array_key_exists("bRemoveConfig", $aOptions) && $aOptions["bRemoveConfig"]) { echo "DELETE Config ...<br>"; // echo "config file: $sCfgfile<br>"; if (file_exists($sCfgfile . ".ok")) { // echo "Delete ${sCfgfile}.ok<br>"; unlink($sCfgfile . ".ok"); } if (file_exists($sCfgfile)) { // echo "Delete ${sCfgfile}<br>"; if (!unlink($sCfgfile)) { $sError = t("class-project-error-delete-project-deletion-failed-configfile"); $this->_logaction(t('aborted') . " " . $sError, __FUNCTION__); return $sError; } } } $this->_sendMessage(t('finished') . " delete()"); $this->_logaction(t('finished') . " delete()", __FUNCTION__, "success"); return false; } // ---------------------------------------------------------------------- // RENDERING // ---------------------------------------------------------------------- /** * return html code for a div with background color based on a checksum of the given text * @param string $sText text that is used for checksum; if false ist returns a gray * @param string $sContent optional: text to show * @return string */ private function _getChecksumDiv($sText, $sContent='') { if ($sText){ // color ranges in decimal values for RGB from ... to $iFgStart=60; $iFgEnd=160; $iBgStart=200; $iBgEnd=250; // deivider: 3 digits of md5 will be extracted $iFgDivider=16*16*16/($iFgEnd-$iFgStart); $iBgDivider=16*16*16/($iBgEnd-$iBgStart); $sHash=md5($sText); $sColor='' . 'color: rgba(' . ($iFgStart + round(hexdec(substr($sHash,0,3))/$iFgDivider)) . ',' . ($iFgStart + round(hexdec(substr($sHash,3,3))/$iFgDivider)) . ',' . ($iFgStart + round(hexdec(substr($sHash,6,3))/$iFgDivider)) . ',' . '1' . ');' . 'background: rgba(' . ($iBgStart + round(hexdec(substr($sHash,0,3))/$iBgDivider)) . ',' . ($iBgStart + round(hexdec(substr($sHash,3,3))/$iBgDivider)) . ',' . ($iBgStart + round(hexdec(substr($sHash,6,3))/$iBgDivider)) . ',' . '1' . ');' ; } else { $sColor = "color: #888; background: #ccc;"; } return '<div style="' . $sColor . '; border-top: 3px solid; ">'.($sContent ? $sContent : ' ').'</div>'; } /** * generate css color based on a checksum of the given text * @param string $sText text that is used for checksum * @return string */ private function _getChecksumColor($sText, $sFormat = "hex", $fAlpha = 1.0) { $sReturn = ''; if ($sText){ $sHash=md5($rssItem["feedtitle"]); $iStartFg=100; $iStartBg=220; $sColor='' . '' . 'color: rgba(' . ($iStartFg + round(hexdec(substr($sHash,0,2))/4)) . ',' . ($iStartFg + round(hexdec(substr($sHash,2,2))/4)) . ',' . ($iStartFg + round(hexdec(substr($sHash,4,2))/4)) . ');' . 'background: rgba(' . ($iStartBg + round(hexdec(substr($sHash,0,2))/8)) . ',' . ($iStartBg + round(hexdec(substr($sHash,2,2))/8)) . ',' . ($iStartBg + round(hexdec(substr($sHash,4,2))/8)) . ');' ; $s = md5($sText); $sRH = substr($s, 0, 2); $sGH = substr($s, 2, 2); $sBH = substr($s, 4, 2); } else { $sReturn = "background: #aaaaaa;"; } switch ($sFormat) { case "rgba": $sReturn = "background: rgba(" . hexdec($sRH) . ", " . hexdec($sGH) . ", " . hexdec($sBH) . ", " . $fAlpha . ")"; break; default: $sReturn = "background: #$sRH$sGH$sBH"; break; } return $sReturn; } /** * get html code for the colored bar on top of each phase detail items * @param string $sPhase phase of a project * @param string $sPlace place in the given phase * @return string */ private function _renderBar($sPhase, $sPlace) { $aDataPhase = $this->getPhaseInfos($sPhase); $aData = $aDataPhase[$sPlace]; if (!array_key_exists("revision", $aData)) { return false; } return $this->_getChecksumDiv($aData["revision"]); } 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>' ; foreach ($aData['_hosts'] as $sHostname => $aHostinfos) { $oUpdateDate = date("U", strtotime($aHostinfos['time'])); $iAgeUpdate = round((date("U") - $oUpdateDate) / 60); $sAge = $iAgeUpdate < 60 * 60 * 13 ? $iAgeUpdate . " min" : "??"; $sReturn.= '<div class="host">' . $this->_getChecksumDiv( $aHostinfos['_data']['revision'], $oHtml->getIcon('host').'<br>' . $sHostname ) . "($sAge)" . '</div>' ; } $sReturn.= '</div><div style="clear: both;"></div>'; } return $sReturn; } /** * get html code for list of hosts in a phase * @param string $sPhase phase of a project * @return string */ private function _renderHosts($sPhase) { $aDataPhase = $this->getPhaseInfos($sPhase); if (is_array($aDataPhase) && array_key_exists('deployed', $aDataPhase)) { return $this->_renderHostsData($aDataPhase['deployed']); } return ''; } /** * get html code for list of files in a phase * @param string $sPhase phase of a project * @return string */ private function _renderFiles($sPhase) { $sReturn = ''; $aFiles = $this->getBuildfilesByPlace($sPhase, 'ready2install'); if (!$aFiles || !$aFiles['filecount']) { return ''; } $sReturn.='<strong>' . t("filelist") . '</strong> (' . $aFiles['filecount'] . '):<br>'; foreach ($aFiles['files'] as $sFilename => $aData) { $sReturn.='<div class="file file-' . $aData['type'] . ' fileext-' . $aData['extension'] . '" title="' . $sFilename . ' (' . $aData['type'] . ')">' . $aData['icon'] . $sFilename // . ' ('.$aData['type'].')' . '</div>' ; } $sReturn.='(' . $aFiles['totalsize-hr'] . ')'; return $sReturn; } /** * render html for a colored link to any project action * @param string $sFunction name of the action; one of accept|build|cleanup|deploy|new|overview|phase|rollback|setup * @param string $sPhase current phase where to place the link * @return string */ public function renderLink($sFunction, $sPhase = false, $sVersion = false) { $sFirst = $this->getNextPhase(); $sNext = $this->getNextPhase($sPhase); $oHtml = new htmlguielements(); $aLinkdata = array( 'default' => array('class' => ''), 'accept' => array('class' => $sNext, 'hint' => sprintf(t("accept-hint"), $sPhase, $sNext), 'label' => t('accept'), ), 'build' => array('class' => $sFirst, 'hint' => sprintf(t("build-hint"), $sFirst), 'label' => t('build'), 'role' => 'buildProject' ), 'cleanup' => array('class' => ''), 'deploy' => array('class' => $sPhase, 'hint' => sprintf(t("deploy-hint"), $sPhase), 'label' => t('deploy'), ), 'new' => array( 'hint' => t("new-project-hint"), 'label' => t('new-project'), ), 'overview' => array('class' => '', 'hint' => t('menu-project-home') . ' [' . $this->getLabel() . ']', 'label' => $this->getLabel() ), 'phase' => array('icon' => 'glyphicon glyphicon-chevron-right', 'class' => $sPhase, 'hint' => sprintf(t('phase-details-hint'), $sPhase), 'label' => t('phase-details') ), 'rollback' => array('class' => $sPhase, 'hint' => sprintf(t('rollback-hint'), $sPhase, $sVersion), 'label' => t('rollback') ), 'setup' => array('class' => $sPhase, 'hint' => sprintf(t('setup-hint'), $sPhase, $sVersion), 'label' => t('setup') ), ); /* if (!$this->oUser->hasRole("project-action-$sFunction")){ // $sClass .= ' disabled'; // return '<span title="no permission [project-action-'.$sFunction.']">[ ]</span>'; } * */ // fuer wen ist der Link $sRole = ''; $sOnMouseover = ''; $sOnMouseout = ''; switch($sFunction){ case 'accept'; $sRole = 'developer'; if ($sNext == "live") { $sRole = 'pl'; // $aLinkdata[$sFunction]['icon']='glyphicon glyphicon-star'; } $sOnMouseover = '$(\'.td-phase-' . $sNext . '.td' . $this->_aConfig["id"] . '\').addClass(\'highlight\');'; $sOnMouseout = '$(\'.td-phase-' . $sNext . '.td' . $this->_aConfig["id"] . '\').removeClass(\'highlight\');'; break; case 'build'; $sRole = 'developer'; $sOnMouseover = '$(\'.td-phase-' . $sNext . '.td' . $this->_aConfig["id"] . '\').addClass(\'highlight\');'; $sOnMouseout = '$(\'.td-phase-' . $sNext . '.td' . $this->_aConfig["id"] . '\').removeClass(\'highlight\');'; break; case 'deploy'; $sRole = 'developer'; $sOnMouseover = '$(\'.td-phase-' . $sPhase . '.td-place-ready2install.td' . $this->_aConfig["id"] . '\').addClass(\'highlight\');' .'$(\'.td-phase-' . $sPhase . '.td-place-deployed.td' . $this->_aConfig["id"] . '\').addClass(\'highlight\');' ; $sOnMouseout = '$(\'.td-phase-' . $sPhase . '.td-place-ready2install.td' . $this->_aConfig["id"] . '\').removeClass(\'highlight\');' .'$(\'.td-phase-' . $sPhase . '.td-place-deployed.td' . $this->_aConfig["id"] . '\').removeClass(\'highlight\');' ; break; } // $sClass = $sPhase; $sIconClass = (array_key_exists($sFunction, $aLinkdata)) ? $aLinkdata[$sFunction]['icon'] : $aLinkdata['default']['icon']; $sHint = ( array_key_exists($sFunction, $aLinkdata) && array_key_exists("hint", $aLinkdata[$sFunction]) ) ? $aLinkdata[$sFunction]['hint'] : ""; $sLabel = ( array_key_exists($sFunction, $aLinkdata) && array_key_exists("label", $aLinkdata[$sFunction]) ) ? $aLinkdata[$sFunction]['label'] : $sFunction; $sClass = ( array_key_exists($sFunction, $aLinkdata) && array_key_exists("class", $aLinkdata[$sFunction]) ) ? $aLinkdata[$sFunction]['class'] : ''; if ($sRole) { $sClass .= " role role" . $sRole; } $sLink = "/deployment/" . ($this->_aConfig["id"] ? $this->_aConfig["id"] : 'all/setup') . "/"; if ($sFunction != "overview") { $sLink.="$sFunction/"; } if ($sPhase) { $sLink.="$sPhase/"; } if ($sVersion) { $sLink.="$sVersion/"; } if (!$this->oUser->hasPermission("project-action-$sFunction")) { // $sClass .= ' disabled'; return '<span title="no permission [project-action-' . $sFunction . '] for ' . $this->oUser->getUsername() . '">[ <i class="' . $sIconClass . '"></i> ' . $sLabel . ' ]</span>'; } return $oHtml->getLinkButton(array( 'href' => $sLink, 'title' => $sHint, 'class' => 'btn btn-default ' . $sClass, 'type' => $sFunction, 'onmouseover' => $sOnMouseover, 'onmouseout' => $sOnMouseout, 'label' => $sLabel, )); // return '<a href="' . $sLink . '" ' . $sOnMouseover . ' title="' . $sHint . '" class="btn btn-default ' . $sClass . '"><i class="' . $sIconClass . '"></i> ' . $sLabel . '</a>'; } /** * render html for the project overview; it shows the defined phases for * the project as a table * @return type */ public function renderPhaseInfo() { $sRow1 = false; $sRow2 = false; foreach ($this->getActivePhases() as $sPhase) { $sRow1.='<th class="' . $sPhase . '">' . $sPhase . '</th>'; $sRow2.='<td class="' . $sPhase . '">' . t('url') . ': <a href="' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '">' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '</a><br>' . '<br>' . t('deploytimes') . ':<br>'; if (count($this->_getDeploytimes($sPhase))) { $sRow2.=implode("<br>", $this->_getDeploytimes($sPhase)); } else { $sRow2.=t('deploytimes-immediately'); } $sRow2.='<br>' . $this->renderLink("phase", $sPhase) . $this->_renderHosts($sPhase) . '<br>' . $this->_renderFiles($sPhase) . '</td>'; } return '<table><thead><tr>' . $sRow1 . '</tr></thead><tbody><tr>' . $sRow2 . '</tr></tbody></table>'; } /** * render html for a place of a phase * @param string $sPhase phase * @param string $sPlace name of the place; one of onhold|ready2install|deployed * @param bool $bActions draw action links (deploy, accept) on/ off * @param bool $bLong use long variant to display infos? * @return string|boolean */ public function renderPhaseDetail($sPhase, $sPlace, $bActions = true, $bLong = true) { if (!$sPhase) { return false; } if (!$sPlace) { return false; } if (!$this->isActivePhase($sPhase)) { return false; } if (!array_key_exists($sPlace, $this->_aPlaces)) { return false; } $sReturn = false; $oHtml = new htmlguielements(); $aDataPhase = $this->getPhaseInfos($sPhase); $aData = $aDataPhase[$sPlace]; // foreach($aDataPhase[$sPlace] as $aData) { if (array_key_exists("ok", $aData) && array_key_exists("version", $aData)) { // TODO: getChecksumDiv anhand der Repo-Versionsnummer - dann kann man beim build auch die Farbe mit dem Repo HEAD vergleichen // time $sDateFormat = "d.m.Y H:i"; $oPkgDate = date("U", strtotime($aData["date"])); /* $iAge=date("U")-$oPkgDate; $sAgeClass=""; if ($iAge< 60*60*24*3){ $sAgeClass="last1d"; } if ($iAge< 60*60){ $sAgeClass="last1h"; } */ if ($bLong) { // long display of the revision // $sJsonUrl = $this->_getInfofile($sPhase, $sPlace); $sReturn .=$this->_getChecksumDiv( $aData["revision"], $oHtml->getIconByType('calendar') .' ' . date($sDateFormat, $oPkgDate) . $oHtml->getIconByType('branch') . t('branch') . ': ' . $aData["branch"] . '<br>' . $oHtml->getIconByType('revision') . t('revision') . ': ' . $this->_renderRevision($aData["revision"]) . '<br>' . $oHtml->getIconByType('comment') . t('commitmessage') . ':<br>' ) . '<pre>' . strip_tags($aData["message"], '<br>') . '</pre>' // . '<i class="glyphicon glyphicon-globe"></i> ' . t('url') . ': <a href="' . $sJsonUrl . '">' . $sJsonUrl . '</a><br>' ; if ($sPlace == "deployed" && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase])) { $sUrl = $this->_aPrjConfig["phases"][$sPhase]["url"]; $sReturn.=$oHtml->getIconByType('link-extern') . ' '. t('url') . ': <a href="' . $sUrl . '">' . $sUrl . '</a><br>'; } } else { $sReturn .= $this->_getChecksumDiv( $aData["revision"], $oHtml->getIconByType('calendar') .' ' . date($sDateFormat, $oPkgDate) ); if ($sPlace == "deployed" && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase])) { $sMore = $oHtml->getIconByType('link-extern').' ' . t('url') . ': <a href="' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '">' . $this->_aPrjConfig["phases"][$sPhase]["url"] . '</a><br>'; } $sReturn.=' ' . $this->renderInfoLink( $aData, array( 'title' => $this->getLabel() . " :: $sPhase :: $sPlace", 'more' => $sMore, ) ); } switch ($sPlace) { case "onhold": if (array_key_exists("phases", $this->_aConfig) && array_key_exists($sPhase, $this->_aConfig["phases"])) { // $sReturn .= print_r($this->_aConfig["phases"][$sPhase], true); if (count($this->_getDeploytimes($sPhase))) { $sReturn .= '<br><i class="glyphicon glyphicon-time"></i> ' . t('deploytimes') . ':<br>' . implode("<br>", array_values($this->_getDeploytimes($sPhase))) . '<br>'; } if ($bActions) { $sReturn .= ' ' . $this->renderLink("deploy", $sPhase); } } break; case "ready2install": break; case "deployed": if ($bActions && $this->canAcceptPhase($sPhase)) { $sReturn .= ' ' . $this->renderLink("accept", $sPhase); } break; default: break; } // $this->_getChecksumDiv($aData["revision"]) } else { if (array_key_exists("error", $aData)) { $sReturn.='' . $this->renderInfoLink(array('error' => $aData["error"]), array()) ; } else if (array_key_exists("warning", $aData)) { $sReturn.= '<div class="warning"><i class="glyphicon glyphicon-info-sign"></i> ' . t('warning') . ':<br>' . $aData["warning"] . '</div>'; } else { return false; // $sReturn.= t('empty'); } } // if // } // for return $sReturn; } /** * render html for a row with td for all places (first row) * @param string $sPhase phase (just needed for coloring) * @return string */ public function renderPlacesAsTd($sPhase) { $sRow1 = ''; foreach (array_keys($this->_aPlaces) as $sPlace) { $sRow1.='<td class="' . $sPhase . ' ' . $this->_aConfig["id"] . ' tdphase">' . t($sPlace) . '</td>'; } return $sRow1; } /** * render html for a row with td for all places of a phase * @param string $sPhase phase * @param bool $bActions draw action links (deploy, accept) on/ off * @param bool $bLong use long variant to display infos? * @return string|boolean */ public function renderAllPhaseDetails($sPhase, $bActions = true, $bLong = true) { if (!$sPhase) { return false; } if (!$this->isActivePhase($sPhase)) { return ' <td class="td-phase-' . $sPhase . ' ' . $this->_aConfig["id"] . '" colspan="' . count($this->_aPlaces) . '"> <div class="versioninfo center inactive"><i class="glyphicon glyphicon-ban-circle"></i> ' . t('inactive') . '</div> </td>'; } $sRow2 = false; $aRows = array(); $sLastPlace = ''; foreach (array_keys($this->_aPlaces) as $sPlace) { $aRows[$sPlace] = $this->renderPhaseDetail($sPhase, $sPlace, $bActions, $bLong); // generate ">>" sign for lastly generated td if ($sLastPlace && array_key_exists("version", $this->_aData["phases"][$sPhase][$sLastPlace]) && array_key_exists("version", $this->_aData["phases"][$sPhase][$sPlace]) && $this->_aData["phases"][$sPhase][$sLastPlace]["version"] == $this->_aData["phases"][$sPhase][$sPlace]["version"] && !$bLong ) { $aRows[$sLastPlace] = $this->_renderBar($sPhase, $sPlace) . "»"; } $sLastPlace = $sPlace; } foreach (array_keys($this->_aPlaces) as $sPlace) { $sRow2.='<td class=" td-phase-'.$sPhase.' td-place-'.$sPlace.' td' . $this->_aConfig["id"] . '">' . $aRows[$sPlace] . '</td>'; } return $sRow2; } /** * return html code for the installed version in the repository * @param boolean $bRefresh optional: refresh flag; default: use cached information * @return string */ public function renderRepoInfo($bRefresh=false) { $oHtml = new htmlguielements(); $sReturn = ""; switch ($this->_aPrjConfig["build"]["type"]) { case "git": $aRepodata = $this->getRepoRevision($bRefresh); if (array_key_exists("revision", $aRepodata)) { $sReturn.=$this->_getChecksumDiv($aRepodata["revision"], $oHtml->getIconByType('branch') . t('branch') . ': ' . (array_key_exists("branch", $aRepodata) ? $aRepodata["branch"] : '-') . '<br>' . $oHtml->getIconByType('revision') . t('revision') . ': ' . $this->_renderRevision($aRepodata["revision"]) . '<br>' . $oHtml->getIconByType('comment') . t('commitmessage') . ':<br>' ) ."<pre>" . strip_tags($aRepodata["message"], '<br>') . "</pre>"; } else { $sReturn .= $oHtml->getBox("error", sprintf(t('class-project-error-no-repoaccess'), $aRepodata["error"])) . $this->renderLink("setup") . '<br>'; } break; default: $sReturn .= $oHtml->getBox("error", sprintf(t('class-project-error-wrong-buildtype'), $this->_aPrjConfig["build"]["type"])); } if (array_key_exists("url", $this->_aPrjConfig["build"])) { $sReturn.=t('repository-url') . ': ' . $this->_aPrjConfig["build"]["url"] . '<br>'; } if (array_key_exists("webaccess", $this->_aPrjConfig["build"])) { $sReturn.=t('repository-access-browser') . ':<br><a href="' . $this->_aPrjConfig["build"]["webaccess"] . '">' . $this->_aPrjConfig["build"]["webaccess"] . '</a><br>'; } return $sReturn; } /** * get html code for a link to the commit * (works for guithub and gitlab instances) * * @param string $sRevision * @return string */ public function _renderRevision($sRevision) { $sUrl = str_replace('/tree/master', '', $this->_aPrjConfig["build"]["webaccess"]) . '/commit/' . $sRevision; return '<a href="' . $sUrl . '">' . $sRevision . '</a>'; return $sUrl; } /** * render html code for info link that shows popup with metadata on mouseover * @param array $aInfos metainfos of the package (from json file) * @param array $aOptions options * title - tile in popover; default: empty * label - link label; default: empty (results in Infos|ERROR) * more - additional infos in popover; default: empty * hpos - horizontal position; one of left|right; default: right * @return string */ public function renderInfoLink($aInfos, $aOptions = array()) { $sReturn = ''; $bIsError = false; $oHtml = new htmlguielements(); $sInfos.=''; if (array_key_exists("title", $aOptions) && $aOptions["title"]) { $sTitle.=$aOptions["title"]; } if (array_key_exists("ok", $aInfos)) { $sLinktitle = t('infos'); if (array_key_exists("message", $aInfos)) { $sInfos.=$this->_getChecksumDiv($aInfos["revision"], $oHtml->getIconByType('calendar') . t('build-from') . ' ' . date("d.m.Y H:i:s", strtotime($aInfos["date"])) . '<br>' . $oHtml->getIconByType('branch') . t('branch') . ': ' . $aInfos["branch"] . '<br>' . $oHtml->getIconByType('revision') . t('revision') . ': ' . $this->_renderRevision($aInfos["revision"]) . '<br>' . $oHtml->getIconByType('comment') . t('commitmessage') . ': ' ) . '<pre>' . strip_tags($aInfos["message"], '<br>') . '</pre>'; if (array_key_exists("more", $aOptions)) { $sInfos.=$aOptions["more"]; } } } else { $bIsError = true; if (!$sTitle) { $sTitle.=' ' . t('error'); } $sLinktitle = t('error'); $sInfos = $aInfos["error"]; } $sInfos.=$this->_renderHostsData($aInfos); if (array_key_exists("label", $aOptions) && $aOptions["label"]) { $sLinktitle.=$aOptions["label"]; } // render html $sId = 'info' . md5($sInfos); $sReturn = '<a href="#" class="btn ' . ($bIsError ? 'btn-danger' : 'btn-default') . '" title="" onclick="showIdAsModalMessage(\'' . $sId . '\'); return false;">' // . '<i class="fa fa-info"></i> ' . $sLinktitle . '</a><div id="' . $sId . '" style="display: none;" '; if (array_key_exists("hpos", $aOptions)) { $sReturn.=' class="' . $aOptions["hpos"] . '"'; } $sReturn.='>'; if ($sTitle) { $sReturn.='<span class="title">' . $sTitle . '</span><br><br>'; } $sReturn.=$sInfos . '</div>'; if ($bIsError) { // $sReturn = '<div class="error">' . $sReturn . '</div>'; } return $sReturn; } /** * return html code for a list of all built packages and their usage * @return string */ public function renderVersionUsage() { $oHtml = new htmlguielements(); $sReturn = false; $sRowHead1 = false; $sRowHead2 = '<td></td>'; $aAllVersions = $this->_getVersionUsage(); if (!count($aAllVersions)) { return $oHtml->getBox("info", t('class-project-info-no-package')); } foreach ($this->getActivePhases() as $sPhase) { $sRowHead1.='<th class="' . $sPhase . '" colspan="' . (count($this->_aPlaces) + 1) . '">' . $sPhase . '</th>'; $sRowHead2.='<td></td>' . $this->renderPlacesAsTd($sPhase); } krsort($aAllVersions); foreach ($aAllVersions as $sVersion => $aData) { $sReturn.='<tr>'; $sInfos = $this->renderInfoLink($aData["info"], array('hpos' => 'left')); $sReturn.='<td>' . $this->_getChecksumDiv( $aData['info']['revision'], $oHtml->getIconByType('calendar') . t('build-from') . ': ' . $sVersion .'<br>' . $oHtml->getIconByType('branch') . t('branch') . ': ' . $aData['info']["branch"] . '<br>' . $oHtml->getIconByType('revision') . t('revision') . ': ' . $this->_renderRevision($aData['info']["revision"]) . '<br>' ) . '</td><td>' . ' ' . $sInfos . ' ' . '</td>' ; foreach ($this->getActivePhases() as $sPhase) { $sTLine = ''; $bCanRollback = $aData["rollback"][$sPhase]; // $sReturn.=$aData["rollback"][$sPhase] ? '<td>'.$this->renderLink("rollback", $sPhase, $sVersion).'</td>' : '<td>Rollback NOT possible</td>'; // $sReturn.=$aData["rollback"][$sPhase] ? '<td> Y </td>' : '<td> N </td>'; $sReturn.='<td> </td>'; foreach (array_keys($this->_aPlaces) as $sPlace) { $bFound = false; $sReturn.=$aData["usage"][$sPhase][$sPlace] ? '<td class="' . $sPhase . '" style="text-align: center;">' . $this->_getChecksumDiv($aData['info']['revision'], 'X') . '</td>' : '<td> </td>' ; } } $sReturn.='</tr>'; } $sReturn = t('class-project-info-table-packages') . '<br><br>' . '<table>' . '<thead><tr><td>Version</td><td></td>' . $sRowHead1 . '</tr><tr><td>' . $sRowHead2 . '</tr></thead>' . '<tbody>' . $sReturn . '</tbody>' . '</table>'; return $sReturn; } /** * render graphical overview of process (in project overview) * @return string */ public function renderVisual() { $sReturn = ''; $sContinue = '<span style="font-size: 300%; color:#ace;">»»</span><br><br>'; $sRepoBar = ''; $aRepodata = $this->getRepoRevision(); if (array_key_exists("revision", $aRepodata)) { $sRepoBar = $this->_getChecksumDiv($aRepodata["revision"]); } else { $sRepoBar = '<span class="error">' . t("error") . '</span>'; } $sPackagebar = ''; $aVersions = $this->_getVersionUsage(); foreach ($aVersions as $sVersion => $aData) { $sBar = $aData["info"]["revision"] ? $this->_getChecksumDiv($aData["info"]["revision"]) : ''; $sPackagebar.='<span title="' . $sVersion . '" style="float: left; background:#eee; height: 3px; width:' . (100 / count($aVersions)) . '%">' . $sBar . ' </span>'; } $sPhaseImg = ''; $sLastPhase = ''; foreach ($this->getActivePhases() as $sPhase) { if ($sPhaseImg) { $sAction = $sContinue; if ($this->canAcceptPhase($sLastPhase)) { $sAction .= $this->renderLink("accept", $sLastPhase); } $sPhaseImg.='<div class="action">' . $sAction . '</div>'; } $sLastPhase = $sPhase; $sFullbar = ''; foreach (array_keys($this->_aPlaces) as $sPlace) { $sFullbar.='<span title="' . $this->_aPlaces[$sPlace] . '" style="float: left; background:#eee; height: 3px; width:' . (100 / count($this->_aPlaces)) . '%">' . $this->_renderBar($sPhase, $sPlace) . ' </span>'; } // $sDetail = $sFullbar . '<br><a href="#h3phases" class="scroll-link">' . $sPhase . '</a>'; $sDetail = $sFullbar . '<br>' . $sPhase; $sPhaseImg.=' <div class="process ' . $sPhase . '"> <div class="details">' . $sDetail . ' </div> <div><img src="/deployment/images/process/bg_phase.png" alt="' . t("phase") . ' ' . $sPhase . '"></div> </div>'; } $oHtml = new htmlguielements(); $sReturn = ' <div class="visualprocess"> <div class="process box"> <div class="title">' . $oHtml->getIcon('repository') . t("versioncontrol") . '</div> <div class="details"> ' . $sRepoBar . '<br> <!-- <a href="#h3repo" class="scroll-link">' . t("repositoryinfos") . '</a><br> --> ' . t("repositoryinfos") . '<br> <strong> ' . $this->_aPrjConfig["build"]["type"] . '</strong> ' . preg_replace('/.*\@(.*):.*/', '($1)', $this->_aPrjConfig["build"]["url"]) . ': <strong title="' . t('branch-select') . '">' . count($this->getRemoteBranches()) . '</strong>' . '<br> </div> <div> <img src="/deployment/images/process/bg_vcs.png" alt="' . t("versioncontrol") . '"> </div> </div> <div class="process"> <div class="title"> </div> <div class="action">' . $sContinue . t("build-hint-overview") . '<br><br>' . ($this->canAcceptPhase() ? $this->renderLink("build") : '') . '</div> </div> <div class="process box"> <div class="title">' . $oHtml->getIcon('package') . t("archive") . '</div> <div class="details"> ' . $sPackagebar . '<br> <!-- <a href="#h3versions" class="scroll-link">' . t("packages") . '</a><br> --> ' . t("packages") . '<br> (<strong>' . count($this->_getVersionUsage()) . '</strong>) </div> <div><img src="/deployment/images/process/bg_archive.png" alt="' . t("archive") . '"></div> </div> <div class="process"> <div class="title"> </div> <div class="action">'.$sContinue . sprintf(t("queue-hint-overview"), $this->getNextPhase()).'</div> </div> <div class="process phases box"> <div class="title">' . $oHtml->getIcon('phase') . t("phases") . '</div> ' . ($sPhaseImg ? $sPhaseImg : '<div class="process">' . t("none") . '</div>') . ' </div> </div> '; return $sReturn; } /** * return html code for the setup form of an exsiting project * @return string */ public function renderProjectSetup() { if (!$this->oUser->hasPermission("project-action-setup")) { return $this->oUser->showDenied(); } $oHtml = new htmlguielements(); $sMessages = ''; require_once ("formgen.class.php"); $aSelectSlack = array( 'type' => 'hidden', 'name' => 'messenger[slack]', 'value' => false, ); if ( array_key_exists('messenger', $this->_aConfig) && array_key_exists('slack', $this->_aConfig['messenger']) && array_key_exists('presets', $this->_aConfig['messenger']['slack']) && count(array_key_exists('presets', $this->_aConfig['messenger']['slack']['presets'])) ) { $aSelectSlack = array( 'type' => 'select', 'name' => 'messenger[slack]', 'label' => t("messenger-slack"), 'options' => array( OPTION_NONE => array( 'label' => t('none'), ), '' => array( 'label' => '- - - - - - - - - - - - - - - - - - - - ', ), ), ); foreach($this->_aConfig['messenger']['slack']['presets'] as $sSlackUrl=>$aSlackCfg){ $bActive=$this->_aPrjConfig['messenger']['slack'] === $sSlackUrl; $aSelectSlack['options'][$sSlackUrl] = array( 'label' => array_key_exists('label', $aSlackCfg) ? $aSlackCfg['label'] : $sSlackUrl, 'selected' => $bActive ? 'selected' : false, ); } } $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('foremanapi.class.php'); $oForeman = new ForemanApi($this->_aConfig['foreman']); // $oForeman->setDebug(1); // $oForeman->selfcheck(); die(__FUNCTION__); $aForemanHostgroups = $oForeman->read(array( 'request' => array( array('hostgroups'), // array('operatingsystems',4), ), 'response' => array( 'id', 'title' ), )); $aSelectForemanGroups = array( 'type' => 'select', 'name' => 'deploy[foreman][hostgroup]', 'label' => t("foreman-hostgroup"), 'options' => array( OPTION_NONE => array( 'label' => t('none'), ), '' => array( 'label' => '- - - - - - - - - - - - - - - - - - - - ', ), ), ); if (count($aForemanHostgroups)) { foreach ($aForemanHostgroups as $aItem) { $bActive=$iForemanHostgroupDefault === (int) $aItem['id']; $aSelectForemanGroups['options'][$aItem['id']] = array( 'label' => $aItem['title'], 'selected' => $bActive ? 'selected' : false, ); $sForemanHostgroupDefault = $bActive ? $aItem['title'] : $sForemanHostgroupDefault; } } } $i = 0; $aPrefixItem = count($this->getVersions()) ? array( 'type' => 'markup', 'value' => '<div class="form-group"> <label class="col-sm-2">' . t('fileprefix') . '</label> <div class="col-sm-10"> <input id="inputprefix" type="hidden" name="fileprefix" value="' . $this->_aPrjConfig["fileprefix"] . '"> ' . $this->_aPrjConfig["fileprefix"] . ' </div></div> ', ) : array( 'type' => 'text', 'name' => 'fileprefix', // 'disabled' => 'disabled', 'label' => t('fileprefix-label'), 'value' => $this->_aPrjConfig["fileprefix"], 'required' => 'required', 'validate' => 'isastring', 'pattern' => '[a-z0-9\-\_]*', 'size' => 100, 'placeholder' => '', ); $aRepodata = $this->getRepoRevision(); if (is_array($aRepodata) && array_key_exists("message", $aRepodata)) { $sRepoCheck = '<span class="ok">' . t('class-project-info-repoaccess') . '</span>'; } else { $sRepoCheck = '<span class="error">' . sprintf(t('class-project-error-no-repoaccess'), $aRepodata["error"]) . '</span>'; $sMessages.=$oHtml->getBox("error", sprintf(t('class-project-error-no-repoaccess'), $aRepodata["error"])); } // generate datalist with exisating ssh keys for auth field $sAuthListitems = ''; foreach ($this->_getSshKeys() as $sKey) { $sAuthListitems.='<option value="' . $sKey . '">'; } $aForms = array( 'setup' => array( 'meta' => array( 'method' => 'POST', 'action' => '?', ), 'validate' => array(), 'form' => array( 'input' . $i++ => array( 'type' => 'hidden', 'name' => 'setupaction', 'value' => 'save', ), 'input' . $i++ => array( 'type' => 'hidden', 'name' => 'id', 'value' => $this->_aConfig["id"], ), 'input' . $i++ => array( 'type' => 'markup', 'value' => '<div class="tabbable"> <ul class="nav nav-tabs"> <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="#tab4" data-toggle="tab">' . $oHtml->getIcon('raw-data').t('raw-data') . '</a></li> </ul> <div class="tab-content"> <div class="tab-pane active" id="tab1"> ', ), 'input' . $i++ => array( 'type' => 'text', 'name' => 'label', 'label' => t('projectname'), 'value' => $this->_aPrjConfig["label"], 'required' => 'required', 'validate' => 'isastring', 'size' => 100, 'placeholder' => 'Projekt', ), 'input' . $i++ => array( 'type' => 'text', 'name' => 'description', 'label' => t('projectdescription'), 'value' => $this->_aPrjConfig["description"], 'required' => 'required', 'validate' => 'isastring', 'size' => 100, 'placeholder' => '', ), 'input' . $i++ => array( 'type' => 'text', 'name' => 'contact', 'label' => t('contact'), 'value' => $this->_aPrjConfig["contact"], 'required' => 'required', 'validate' => 'isastring', 'size' => 100, 'placeholder' => '', ), 'input' . $i++ => array( 'type' => 'markup', 'value' => '<p>' . t('messenger') . '</p>', ), 'input' . $i++ => array( 'type' => 'text', 'name' => 'messenger[email]', 'label' => t("messenger-email"), 'value' => $this->_aPrjConfig["messenger"]["email"], 'validate' => 'isastring', 'size' => 100, 'placeholder' => '', ), 'input' . $i++ => $aSelectSlack, // -------------------------------------------------- 'input' . $i++ => array( 'type' => 'markup', 'value' => ' </div><div class="tab-pane" id="tab2"> <p>' . t('setup-hint-build') . '</p>', ), 'input' . $i++ => array( 'type' => 'text', 'name' => 'build[type]', 'label' => t("build-type"), 'value' => $this->_aPrjConfig["build"]["type"], 'required' => 'required', 'validate' => 'isastring', 'size' => 100, 'placeholder' => '', ), 'input' . $i++ => array( 'type' => 'text', 'name' => 'build[url]', 'label' => t("repository-url"), 'value' => $this->_aPrjConfig["build"]["url"], // 'required' => 'required', 'validate' => 'isastring', 'size' => 100, 'placeholder' => '', ), 'input' . $i++ => array( 'type' => 'text', 'name' => 'build[auth]', 'label' => t("repository-auth"), 'value' => $this->_aPrjConfig["build"]["auth"], // 'required' => 'required', 'list' => 'listauth', // listauth is the next form id below 'validate' => 'isastring', 'size' => 100, 'placeholder' => '', ), 'input' . $i++ => array( 'type' => 'markup', 'value' => '<datalist id="listauth">' . $sAuthListitems . '</datalist>', ), 'input' . $i++ => array( 'type' => 'markup', 'value' => '<div class="form-group">' . '<label class="col-sm-2"> </label><div class="col-sm-10">' . $sRepoCheck . '</div></div>', ), 'input' . $i++ => array( 'type' => 'text', 'name' => 'build[webaccess]', 'label' => t("repository-urlwebgui"), 'value' => $this->_aPrjConfig["build"]["webaccess"], 'validate' => 'isastring', 'size' => 100, 'placeholder' => '', ), 'input' . $i++ => $aPrefixItem, 'input' . $i++ => array( 'type' => 'markup', 'value' => '<div style="clear: both"></div>', ), // task#1498 - handle project without "public" directory 'input' . $i++ => array( 'type' => 'checkbox', 'name' => 'build[haspublic]', 'label' => t("repository-has-public-dir"), 'required' => false, 'validate' => 'isastring', 'options' => array( '1' => array( 'label' => t("yes"), 'checked' => (array_key_exists('haspublic', $this->_aPrjConfig["build"]) ? $this->_aPrjConfig["build"]["haspublic"] : 0), ), ), ), // -------------------------------------------------- 'input' . $i++ => array( 'type' => 'markup', 'value' => ' </div><div class="tab-pane" id="tab3"> <p>' . sprintf(t("class-project-info-setup-phaseinfos"), $this->getNextPhase()) . '</p>', ), ), ), ); if ($aSelectForemanGroups) { $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', 'value' => '<strong>'.t("defaults-all-phases").'</strong><br><br>', ); $aForms["setup"]["form"]['input' . $i++] = $aSelectForemanGroups; $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', 'value' => '<br><br>', ); } foreach (array_keys($this->getPhases()) as $sPhase) { $bActivePhase = $this->isActivePhase($sPhase); $sUrl = array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["url"] : ""; $sDeploymethod = array_key_exists("deploymethod", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["deploymethod"] : ""; $sDeployhosts = array_key_exists("hosts", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["hosts"] : ""; if($sDeployhosts){ echo "$sDeployhosts<br>"; if(!strpos($sDeployhosts, ",")){ $sCmd=sprintf($this->_aConfig["installPackages"]["addkeycommand"], $sDeployhosts, $sDeployhosts); exec($sCmd . " 2>&1", $aOut); echo "<pre>\$ $sCmd<br>" . implode('<br>', $aOut) ."</pre>" ; } } $sDeploytimes = array_key_exists("deploytimes", $this->_aPrjConfig["phases"][$sPhase]) ? $this->_aPrjConfig["phases"][$sPhase]["deploytimes"] : ""; $sDivId4PhaseSettings = 'divSettings' . $sPhase; $sDivId4TargetHosts = 'divSettings' . $sPhase . 'hosts'; if ($aSelectForemanGroups) { $iForemanHostgroup = (int) $this->_aPrjConfig['phases'][$sPhase]['foreman-hostgroup']; $aSelectForemanHostGroup = array( 'type' => 'select', 'name' => 'phases[' . $sPhase . '][foreman-hostgroup]', 'label' => t("foreman-hostgroup"), 'options' => array( OPTION_DEFAULT => array( 'label' => t('default') . ' (' . $sForemanHostgroupDefault . ')', 'selected' => $iForemanHostgroup === OPTION_DEFAULT ? 'selected' : false, ), OPTION_NONE => array( 'label' => t('none'), 'selected' => $iForemanHostgroup === OPTION_NONE ? 'selected' : false, ), '' => array( 'label' => '- - - - - - - - - - - - - - - - - - - - ', ), ), ); if (count($aForemanHostgroups)) { foreach ($aForemanHostgroups as $aItem) { $aSelectForemanHostGroup['options'][$aItem['id']] = array( 'label' => $aItem['title'], 'selected' => ($iForemanHostgroup === $aItem['id']) ? 'selected' : false, ); } } } $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', 'value' => '' // .'<pre>'.print_r($this->_aPrjConfig["phases"][$sPhase], 1).'</pre>' /* . '<a class="'.$sPhase.'">' . t("phase") . ' ' . $sPhase . '</a>' */ . '<table class="table">' . '<tbody>' . '<tr><th class="' . $sPhase . '">' . t("phase") . ' ' . $sPhase . '</th></tr>' . '<tr><td class="' . ($bActivePhase ? $sPhase : '') . '">' . '' ); $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'checkbox', 'name' => 'phases[' . $sPhase . '][active]', 'label' => t("phase-is-active"), // 'value' => $bUsePuppet, 'required' => false, 'validate' => 'isastring', // 'size' => 100, // 'placeholder' => '...', 'options' => array( '1' => array( 'label' => t("yes"), 'checked' => $bActivePhase, 'onclick' => '$(\'#' . $sDivId4PhaseSettings . '\').css(\'display\', (this.checked ? \'block\' : \'none\') )', ), ), ); $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', 'value' => '' . '<div id="' . $sDivId4PhaseSettings . '" ' . ($bActivePhase ? '' : ' style="display: none;"') . '">' ); $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'text', 'name' => 'phases[' . $sPhase . '][url]', 'label' => t("url-project-website"), 'value' => $sUrl, // 'required' => 'required', 'validate' => 'isastring', 'size' => 100, 'placeholder' => 'http://' . $sPhase . '.[' . t("project") . '].[...]/', ); $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'radio', 'name' => 'phases[' . $sPhase . '][deploymethod]', 'label' => t("deploymethod"), // 'value' => $bUsePuppet, // 'required' => 'required', 'validate' => 'isastring', // 'size' => 100, // 'placeholder' => '...', 'options' => array( 'none' => array( 'label' => t("deploymethod-none"), 'checked' => $sDeploymethod === "none", 'onclick' => '$(\'#' . $sDivId4TargetHosts . '\').css(\'display\', (this.checked ? \'none\' : \'block\') )', ), 'puppet' => array( 'label' => t("deploymethod-puppet"), 'checked' => $sDeploymethod === "puppet", 'onclick' => '$(\'#' . $sDivId4TargetHosts . '\').css(\'display\', (this.checked ? \'block\' : \'none\') )', ), /* * see deploy method to handle an action 'sshproxy' => array( 'label' => t("deploymethod-sshproxy"), 'checked' => $sDeploymethod==="sshproxy", 'onclick' => '$(\'#'.$sDivId4TargetHosts.'\').css(\'display\', (this.checked ? \'block\' : \'none\') )', ), */ ), ); $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', 'value' => '' . '<div id="' . $sDivId4TargetHosts . '" ' . ($sDeploymethod !== "none" ? '' : ' style="display: none;"') . '">' ); $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'text', 'name' => 'phases[' . $sPhase . '][hosts]', 'label' => t("phase-targethosts"), 'value' => $sDeployhosts, // 'required' => 'required', 'validate' => 'isastring', 'size' => 100, 'placeholder' => 'FQDN1,FQDN2', ); /* if ($sPuppethost) { // add ssh host key $sOut0 = shell_exec(sprintf($this->_aConfig["installPackages"]["addkeycommand"], $sPuppethost, $sPuppethost)); $sCmd2 = 'ssh ' . $this->_aConfig["installPackages"]["user"] . '@' . $sPuppethost . ' ' . $this->_aConfig["installPackages"]["testcommand"]; $sOut = 'skip'; // $sOut = shell_exec($sCmd2); // Check auf Versionsnummer - mehr als n Zeichen ist mutmasslich eine Fehlermeldung if (strlen($sOut) > 7) { $sMessages.=$this->getBox("error", sprintf(t("class-project-error-setup-sudo-pupet-agent-failed"), $sPhase, $sCmd, $sOut)); $sOut = '<span class="error" title="' . $sCmd . '">' . $sOut . '</span>'; } else { $sOut = '<span class="ok">' . sprintf(t("class-project-info-setup-ssh-and-puppet-ok"), $sPuppethost) . '</span>'; } $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', 'value' => '<div class="form-group">' . '<label class="col-sm-2"> </label><div class="col-sm-10">' . $sOut . '</div></div>', ); } */ $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', 'value' => '' . '</div>' ); // when to deploy $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'text', 'name' => 'phases[' . $sPhase . '][deploytimes]', 'label' => t("deploytimes"), 'value' => $sDeploytimes, // 'required' => 'required', 'validate' => 'isastring', 'size' => 100, 'placeholder' => implode(", ", $this->_aConfig["phases"][$sPhase]["deploytimes"]), ); if ($aSelectForemanGroups) { $aForms["setup"]["form"]['input' . $i++] = $aSelectForemanHostGroup; } $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', 'value' => '' . '</div>' ); // close div for active phase $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', 'value' => '</td></tr></tbody></table>', ); } // END: loop over phases $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', 'value' => '</div>' . '<div class="tab-pane" id="tab4">' . '<br><pre>'.print_r($this->_aPrjConfig, 1).'</pre>' . '</div>' . '</div>' . '</div>' . '<div style="clear: both; margin-bottom: 1em;"></div>' . '<hr>', ); $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'submit', 'name' => 'btnsave', 'label' => t("save"), 'value' => '<i class="glyphicon glyphicon-ok"></i> ' . t("save"), ); $oForm = new formgen($aForms); return $sMessages . $oForm->renderHtml("setup"); } /** * return html code for the setup form for a new project * @return string */ public function renderNewProject() { global $aParams; if (!$this->oUser->hasPermission("project-action-create")) { return $this->oUser->showDenied(); } require_once ("formgen.class.php"); $i = 0; $sID = array_key_exists("id", $aParams) ? $aParams["id"] : ""; $aForms = array( 'setup' => array( 'meta' => array( 'method' => 'POST', 'action' => '?', ), 'validate' => array(), 'form' => array( 'input' . $i++ => array( 'type' => 'hidden', 'name' => 'setupaction', 'value' => 'create', ), 'input' . $i++ => array( 'type' => 'text', 'name' => 'id', 'label' => t("class-project-info-setup-projectId"), 'value' => $sID, 'required' => 'required', 'validate' => 'isastring', 'size' => 100, 'pattern' => '[a-z0-9\-\_]*', 'placeholder' => t("class-project-info-setup-projectId-placeholder"), ), ), ), ); $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'submit', 'name' => 'btnsave', 'label' => t("save"), 'value' => '<i class="glyphicon glyphicon-ok"></i> ' . t("save"), ); $oForm = new formgen($aForms); return $oForm->renderHtml("setup"); } }