<?php define("OPTION_DEFAULT", -999); define("OPTION_NONE", -1); require_once __DIR__ . '/../inc_functions.php'; require_once 'base.class.php'; require_once 'messenger.class.php'; // plugins // require_once 'plugins.class.php'; require_once 'build_base.class.php'; require_once 'rollout_base.class.php'; require_once 'htmlguielements.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 */ protected $_aConfig = array(); /** * configuration of the project (= $aProjects[ID] in the config file) * @var array */ protected $_aPrjConfig = array(); /** * version infos of all phases * @var array */ protected $_aData = array(); /** * existing versions in the archive dir * @var array */ protected $_aVersions = array(); /** * output file to fetch processing content with ajax request * @var string */ protected $_sProcessTempOut = false; /** * places of version infos in each deployment phase * @var array */ protected $_aPlaces = array( "onhold" => "Queue", "ready2install" => "Puppet", "deployed" => "Installiert", ); /** * collector for returncodes of multiple exec calls * @var int */ protected $_iRcAll = 0; /** * reference to html renderer class to draw output items * @var object */ protected $_oHtml = false; /** * object to access a version control, .e. git * @var object */ protected $_oVcs = false; /** * object for rollout * @var type */ public $oRolloutPlugin = false; protected $_sBranchname = false; /** * send messages * @var messengerobject */ protected $oMessenger = false; /** * collected errors * @var array */ protected $_errors = []; // ---------------------------------------------------------------------- // constructor // ---------------------------------------------------------------------- /** * constructor * @param string $sId id of the project */ public function __construct($sId = false) { $this->oUser = new user(); $this->_oHtml = new htmlguielements(); $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 */ protected 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 */ protected 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 (isset($this->_aConfig['messenger']['slack']['presets'][$sSlack][$sKey])) { $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 */ protected function _readConfig() { global $aConfig; $this->_aConfig = $aConfig; return true; } /** * validate config data * @return boolean */ protected function _verifyConfig() { if (!is_array($this->_aPrjConfig) || !count($this->_aPrjConfig)) { // die(t("class-project-error-no-config")); throw new Exception(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 */ protected 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 */ protected function _logaction($sMessage, $sAction = "", $sLoglevel = "info") { require_once("actionlog.class.php"); $oLog = new Actionlog($this->_aConfig["id"]); $oLog->add($sMessage, (__CLASS__ . "->" . $sAction), $sLoglevel); } // ---------------------------------------------------------------------- // GETTER // ---------------------------------------------------------------------- protected 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 */ protected 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 */ protected function _getBuildDir() { return $this->_aConfig['buildDir'] . '/' . $this->_aConfig["id"]; } /** * get full path where the project default files are * @return type */ protected 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 */ protected 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 = ''; } } // echo "DEBUG: ".__METHOD__."($sPhase, $sPlace) --> $sBase<br>"; 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 */ protected function _getInfofile($sPhase, $sPlace) { $sBase = $this->_getFileBase($sPhase, $sPlace); return $sBase ? $sBase . '/' . $this->_aPrjConfig["fileprefix"] . '.json' : false; } /** * get filename for package file (without file extension) * @param string $sPhase one of preview|stage|live ... * @param string $sPlace one of onhold|ready2install|deployed * @return string */ protected function _getPackagefile($sPhase, $sPlace) { $sBase = $this->_getFileBase($sPhase, $sPlace); return $sBase ? $sBase . '/' . $this->_aPrjConfig["fileprefix"] : 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 = 'file-template'; break; case 'tgz': case 'zip': $sType = 'package'; $sIcon = 'file-archive'; break; case 'json': $sType = 'metadata'; $sIcon = 'file-meta'; break; default: $sType = 'any'; $sIcon = 'file-any'; break; } $iTotalSize += $aStat['size']; $aReturn['files'][$sFileBase] = array( 'type' => $sType, 'icon' => $this->_oHtml->getIcon($sIcon), '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 the group id of the project * @return string */ public function getProjectGroup() { return isset($this->_aPrjConfig["projectgroup"]) && $this->_aPrjConfig["projectgroup"] != '-1' ? $this->_aPrjConfig["projectgroup"] : false; } /** * get the group label of the project * @return string */ public function getProjectGroupLabel() { $sGroupid = $this->getProjectGroup(); return isset($this->_aConfig["projectgroups"][$sGroupid]) ? $this->_aConfig["projectgroups"][$sGroupid] : false; } /** * get full path of a packed project archive * @param string $sVersion version number of the build * @return string */ protected 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 */ protected 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"]; } /** * TODO: REMOVE * 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 (isset($aData[$sPlace]["version"])) { $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 */ protected 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 string $dir directory to delete * @return type */ protected 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__ . " sorry, Method 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 isset($this->_aPrjConfig["label"]) ? $this->_aPrjConfig["label"] : ''; } /** * get description of the project * @return string */ public function getDescription() { return isset($this->_aPrjConfig["description"]) ? $this->_aPrjConfig["description"] : ''; } /** * get the id of the current project * @return string */ public function getId() { return isset($this->_aConfig["id"]) ? $this->_aConfig["id"] : ''; } /** * get deploy and queue infos for all phases * It build up a subkey "progress" with info if a build is queued * or an installation of a new package is going on * @return array */ 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 ( $sPlace !== 'onhold' && isset($aDataPhase[$sPlace]['version']) ) { if ($bFirstVersion && !$bHasDifferentVersions && $bFirstVersion !== $aDataPhase[$sPlace]['version']) { $bHasDifferentVersions = true; } if (!$bFirstVersion) { $bFirstVersion = $aDataPhase[$sPlace]['version']; } } } // check queue if (!$bHasQueue && isset($aDataPhase['onhold']['version']) && $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; } // echo '<pre>'.print_r($this->_aData["phases"][$sPhase], 1).'</pre>'.__METHOD__.'<br>'; return $this->_aData["phases"][$sPhase]; } /** * get a list of all existing projects as a flat array; * it can be ordered by "id" or "label" * <code> * print_r($oPrj->getProjects("label")); * </code> * returns<br> * Array ( [0] => project1 [1] => project2 ) * @param string $sort sort by "id" (default) or "label" * @return array */ public function getProjects($sort = 'id') { $aReturn = array(); foreach (glob(dirname($this->_getConfigFile("dummy")) . "/*.json") as $filename) { $aReturn[] = str_replace(".json", "", basename($filename)); } if ($sort == "label") { $aProjectLabels = []; foreach ($aReturn as $sPrj) { $oPrj = new project($sPrj); $aProjectLabels[strtolower($oPrj->getLabel() . '-' . $sPrj)] = [ 'id' => $sPrj, 'label' => $oPrj->getLabel(), 'description' => $oPrj->getDescription(), ]; } ksort($aProjectLabels); return array_values($aProjectLabels); } if ($sort == "id") { sort($aReturn); } return $aReturn; } /** * check if the given phase is active for this project * @param string $sPhase * @return bool */ public function isActivePhase(string $sPhase): bool { return ( $this->_aPrjConfig && isset($this->_aPrjConfig["phases"][$sPhase]["active"][0]) ? $this->_aPrjConfig["phases"][$sPhase]["active"][0] : false ); } /** * return array of all (active and inactive) phases * @return array */ public function getPhases() { return $this->_aConfig["phases"]; } /** * return array of all (active and inactive) phases * @return array */ 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; } /** * get an array with deploy status ... * 'inprogress'=>do versions differ from phase to phase = rollout of a version is in progress 'hasQueue'=>is there a package in a queue (waiting for deployment time to get ready to be installed) * @return array */ 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 '<span class="btn" title="no permission [project-action-accept] for user [' . $this->oUser->getUsername() . ']">' . $sPhase . '</span>'; } 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 bool $bIgnoreCache flag to ignore exiting cached data * @return array|boolean */ public function getRemoteBranches($bIgnoreCache = false) { $this->log(__FUNCTION__ . "($bIgnoreCache) 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($bIgnoreCache); } return false; } /** * 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__ . "($bRefresh) 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"]), ); } } $this->log(__FUNCTION__ . " result:<pre>" . print_r($this->_aData, 1) . "</pre>"); return $this->_aData["phases"]["source"]; } /** * init version control system (git, ...) * @return vcs-object */ protected 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 an array of enabled plugins * @param string $sSection one of false|"rollout"|... * @return array */ public function getConfiguredPlugins($sSection = false) { $aReturn = array(); if (!$sSection) { $aReturn = $this->_aConfig["plugins"]; } else { foreach ($this->_aConfig["plugins"][$sSection] as $sPluginName => $aItem) { $aReturn[$sPluginName] = $aItem; } } return $aReturn; } /** * get a location of a plugin file with full path * @param string $sType type of plugin, i.e. "rollout" * @param string $sPluginName Name of plugin * @return string */ protected function _getPluginFilename($sType, $sPluginName) { return __DIR__ . '/../plugins/' . $sType . '/' . $sPluginName . '/' . $sType . '_' . $sPluginName . '.php'; } /** * get a flat array of all existing ssh keys * @return array */ protected 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 */ protected 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 // ---------------------------------------------------------------------- protected function _setProcessOutFile($sNewTempfile = false) { if ($this->_sProcessTempOut && file_exists($this->_sProcessTempOut)) { unlink($this->_sProcessTempOut); } // $sNewTempfile = sys_get_temp_dir() . "/" . basename($sNewTempfile); $this->_sProcessTempOut = $sNewTempfile ? sys_get_temp_dir() . "/" . basename($sNewTempfile) : false; return $this->_sProcessTempOut; } /** * get projects from ldap; it returns ldap search items with cn as * array key. * * @return array */ protected 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)) { $this->_errors[] = sprintf(t("class-project-error-config-wrongid"), htmlentities($sId)); return false; } $this->_aPrjConfig = []; $this->_aConfig["id"] = $sId; $this->_errors = []; if (isset($this->_aConfig['projects']['json']['active']) && $this->_aConfig['projects']['json']['active']) { $sCfgfile = $this->_getConfigFile($sId); if (!$sCfgfile || !file_exists($sCfgfile)) { return false; } $_aPrjConfigTmp = json_decode(file_get_contents($this->_getConfigFile($sId)), true); if (!$_aPrjConfigTmp) { $this->_errors[] = sprintf(t("class-project-error-config-invalid"), $sId, $this->_getConfigFile($sId)); return false; } $this->_aPrjConfig = $_aPrjConfigTmp; } if (isset($this->_aConfig['projects']['ldap']['active']) && $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(); // ----- init rollout plugin // set name of the activated plugin for this project $sPluginName = (isset($this->_aPrjConfig['deploy']['enabled_rollout_plugin']) && $this->_aPrjConfig['deploy']['enabled_rollout_plugin']) ? $this->_aPrjConfig['deploy']['enabled_rollout_plugin'] : 'default'; $this->oRolloutPlugin = false; try { require_once $this->_getPluginFilename('rollout', $sPluginName); $sPluginClassname = 'rollout_' . $sPluginName; $this->oRolloutPlugin = new $sPluginClassname(array( 'lang' => $this->_aConfig['lang'], 'phase' => false, 'globalcfg' => isset($this->_aConfig['plugins']['rollout'][$sPluginName]) ? $this->_aConfig['plugins']['rollout'][$sPluginName] : [], 'projectcfg' => $this->_aPrjConfig, )); // print_r($this->_oRolloutPlugin->getPluginfos()); // print_r($this->_oRolloutPlugin->getName()); } catch (Exception $ex) { } 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 */ protected 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 */ protected 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) * @param bool $bReturnMasterIfEmpty flag: if there is no current branch then detect a master branch * @return string */ public function getBranchname($bReturnMasterIfEmpty = false) { $this->log(__FUNCTION__ . " start"); $this->_initVcs(); if ($this->_oVcs) { if (method_exists($this->_oVcs, "getCurrentBranch")) { $sCurrentBranch = $this->_oVcs->getCurrentBranch(true); // true means search for master branch if empty if ($sCurrentBranch) { $this->setBranchname($sCurrentBranch); return $sCurrentBranch; } } } 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; $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 $this->_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 $this->_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>'; $aRepodata=$this->getRepoRevision(); $sRevisionShort=substr($aRepodata['revision'], 0, 8); $sReturn .= $this->_oHtml->getBox("info", t('commitmessage') . '<pre>'.htmlentities($aRepodata['message']).'</pre>'); $sReturn .= $this->_execAndSend("ls -lisa $sTempBuildDir"); if (!$this->_iRcAll == 0) { $sError = sprintf(t('class-project-error-command-failed'), $sTempBuildDir); $this->_logaction($sError, __FUNCTION__, "error"); $this->_TempFill($sError . $sReturn, $aActionList); $this->_TempDelete($sTempBuildDir); return $this->_oHtml->getBox("error", $sError . $sReturn); } // -------------------------------------------------- foreach (glob($sTempBuildDir . '/hooks/on*') as $filename) { $sReturn .= 'chmod 755 ' . $filename . '<br>'; $sReturn .= $this->_execAndSend('chmod 755 ' . $filename); $sReturn .= $this->_execAndSend('ls -l ' . $filename); } // -------------------------------------------------- $sCfgout = $sTempBuildDir . '/ci-custom-vars'; $sCfgContent = ''; if (isset($this->_aPrjConfig['deploy']["configfile"]) && $this->_aPrjConfig['deploy']["configfile"]) { # task#5047 - FIX EOL $sCfgContent .= $this->_aPrjConfig['deploy']["configfile"]; // detect unix, linux, mac if (DIRECTORY_SEPARATOR === '/') { $sCfgContent = str_replace("\r\n", "\n", $sCfgContent); } # /task#5047 } $aCivars=[ 'branch'=>$aRepodata['branch'], 'branch_short'=>$aRepodata['shortname'], 'branch_type'=>$aRepodata['type'], 'revision'=>$aRepodata['revision'], 'revision_short'=>$sRevisionShort, 'imagepart'=>$aRepodata['type']=='tags' ? $aRepodata['shortname'] : $sRevisionShort , // 'message'=>$aRepodata['message'], ]; $sCfgContent .= "\n" . "# ---------- generated CI SERVER variables\n" . "\n" ; foreach ($aCivars as $sKey => $value){ $sCfgContent .= "export CI_$sKey=\"$value\"; \n"; } file_put_contents($sCfgout, $sCfgContent); $sReturn .= $this->_execAndSend('ls -l ' . $sCfgout); $sReturn .= $this->_execAndSend('cat ' . $sCfgout); $sReturn .= $this->_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 . '";' . 'export NVMINIT="' . $this->_aConfig['appRootDir'] . '/shellscripts/nvm_init.sh";' . (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->_TempFill($sError . $sReturn, $aActionList); $this->_TempDelete($sTempBuildDir); return $this->_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) { $sError = sprintf(t('class-project-error-command-failed'), $sTempBuildDir); $this->_logaction($sError, __FUNCTION__, "error"); $this->_TempFill($sError . $sReturn, $aActionList); $this->_TempDelete($sTempBuildDir); return $this->_oHtml->getBox("error", $sError . $sReturn); } } 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) { $sError = t('class-project-error-build-docroot-not-found'); $this->_logaction($sError, __FUNCTION__, "error"); $this->_TempFill($sError . $sReturn, $aActionList); $this->_TempDelete($sTempBuildDir); return $this->_oHtml->getBox("error", $sError . $sReturn . $sError); } } if (!$this->_iRcAll == 0) { $sError = sprintf(t('class-project-error-command-failed'), $sTempBuildDir); $this->_logaction($sError, __FUNCTION__, "error"); $this->_TempFill($sError . $sReturn, $aActionList); $this->_TempDelete($sTempBuildDir); return $this->_oHtml->getBox("error", $sError . $sReturn); } // $sReturn.=$this->_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, 'branch'=>$aRepodata['branch'], 'branch_short'=>$aRepodata['shortname'], 'branch_type'=>$aRepodata['type'], 'revision'=>$aRepodata['revision'], 'revision_short'=>$sRevisionShort, 'imagepart'=>$aCivars['imagepart'], 'message' => $aRepodata['message'], ); /* "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))) { $sError = sprintf(t('"class-project-error-build-dir-was-not-created"'), $sTempBuildDir); $this->_logaction($sError, __FUNCTION__, "error"); $this->_TempFill($sError . $sReturn, $aActionList); $this->_TempDelete($sTempBuildDir); return $this->_oHtml->getBox("error", $sError . $sReturn); } $this->_TempFill($sReturn, $aActionList); // ----- loop over enabled build plugins // WIP // set name of the activated plugin for this project $aPlugins = (isset($this->_aPrjConfig['build']['enabled_build_plugins']) && $this->_aPrjConfig['build']['enabled_build_plugins']) ? $this->_aPrjConfig['build']['enabled_build_plugins'] : ['tgz']; foreach ($aPlugins as $sPluginName) { $oPlugin = false; $sReturn .= '<h4>' . $sPluginName . '</h4>'; try { include_once $this->_getPluginFilename('build', $sPluginName); $sPluginClassname = 'build_' . $sPluginName; $oPlugin = new $sPluginClassname(array( 'lang' => $this->_aConfig['lang'], 'workdir' => $sTempBuildDir, 'outfile' => $sPackageFileArchiv, )); } catch (Exception $ex) { return $this->_oHtml->getBox( "error", "FAILED to initialize build plugin " . $sPluginName . '<br>' . $sReturn ); } $sReturn .= sprintf(t("creating-file"), $oPlugin->getOutfile()) . "<br>"; foreach ($oPlugin->checkRequirements() as $sCommand) { $sReturn .= $this->_execAndSend($sCommand); $this->_TempFill($sReturn, $aActionList); } foreach ($oPlugin->getBuildCommands() as $sCommand) { $sReturn .= $this->_execAndSend($sCommand); $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) { $sError = t('class-project-error-build-packaging-failed'); $this->_logaction($sError, __FUNCTION__, "error"); $this->_TempFill($sError . $sReturn, $aActionList); $this->_TempDelete($sTempBuildDir); return $this->_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 .= $this->_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); if (!$this->isActivePhase($sPhase)) { $sError = sprintf(t("class-project-warning-phase-not-active"), $sPhase); $this->_logaction($sError, __FUNCTION__, "error"); return $this->_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 $this->_oHtml->getBox("error", $sError . $sReturn); } $this->_logaction(t('finished') . " queue($sPhase, $sVersion) " . t("class-project-info-queue-successful"), __FUNCTION__); $sReturn .= $this->_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) { $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 . $this->_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 .= $this->_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 .= $this->_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 .= $this->_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 -rLvt $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") { $sReturn .= t("class-project-info-deploy-start-by-method-skip") . "<br>"; } else { $sReturn .= '<p>Plugin: ' . $this->oRolloutPlugin->getId() . '</p>'; foreach ($this->oRolloutPlugin->getDeployCommands($sPhase) as $sCmd) { $sReturn .= $this->_execAndSend("$sCmd"); } /* $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>"; if (!$this->_iRcAll == 0) { $sWarnlevel = 'error'; $sMessage = sprintf(t('class-project-info-deploy-failed'), $sPhase); } else { $sWarnlevel = 'success'; $sMessage = sprintf(t("class-project-info-deploy-successful"), $sPhase); } $sReturn .= $this->_oHtml->getBox($sWarnlevel, $sMessage); $this->_sendMessage($sMessage); $this->_logaction(t('finished') . " deploy($sPhase, $bIgnoreDeploytimes) " . $sMessage, __FUNCTION__, $sWarnlevel); $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) { $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 . $this->_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 .= $this->_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, JSON_PRETTY_PRINT)); $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; } }