From 11825448fdc2066c6cd4f72e8cd8fbf29ef7add9 Mon Sep 17 00:00:00 2001 From: "Hahn Axel (hahn)" <axel.hahn@iml.unibe.ch> Date: Wed, 10 May 2017 16:59:41 +0200 Subject: [PATCH] CI Webgui: ad feature in project setup: input for replacements --- config/inc_roles.php | 1 + config/lang/de.json | 5 + config/lang/en.json | 5 + .../classes/config-replacement.class.php | 91 +++++++++ .../deployment/classes/project.class.php | 175 ++++++++++++++++-- public_html/deployment/pages/act_overview.php | 6 +- 6 files changed, 269 insertions(+), 14 deletions(-) create mode 100644 public_html/deployment/classes/config-replacement.class.php diff --git a/config/inc_roles.php b/config/inc_roles.php index 9375e00d..4fc52c01 100644 --- a/config/inc_roles.php +++ b/config/inc_roles.php @@ -57,6 +57,7 @@ return array( "project-action-overview", "project-action-phase", "project-action-setup", + "project-action-edit-replacements", ), // ----- wenn es mal eine feinere Granulierung braucht, muss man eine diff --git a/config/lang/de.json b/config/lang/de.json index 2456673f..aed7b29c 100644 --- a/config/lang/de.json +++ b/config/lang/de.json @@ -224,6 +224,7 @@ "defaultbranch": "[default: Master oder Trunk]", "deploy": "Deploy", "deploy-hint": "Deploy der Queue von Phase [%s]", + "deploy-settings": "Deployment - Einstellungen", "deploytimes": "Deployment Zeitpunkte", "deploytimes-immediately": "Ein Archiv in der Queue wird sofort ins Repo gestellt.", "deploymethod": "Deployment-Methode", @@ -267,6 +268,10 @@ "projectname": "Projektname", "projectmanager": "Projektleiter", "queue": "Queue", + "replacement-fields": "erkannte Platzhalter:", + "replacement-targetfile": "Ziel-Datei", + "replacements": "Ersetzungen mit Template-Dateien", + "replacements-info": "Beim Build-Prozess gefundene Templates werden aufgelistet und darin erkannte Platzhalter erkannt. Die Felder sind ausschliesslich für die neue Infrastruktur relevant.", "repositoryinfos": "Quell-Repository", "repository-access-browser": "Browserzugriff auf das Repo", "repository-auth": "Authentifizierung/ Dateiname zum SSH-Private-Key", diff --git a/config/lang/en.json b/config/lang/en.json index 0cdc4aa3..4b3132a6 100644 --- a/config/lang/en.json +++ b/config/lang/en.json @@ -226,6 +226,7 @@ "defaultbranch": "[default: master or trunk]", "deploy": "Deploy", "deploy-hint": "Deploy queue of phase [%s]", + "deploy-settings": "Deployment - settings", "deploymethod": "Deployment method", "deploymethod-none": "None. Do not trigger any action.", "deploymethod-puppet": "Run Puppet on target host(s)", @@ -269,6 +270,10 @@ "projectname": "Project name", "projectmanager": "Projekt manager", "queue": "Queue", + "replacement-fields": "Detected placeholders:", + "replacement-targetfile": "Target file", + "replacements": "Replacements in template files", + "replacements-info": "List of detected templates with its placeholders.", "repositoryinfos": "Sourcecode Repository", "repository-access-browser": "Browser access to sources", "repository-auth": "Authentication/ filename of ssh private key", diff --git a/public_html/deployment/classes/config-replacement.class.php b/public_html/deployment/classes/config-replacement.class.php new file mode 100644 index 00000000..d56557c9 --- /dev/null +++ b/public_html/deployment/classes/config-replacement.class.php @@ -0,0 +1,91 @@ +<?php + +// namespace imldeployment; + +require_once 'project.class.php'; + + +/** + * config-replacement class + * reads templatefiles and scans its placeholders for replacements + * + * @author hahn + */ +class configreplacement { + + /** + * project class + * @var type + */ + protected $_oProject = false; + + + /** + * init + * @param string $sProject optional: project id; you can use setProject() too + * @return boolean + */ + public function __construct($sProject = false) { + if ($sProject){ + $this->setProject($sProject); + } + return true; + } + + + /** + * get an array with a flat list of all templatefiles of a build + * @param string $sPhase name of phase + * @return boolean|array + */ + public function getTemplatefiles($sPhase=false){ + $aReturn = array(); + if (!$sPhase){ + $sPhase=$this->_oProject->getNextPhase(false); + } + + $aBuildfiles=$this->_oProject->_getBuildfilesByPlace($sPhase, 'onhold'); + if (!$aBuildfiles){ + $aBuildfiles=$this->_oProject->_getBuildfilesByPlace($sPhase, 'ready2install'); + } + + if (!$aBuildfiles || !array_key_exists('types', $aBuildfiles) || !array_key_exists('templates', $aBuildfiles['types'])){ + return false; + } + foreach ($aBuildfiles['types']['templates'] as $sFile){ + $aReturn[]=$aBuildfiles['dir'].'/'.$sFile; + } + return $aReturn; + } + + /** + * get an array with all template files (basename) and its replacement fields + * @param string $sPhase name of phase + * @return array + */ + public function getReplacements($sPhase=false){ + $aFiles=$this->getTemplatefiles(); + if (!$aFiles){ + return false; + } + + $aReturn=array(); + foreach ($aFiles as $sFile){ + // $sFile always exists because it was read from filesystem + $sContent=file_get_contents($sFile); + + preg_match_all('/replace\[[\'\"](.*)[\'\"]\]/U', $sContent, $aMatches); + // echo '<strong>'.$sFile.'</strong><br>'.print_r($aMatches, 1).'<br>'.$sContent.'<br><br>'; + $aReturn[$sFile]=$aMatches[1]; + } + return $aReturn; + } + + /** + * switch to a project + * @param type $sProject + */ + public function setProject($sProject){ + $this->_oProject = new project($sProject); + } +} diff --git a/public_html/deployment/classes/project.class.php b/public_html/deployment/classes/project.class.php index 6eecaf10..fbfe6eda 100644 --- a/public_html/deployment/classes/project.class.php +++ b/public_html/deployment/classes/project.class.php @@ -362,6 +362,80 @@ class project extends base { $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'; + break; + case 'tgz': + $sType='package'; + break; + case 'json': + $sType='metadata'; + break; + default: + $sType='any'; + break; + } + $iTotalSize+=$aStat['size']; + $aReturn['files'][$sFileBase]=array( + 'type'=>$sType, + '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 phase and place + * @param string $sPhase one of preview|stage|live ... + * @param string $sPlace one of onhold|ready2install|deployed + * @return array + */ + public function _getBuildfilesByVersion($sVersion) { + return $this->_getBuildfilesByDir($this->_getProjectArchiveDir().'/'.$sVersion); + } + + /** * get full path of a packed project archive @@ -2704,8 +2778,12 @@ class project extends base { foreach ($aAllVersions as $sVersion => $aData) { $sReturn.='<tr>'; + // $aFiles=$this->_getBuildfilesByVersion($sVersion); $sInfos = $this->renderInfoLink($aData["info"], array('hpos' => 'left')); - $sReturn.='<td>' . $sVersion . ' ' . $sInfos . '</td>'; + $sReturn.='<td>' + . $sVersion . ' ' . $sInfos . '<br>' + // . $aFiles['filecount'] . ' ('.$aFiles['totalsize-hr'].')' + . '</td>'; foreach ($this->getActivePhases() as $sPhase) { $sTLine = ''; @@ -2850,6 +2928,11 @@ class project extends base { $sMessages = ''; require_once ("formgen.class.php"); + + require_once("./classes/config-replacement.class.php"); + $oConfig = new configreplacement(); + $oConfig->setProject($this->_aConfig["id"]); + $i = 0; $aPrefixItem = count($this->getVersions()) ? @@ -3033,14 +3116,17 @@ class project extends base { $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'markup', 'value' => '' - // .'<pre>'.print_r($this->_aPrjConfig["phases"][$sPhase], 1).'</pre>' - . '<a class="'.$sPhase.'">' - . t("phase") . ' ' . $sPhase - . ($bActivePhase ? '' : ' (' . t("inactive") . ')') - . '</a>' - . '<table class="table">' - . '<tbody>' - . '<tr><td class="' . ($bActivePhase ? $sPhase : '') . '">' + // .'<pre>'.print_r($this->_aPrjConfig["phases"][$sPhase], 1).'</pre>' + /* + . '<a class="'.$sPhase.'">' + . t("phase") . ' ' . $sPhase + . '</a>' + */ + . '<table class="table">' + . '<tbody>' + . '<tr><th class="' . $sPhase . '">'. t("phase") . ' ' . $sPhase.'</th></tr>' + . '<tr><td class="' . ($bActivePhase ? $sPhase : '') . '">' + . '' ); $aForms["setup"]["form"]['input' . $i++] = array( @@ -3066,6 +3152,10 @@ class project extends base { 'value' => '' .'<div id="'.$sDivId4PhaseSettings.'" '.($bActivePhase ? '' : ' style="display: none;"').'">' ); + $aForms["setup"]["form"]['input' . $i++] = array( + 'type' => 'markup', + 'value' => '<div style="clear: both"></div><div class="form-group"><h3>'.t("deploy-settings").'</h3></div>' + ); $aForms["setup"]["form"]['input' . $i++] = array( 'type' => 'text', 'name' => 'phases[' . $sPhase . '][url]', @@ -3164,16 +3254,79 @@ class project extends base { 'size' => 100, 'placeholder' => implode(", ", $this->_aConfig["phases"][$sPhase]["deploytimes"]), ); + + $aReplacements=$oConfig->getReplacements($sPhase); + $aForms["setup"]["form"]['input' . $i++] = array( + 'type' => 'markup', + 'value' => '<div style="clear: both; height: 2em;"></div>' + . '<div class="form-group">' + . '<h3>'.t("replacements").' ('.($aReplacements ? count($aReplacements):0).')</h3>' + . t('replacements-info') + . '</div>' + ); + + if ($aReplacements){ + foreach($aReplacements as $sFile=>$aFields){ + $tTplFile=basename($sFile); + $aValues = (array_key_exists("replace", $this->_aPrjConfig["phases"][$sPhase]) + && array_key_exists($tTplFile, $this->_aPrjConfig["phases"][$sPhase]["replace"]) + ) + ? $this->_aPrjConfig["phases"][$sPhase]["replace"][$tTplFile] : false; + $aForms["setup"]["form"]['input' . $i++] = array( + 'type' => 'markup', + 'value' => '<div class="form-group"><br><h4><i class="fa fa-file-code-o"></i> '.$tTplFile.'</h4>' + // . '<textarea cols="100" rows="7" >'.file_get_contents($sFile).'</textarea>' + . '</div>' + ); + $aForms["setup"]["form"]['input' . $i++] = array( + 'type' => 'text', + 'name' => 'phases[' . $sPhase . '][replace]['.$tTplFile.'][targetfile]', + 'label' => t("replacement-targetfile"), + 'value' => $aValues && array_key_exists('targetfile', $aValues) ? $aValues['targetfile'] : '', + // 'required' => 'required', + 'validate' => 'isastring', + 'size' => 100, + 'placeholder' => strip_tags(t("replacement-targetfile")), + ); + $aForms["setup"]["form"]['input' . $i++] = array( + 'type' => 'markup', + 'value' => '<br>'.t("replacement-fields") + // . '<pre>'.print_r($aValues, 1).'</pre>' + ); + foreach ($aFields as $sField){ + + $aForms["setup"]["form"]['input' . $i++] = array( + 'type' => 'text', + 'disabled' => $this->oUser->hasPermission("project-action-edit-replacements") ? '' : 'disabled', + 'name' => 'phases[' . $sPhase . '][replace]['.$tTplFile.']['.$sField.']', + 'label' => $sField, + 'value' => $aValues && array_key_exists($sField, $aValues) ? $aValues[$sField] : '', + // 'required' => 'required', + 'validate' => 'isastring', + 'size' => 100, + 'placeholder' => $sField, + ); + } + } + } else { + $aForms["setup"]["form"]['input' . $i++] = array( + 'type' => 'markup', + 'value' => '<div class="form-group"><h4>'.t("none").'</h4></div>' + ); + } + $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></div>' diff --git a/public_html/deployment/pages/act_overview.php b/public_html/deployment/pages/act_overview.php index 9dd3c282..f40f6803 100644 --- a/public_html/deployment/pages/act_overview.php +++ b/public_html/deployment/pages/act_overview.php @@ -37,12 +37,12 @@ if (!array_key_exists("prj", $aParams)) { $sListOfBranches.='<li title="'.$aBranch['revision'].'">'.$aBranch['label'] . '</li>'; } $sListOfBranches.='</ol>'; - + $sPhaseTabs=''; $sPhaseDetails=''; - + $sOut = ' - + ' . $oPrj->renderLink("setup") . '<br> <br> -- GitLab