project.class.php 159.90 KiB
<?php
define("OPTION_DEFAULT", -999);
define("OPTION_NONE", -1);
require_once __DIR__.'/../inc_functions.php';
require_once 'base.class.php';
require_once 'htmlguielements.class.php';
require_once 'messenger.class.php';
require_once 'rollout_base.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;
/**
* reference to html renderer class to draw output items
* @var object
*/
protected $_oHtml = false;
/**
* object to access a version control, .e. git
* @var object
*/
private $_oVcs = false;
/**
* object for rollout
* @var type
*/
public $oRolloutPlugin = 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->_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
*/
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"));
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
*/
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 = 'file-template';
break;
case 'tgz':
$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 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;
}
/**
* 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 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' => $this->_oHtml->getIcon('sign-ok').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__ . "($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"]),
);
}
}
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 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"]["rollout"] as $sPluginName=>$aItem) {
$aReturn[$sPluginName] = $aItem;
}
}
return $aReturn;
}
/**
* 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 ? 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
*/
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();
// ----- init rollout plugin
// set name of the activated plugin for this project
$sPluginName=(isset($this->_aPrjConfig['deploy']['active_rollout_plugin']) && $this->_aPrjConfig['deploy']['active_rollout_plugin'])
? $this->_aPrjConfig['deploy']['active_rollout_plugin']
: 'default'
;
$this->oRolloutPlugin = false;
try{
require_once __DIR__.'/../plugins/rollout/'.$sPluginName.'/rollout_'.$sPluginName.'.php';
$this->oRolloutPlugin = new rollout(array(
'lang'=>$this->_aConfig['lang'],
'phase'=>false,
'globalcfg'=>$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
*/
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;
$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>';
$aVersion = $this->_oVcs->getRevision($sTempBuildDir);
$sRevision = $aVersion["revision"];
$sCommitMsg = $aVersion["message"];
$sCommitMsg = str_replace("\n", "<br>", $sCommitMsg);
$sCommitMsg = str_replace('"', """, $sCommitMsg);
$sReturn.=$this->_oHtml->getBox("info", $sCommitMsg);
$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';
if (isset($this->_aPrjConfig['deploy']["configfile"]) && $this->_aPrjConfig['deploy']["configfile"]){
file_put_contents($sCfgout, $this->_aPrjConfig['deploy']["configfile"]);
$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,
'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))) {
$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);
// 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) {
$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 -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.=$this->_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) {
$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));
$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)) {
// $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'],
$this->_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);
$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' => $this->_oHtml->getIcon('phase'), '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 $this->_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;
$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"],
$this->_oHtml->getIconByType('calendar') .' ' . date($sDateFormat, $oPkgDate)
. $this->_oHtml->getIconByType('branch') . t('branch') . ': ' . $aData["branch"] . '<br>'
. $this->_oHtml->getIconByType('revision') . t('revision') . ': ' . $this->_renderRevision($aData["revision"]) . '<br>'
. $this->_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.=$this->_oHtml->getIconByType('link-extern') . ' '. t('url') . ': <a href="' . $sUrl . '">' . $sUrl . '</a><br>';
}
} else {
$sReturn .= $this->_getChecksumDiv(
$aData["revision"],
$this->_oHtml->getIconByType('calendar') .' ' . date($sDateFormat, $oPkgDate)
);
if ($sPlace == "deployed" && array_key_exists("url", $this->_aPrjConfig["phases"][$sPhase])) {
$sMore = $this->_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>'.$this->_oHtml->getIcon('time').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">'.$this->_oHtml->getIcon('sign-info'). 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 . ' td-phase-inactive ' . $this->_aConfig["id"] . '" colspan="' . count($this->_aPlaces) . '">
<div class="versioninfo center inactive">' . $this->_oHtml->getIcon('sign-info').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) {
$sReturn = "";
switch ($this->_aPrjConfig["build"]["type"]) {
case "git":
$aRepodata = $this->getRepoRevision($bRefresh);
if (array_key_exists("revision", $aRepodata)) {
$sReturn.=$this->_getChecksumDiv($aRepodata["revision"],
$this->_oHtml->getIconByType('branch') . t('branch') . ': ' . (array_key_exists("branch", $aRepodata) ? $aRepodata["branch"] : '-') . '<br>'
. $this->_oHtml->getIconByType('revision') . t('revision') . ': ' . $this->_renderRevision($aRepodata["revision"]) . '<br>'
. $this->_oHtml->getIconByType('comment') . t('commitmessage') . ':<br>'
)
."<pre>" . strip_tags($aRepodata["message"], '<br>') . "</pre>";
} else {
$sReturn .= $this->_oHtml->getBox("error", sprintf(t('class-project-error-no-repoaccess'), $aRepodata["error"]))
. $this->renderLink("setup") . '<br>';
}
break;
default:
$sReturn .= $this->_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;
$this->_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"],
$this->_oHtml->getIconByType('calendar') . t('build-from') . ' ' . date("d.m.Y H:i:s", strtotime($aInfos["date"])) . '<br>'
. $this->_oHtml->getIconByType('branch') . t('branch') . ': ' . $aInfos["branch"] . '<br>'
. $this->_oHtml->getIconByType('revision') . t('revision') . ': ' . $this->_renderRevision($aInfos["revision"]) . '<br>'
. $this->_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() {
$sReturn = false;
$sRowHead1 = false;
$sRowHead2 = '<td></td>';
$aAllVersions = $this->_getVersionUsage();
if (!count($aAllVersions)) {
return $this->_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'],
$this->_oHtml->getIconByType('calendar') . t('build-from') . ': ' . $sVersion .'<br>'
. $this->_oHtml->getIconByType('branch') . t('branch') . ': ' . $aData['info']["branch"] . '<br>'
. $this->_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>';
}
$sReturn = '
<div class="visualprocess">
<div class="process box">
<div class="title">' . $this->_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">' . $this->_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">' . $this->_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();
}
$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' => $this->_oHtml->getIcon('foreman') . 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.=$this->_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">' . $this->_oHtml->getIcon('list').t('setup-metadata') . '</a></li>
<li><a href="#tab2" data-toggle="tab">' . $this->_oHtml->getIcon('repository').t('repositoryinfos') . '</a></li>
<li><a href="#tab3" data-toggle="tab">' . $this->_oHtml->getIcon('deploy-configfile').t('deploy-configfile') . '</a></li>
<li><a href="#tab4" data-toggle="tab">' . $this->_oHtml->getIcon('phase').t('phases') . '</a></li>
<li><a href="#tab5" data-toggle="tab">' . $this->_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>' . t('deploy-configfile-hint') . '</p>',
),
'textarea' . $i++ => array(
'type' => 'textarea',
'name' => 'deploy[configfile]',
'label' => t("deploy-configfile"),
'value' => $this->_aPrjConfig['deploy']["configfile"],
// 'required' => 'required',
'validate' => 'isastring',
'cols' => 100,
'rows' => 10,
'placeholder' => 'export myvariable="hello world"',
),
'input' . $i++ => array(
'type' => 'text',
'name' => 'api[secret]',
'label' => t("api-secret"),
'value' => $this->_aPrjConfig["api"]["secret"],
'validate' => 'isastring',
'size' => 100,
'placeholder' => '',
),
'input' . $i++ => array(
'type' => 'markup',
'value' => '<div class="col-sm-12">'
. '<p>' . t('api-secret-hint') . '<br>'
. '<a href="#" class="btn btn-default" onclick="$(\'#input'.($i-2).'\').val(generateSecret(64)); return false">'.t("api-secret-generate").'</a>'
. '</p></div>',
),
// --------------------------------------------------
'input' . $i++ => array(
'type' => 'markup',
'value' => ' </div><div class="tab-pane" id="tab4">
<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"] : "";
/*
* task-1847 - reove adding ssh key
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' => $this->_oHtml->getIcon('foreman') . 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 . '">' . $this->_oHtml->getIcon('phase') . 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' => $this->_oHtml->getIcon('url') . 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' => $this->_oHtml->getIcon('method') . 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' => $this->_oHtml->getIcon('host') . 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' => $this->_oHtml->getIcon('time') . 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="tab5">'
. '<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' => $this->_oHtml->getIcon('sign-ok').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' => $this->_oHtml->getIcon('sign-ok') . t("save"),
);
$oForm = new formgen($aForms);
return $oForm->renderHtml("setup");
}
}