* src/IDF/Middlewre.php: add a global template variable
"usherConfigured" to denote whether we render links to the usher control functions in the forge administration * src/IDF/Scm/Monotone.php: moved IDF_Scm_Monotone_Stdio into separate file src/IDF/Scm/Monotone/Stdio.php * src/IDF/Scm/Usher.php: new class to query and modify the state of a running usher instance * src/IDF/Views/Admin.php: add actions to query the list of configured servers, edit their status, view their open connections and control the state of the usher as a whole * src/IDF/conf/idf.php-dist: optional usher configuration added; mail address of monotone-users added; spelling changes * src/IDF/conf/urls.php: added needed URLs for usher actions * src/IDF/templates/idf/gadmin/base.html: usher links
This commit is contained in:
parent
780267978d
commit
de036920a4
@ -111,6 +111,7 @@ function IDF_Middleware_ContextPreProcessor($request)
|
|||||||
$request->project);
|
$request->project);
|
||||||
$c = array_merge($c, $request->rights);
|
$c = array_merge($c, $request->rights);
|
||||||
}
|
}
|
||||||
|
$c['usherConfigured'] = Pluf::f("mtn_usher", null) !== null;
|
||||||
return $c;
|
return $c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
/*
|
/*
|
||||||
# ***** BEGIN LICENSE BLOCK *****
|
# ***** BEGIN LICENSE BLOCK *****
|
||||||
# This file is part of InDefero, an open source project management application.
|
# This file is part of InDefero, an open source project management application.
|
||||||
# Copyright (C) 2008 Céondo Ltd and contributors.
|
# Copyright (C) 2010 Céondo Ltd and contributors.
|
||||||
#
|
#
|
||||||
# InDefero is free software; you can redistribute it and/or modify
|
# InDefero is free software; you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -21,363 +21,7 @@
|
|||||||
#
|
#
|
||||||
# ***** END LICENSE BLOCK ***** */
|
# ***** END LICENSE BLOCK ***** */
|
||||||
|
|
||||||
/**
|
require_once(dirname(__FILE__) . "/Monotone/Stdio.php");
|
||||||
* Monotone stdio class
|
|
||||||
*
|
|
||||||
* Connects to a monotone process and executes commands via its
|
|
||||||
* stdio interface
|
|
||||||
*
|
|
||||||
* @author Thomas Keller <me@thomaskeller.biz>
|
|
||||||
*/
|
|
||||||
class IDF_Scm_Monotone_Stdio
|
|
||||||
{
|
|
||||||
/** this is the most recent STDIO version. The number is output
|
|
||||||
at the protocol start. Older versions of monotone (prior 0.47)
|
|
||||||
do not output it and are therefor incompatible */
|
|
||||||
public static $SUPPORTED_STDIO_VERSION = 2;
|
|
||||||
|
|
||||||
private $project;
|
|
||||||
private $proc;
|
|
||||||
private $pipes;
|
|
||||||
private $oob;
|
|
||||||
private $cmdnum;
|
|
||||||
private $lastcmd;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor - starts the stdio process
|
|
||||||
*
|
|
||||||
* @param IDF_Project
|
|
||||||
*/
|
|
||||||
public function __construct(IDF_Project $project)
|
|
||||||
{
|
|
||||||
$this->project = $project;
|
|
||||||
$this->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destructor - stops the stdio process
|
|
||||||
*/
|
|
||||||
public function __destruct()
|
|
||||||
{
|
|
||||||
$this->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the stdio process and resets the command counter
|
|
||||||
*/
|
|
||||||
public function start()
|
|
||||||
{
|
|
||||||
if (is_resource($this->proc))
|
|
||||||
$this->stop();
|
|
||||||
|
|
||||||
$remote_db_access = Pluf::f('mtn_db_access', 'remote') == "remote";
|
|
||||||
|
|
||||||
$cmd = Pluf::f('idf_exec_cmd_prefix', '') .
|
|
||||||
Pluf::f('mtn_path', 'mtn') . ' ';
|
|
||||||
|
|
||||||
$opts = Pluf::f('mtn_opts', array());
|
|
||||||
foreach ($opts as $opt)
|
|
||||||
{
|
|
||||||
$cmd .= sprintf('%s ', escapeshellarg($opt));
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: we might want to add an option for anonymous / no key
|
|
||||||
// access, but upstream bug #30237 prevents that for now
|
|
||||||
if ($remote_db_access)
|
|
||||||
{
|
|
||||||
$host = sprintf(Pluf::f('mtn_remote_url'), $this->project->shortname);
|
|
||||||
$cmd .= sprintf('automate remote_stdio %s', escapeshellarg($host));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$repo = sprintf(Pluf::f('mtn_repositories'), $this->project->shortname);
|
|
||||||
if (!file_exists($repo))
|
|
||||||
{
|
|
||||||
throw new IDF_Scm_Exception(
|
|
||||||
"repository file '$repo' does not exist"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$cmd .= sprintf('--db %s automate stdio', escapeshellarg($repo));
|
|
||||||
}
|
|
||||||
|
|
||||||
$descriptors = array(
|
|
||||||
0 => array("pipe", "r"),
|
|
||||||
1 => array("pipe", "w"),
|
|
||||||
2 => array("pipe", "w"),
|
|
||||||
);
|
|
||||||
|
|
||||||
$env = array("LANG" => "en_US.UTF-8");
|
|
||||||
|
|
||||||
$this->proc = proc_open($cmd, $descriptors, $this->pipes,
|
|
||||||
null, $env);
|
|
||||||
|
|
||||||
if (!is_resource($this->proc))
|
|
||||||
{
|
|
||||||
throw new IDF_Scm_Exception("could not start stdio process");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->_checkVersion();
|
|
||||||
|
|
||||||
$this->cmdnum = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the stdio process and closes all pipes
|
|
||||||
*/
|
|
||||||
public function stop()
|
|
||||||
{
|
|
||||||
if (!is_resource($this->proc))
|
|
||||||
return;
|
|
||||||
|
|
||||||
fclose($this->pipes[0]);
|
|
||||||
fclose($this->pipes[1]);
|
|
||||||
fclose($this->pipes[2]);
|
|
||||||
|
|
||||||
proc_close($this->proc);
|
|
||||||
$this->proc = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* select()'s on stdout and returns true as soon as we got new
|
|
||||||
* data to read, false if the select() timed out
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
* @throws IDF_Scm_Exception
|
|
||||||
*/
|
|
||||||
private function _waitForReadyRead()
|
|
||||||
{
|
|
||||||
if (!is_resource($this->pipes[1]))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
$read = array($this->pipes[1], $this->pipes[2]);
|
|
||||||
$streamsChanged = stream_select(
|
|
||||||
$read, $write = null, $except = null, 0, 20000
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($streamsChanged === false)
|
|
||||||
{
|
|
||||||
throw new IDF_Scm_Exception(
|
|
||||||
"Could not select() on read pipe"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($streamsChanged == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the version of the used stdio protocol
|
|
||||||
*
|
|
||||||
* @throws IDF_Scm_Exception
|
|
||||||
*/
|
|
||||||
private function _checkVersion()
|
|
||||||
{
|
|
||||||
$this->_waitForReadyRead();
|
|
||||||
|
|
||||||
$version = fgets($this->pipes[1]);
|
|
||||||
if ($version === false)
|
|
||||||
{
|
|
||||||
throw new IDF_Scm_Exception(
|
|
||||||
"Could not determine stdio version, stderr is:\n".
|
|
||||||
$this->_readStderr()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!preg_match('/^format-version: (\d+)$/', $version, $m) ||
|
|
||||||
$m[1] != self::$SUPPORTED_STDIO_VERSION)
|
|
||||||
{
|
|
||||||
throw new IDF_Scm_Exception(
|
|
||||||
"stdio format version mismatch, expected '".
|
|
||||||
self::$SUPPORTED_STDIO_VERSION."', got '".@$m[1]."'"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fgets($this->pipes[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a command to stdio
|
|
||||||
*
|
|
||||||
* @param array
|
|
||||||
* @param array
|
|
||||||
* @throws IDF_Scm_Exception
|
|
||||||
*/
|
|
||||||
private function _write(array $args, array $options = array())
|
|
||||||
{
|
|
||||||
$cmd = "";
|
|
||||||
if (count($options) > 0)
|
|
||||||
{
|
|
||||||
$cmd = "o";
|
|
||||||
foreach ($options as $k => $vals)
|
|
||||||
{
|
|
||||||
if (!is_array($vals))
|
|
||||||
$vals = array($vals);
|
|
||||||
|
|
||||||
foreach ($vals as $v)
|
|
||||||
{
|
|
||||||
$cmd .= strlen((string)$k) . ":" . (string)$k;
|
|
||||||
$cmd .= strlen((string)$v) . ":" . (string)$v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$cmd .= "e ";
|
|
||||||
}
|
|
||||||
|
|
||||||
$cmd .= "l";
|
|
||||||
foreach ($args as $arg)
|
|
||||||
{
|
|
||||||
$cmd .= strlen((string)$arg) . ":" . (string)$arg;
|
|
||||||
}
|
|
||||||
$cmd .= "e\n";
|
|
||||||
|
|
||||||
if (!fwrite($this->pipes[0], $cmd))
|
|
||||||
{
|
|
||||||
throw new IDF_Scm_Exception("could not write '$cmd' to process");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->lastcmd = $cmd;
|
|
||||||
$this->cmdnum++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads all output from stderr and returns it
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function _readStderr()
|
|
||||||
{
|
|
||||||
$err = "";
|
|
||||||
while (($line = fgets($this->pipes[2])) !== false)
|
|
||||||
{
|
|
||||||
$err .= $line;
|
|
||||||
}
|
|
||||||
return empty($err) ? "<empty>" : $err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads the last output from the stdio process, parses and returns it
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @throws IDF_Scm_Exception
|
|
||||||
*/
|
|
||||||
private function _readStdout()
|
|
||||||
{
|
|
||||||
$this->oob = array('w' => array(),
|
|
||||||
'p' => array(),
|
|
||||||
't' => array(),
|
|
||||||
'e' => array());
|
|
||||||
|
|
||||||
$output = "";
|
|
||||||
$errcode = 0;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (!$this->_waitForReadyRead())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
$data = array(0,"",0);
|
|
||||||
$idx = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
$c = fgetc($this->pipes[1]);
|
|
||||||
if ($c === false)
|
|
||||||
{
|
|
||||||
throw new IDF_Scm_Exception(
|
|
||||||
"No data on stdin, stderr is:\n".
|
|
||||||
$this->_readStderr()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($c == ':')
|
|
||||||
{
|
|
||||||
if ($idx == 2)
|
|
||||||
break;
|
|
||||||
|
|
||||||
++$idx;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_numeric($c))
|
|
||||||
$data[$idx] = $data[$idx] * 10 + $c;
|
|
||||||
else
|
|
||||||
$data[$idx] .= $c;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sanity
|
|
||||||
if ($this->cmdnum != $data[0])
|
|
||||||
{
|
|
||||||
throw new IDF_Scm_Exception(
|
|
||||||
"command numbers out of sync; ".
|
|
||||||
"expected {$this->cmdnum}, got {$data[0]}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$toRead = $data[2];
|
|
||||||
$buffer = "";
|
|
||||||
while ($toRead > 0)
|
|
||||||
{
|
|
||||||
$buffer .= fread($this->pipes[1], $toRead);
|
|
||||||
$toRead = $data[2] - strlen($buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($data[1])
|
|
||||||
{
|
|
||||||
case 'w':
|
|
||||||
case 'p':
|
|
||||||
case 't':
|
|
||||||
case 'e':
|
|
||||||
$this->oob[$data[1]][] = $buffer;
|
|
||||||
continue;
|
|
||||||
case 'm':
|
|
||||||
$output .= $buffer;
|
|
||||||
continue;
|
|
||||||
case 'l':
|
|
||||||
$errcode = $buffer;
|
|
||||||
break 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($errcode != 0)
|
|
||||||
{
|
|
||||||
throw new IDF_Scm_Exception(
|
|
||||||
"command '{$this->lastcmd}' returned error code $errcode: ".
|
|
||||||
implode(" ", $this->oob['e'])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes a command over stdio and returns its result
|
|
||||||
*
|
|
||||||
* @param array Array of arguments
|
|
||||||
* @param array Array of options as key-value pairs. Multiple options
|
|
||||||
* can be defined in sub-arrays, like
|
|
||||||
* "r" => array("123...", "456...")
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function exec(array $args, array $options = array())
|
|
||||||
{
|
|
||||||
$this->_write($args, $options);
|
|
||||||
return $this->_readStdout();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the last out-of-band output for a previously executed
|
|
||||||
* command as associative array with 'e' (error), 'w' (warning),
|
|
||||||
* 'p' (progress) and 't' (ticker, unparsed) as keys
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function getLastOutOfBandOutput()
|
|
||||||
{
|
|
||||||
return $this->oob;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monotone scm class
|
* Monotone scm class
|
||||||
|
381
src/IDF/Scm/Monotone/Stdio.php
Normal file
381
src/IDF/Scm/Monotone/Stdio.php
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
<?php
|
||||||
|
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||||
|
/*
|
||||||
|
# ***** BEGIN LICENSE BLOCK *****
|
||||||
|
# This file is part of InDefero, an open source project management application.
|
||||||
|
# Copyright (C) 2010 Céondo Ltd and contributors.
|
||||||
|
#
|
||||||
|
# InDefero is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# InDefero is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
#
|
||||||
|
# ***** END LICENSE BLOCK ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monotone stdio class
|
||||||
|
*
|
||||||
|
* Connects to a monotone process and executes commands via its
|
||||||
|
* stdio interface
|
||||||
|
*
|
||||||
|
* @author Thomas Keller <me@thomaskeller.biz>
|
||||||
|
*/
|
||||||
|
class IDF_Scm_Monotone_Stdio
|
||||||
|
{
|
||||||
|
/** this is the most recent STDIO version. The number is output
|
||||||
|
at the protocol start. Older versions of monotone (prior 0.47)
|
||||||
|
do not output it and are therefor incompatible */
|
||||||
|
public static $SUPPORTED_STDIO_VERSION = 2;
|
||||||
|
|
||||||
|
private $project;
|
||||||
|
private $proc;
|
||||||
|
private $pipes;
|
||||||
|
private $oob;
|
||||||
|
private $cmdnum;
|
||||||
|
private $lastcmd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor - starts the stdio process
|
||||||
|
*
|
||||||
|
* @param IDF_Project
|
||||||
|
*/
|
||||||
|
public function __construct(IDF_Project $project)
|
||||||
|
{
|
||||||
|
$this->project = $project;
|
||||||
|
$this->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor - stops the stdio process
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the stdio process and resets the command counter
|
||||||
|
*/
|
||||||
|
public function start()
|
||||||
|
{
|
||||||
|
if (is_resource($this->proc))
|
||||||
|
$this->stop();
|
||||||
|
|
||||||
|
$remote_db_access = Pluf::f('mtn_db_access', 'remote') == "remote";
|
||||||
|
|
||||||
|
$cmd = Pluf::f('idf_exec_cmd_prefix', '') .
|
||||||
|
Pluf::f('mtn_path', 'mtn') . ' ';
|
||||||
|
|
||||||
|
$opts = Pluf::f('mtn_opts', array());
|
||||||
|
foreach ($opts as $opt)
|
||||||
|
{
|
||||||
|
$cmd .= sprintf('%s ', escapeshellarg($opt));
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: we might want to add an option for anonymous / no key
|
||||||
|
// access, but upstream bug #30237 prevents that for now
|
||||||
|
if ($remote_db_access)
|
||||||
|
{
|
||||||
|
$host = sprintf(Pluf::f('mtn_remote_url'), $this->project->shortname);
|
||||||
|
$cmd .= sprintf('automate remote_stdio %s', escapeshellarg($host));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$repo = sprintf(Pluf::f('mtn_repositories'), $this->project->shortname);
|
||||||
|
if (!file_exists($repo))
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"repository file '$repo' does not exist"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$cmd .= sprintf('--db %s automate stdio', escapeshellarg($repo));
|
||||||
|
}
|
||||||
|
|
||||||
|
$descriptors = array(
|
||||||
|
0 => array("pipe", "r"),
|
||||||
|
1 => array("pipe", "w"),
|
||||||
|
2 => array("pipe", "w"),
|
||||||
|
);
|
||||||
|
|
||||||
|
$env = array("LANG" => "en_US.UTF-8");
|
||||||
|
|
||||||
|
$this->proc = proc_open($cmd, $descriptors, $this->pipes,
|
||||||
|
null, $env);
|
||||||
|
|
||||||
|
if (!is_resource($this->proc))
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception("could not start stdio process");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_checkVersion();
|
||||||
|
|
||||||
|
$this->cmdnum = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the stdio process and closes all pipes
|
||||||
|
*/
|
||||||
|
public function stop()
|
||||||
|
{
|
||||||
|
if (!is_resource($this->proc))
|
||||||
|
return;
|
||||||
|
|
||||||
|
fclose($this->pipes[0]);
|
||||||
|
fclose($this->pipes[1]);
|
||||||
|
fclose($this->pipes[2]);
|
||||||
|
|
||||||
|
proc_close($this->proc);
|
||||||
|
$this->proc = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* select()'s on stdout and returns true as soon as we got new
|
||||||
|
* data to read, false if the select() timed out
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
* @throws IDF_Scm_Exception
|
||||||
|
*/
|
||||||
|
private function _waitForReadyRead()
|
||||||
|
{
|
||||||
|
if (!is_resource($this->pipes[1]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$read = array($this->pipes[1], $this->pipes[2]);
|
||||||
|
$streamsChanged = stream_select(
|
||||||
|
$read, $write = null, $except = null, 0, 20000
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($streamsChanged === false)
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"Could not select() on read pipe"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($streamsChanged == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the version of the used stdio protocol
|
||||||
|
*
|
||||||
|
* @throws IDF_Scm_Exception
|
||||||
|
*/
|
||||||
|
private function _checkVersion()
|
||||||
|
{
|
||||||
|
$this->_waitForReadyRead();
|
||||||
|
|
||||||
|
$version = fgets($this->pipes[1]);
|
||||||
|
if ($version === false)
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"Could not determine stdio version, stderr is:\n".
|
||||||
|
$this->_readStderr()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preg_match('/^format-version: (\d+)$/', $version, $m) ||
|
||||||
|
$m[1] != self::$SUPPORTED_STDIO_VERSION)
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"stdio format version mismatch, expected '".
|
||||||
|
self::$SUPPORTED_STDIO_VERSION."', got '".@$m[1]."'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fgets($this->pipes[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a command to stdio
|
||||||
|
*
|
||||||
|
* @param array
|
||||||
|
* @param array
|
||||||
|
* @throws IDF_Scm_Exception
|
||||||
|
*/
|
||||||
|
private function _write(array $args, array $options = array())
|
||||||
|
{
|
||||||
|
$cmd = "";
|
||||||
|
if (count($options) > 0)
|
||||||
|
{
|
||||||
|
$cmd = "o";
|
||||||
|
foreach ($options as $k => $vals)
|
||||||
|
{
|
||||||
|
if (!is_array($vals))
|
||||||
|
$vals = array($vals);
|
||||||
|
|
||||||
|
foreach ($vals as $v)
|
||||||
|
{
|
||||||
|
$cmd .= strlen((string)$k) . ":" . (string)$k;
|
||||||
|
$cmd .= strlen((string)$v) . ":" . (string)$v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$cmd .= "e ";
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd .= "l";
|
||||||
|
foreach ($args as $arg)
|
||||||
|
{
|
||||||
|
$cmd .= strlen((string)$arg) . ":" . (string)$arg;
|
||||||
|
}
|
||||||
|
$cmd .= "e\n";
|
||||||
|
|
||||||
|
if (!fwrite($this->pipes[0], $cmd))
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception("could not write '$cmd' to process");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->lastcmd = $cmd;
|
||||||
|
$this->cmdnum++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads all output from stderr and returns it
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function _readStderr()
|
||||||
|
{
|
||||||
|
$err = "";
|
||||||
|
while (($line = fgets($this->pipes[2])) !== false)
|
||||||
|
{
|
||||||
|
$err .= $line;
|
||||||
|
}
|
||||||
|
return empty($err) ? "<empty>" : $err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the last output from the stdio process, parses and returns it
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @throws IDF_Scm_Exception
|
||||||
|
*/
|
||||||
|
private function _readStdout()
|
||||||
|
{
|
||||||
|
$this->oob = array('w' => array(),
|
||||||
|
'p' => array(),
|
||||||
|
't' => array(),
|
||||||
|
'e' => array());
|
||||||
|
|
||||||
|
$output = "";
|
||||||
|
$errcode = 0;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (!$this->_waitForReadyRead())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$data = array(0,"",0);
|
||||||
|
$idx = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
$c = fgetc($this->pipes[1]);
|
||||||
|
if ($c === false)
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"No data on stdin, stderr is:\n".
|
||||||
|
$this->_readStderr()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($c == ':')
|
||||||
|
{
|
||||||
|
if ($idx == 2)
|
||||||
|
break;
|
||||||
|
|
||||||
|
++$idx;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_numeric($c))
|
||||||
|
$data[$idx] = $data[$idx] * 10 + $c;
|
||||||
|
else
|
||||||
|
$data[$idx] .= $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanity
|
||||||
|
if ($this->cmdnum != $data[0])
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"command numbers out of sync; ".
|
||||||
|
"expected {$this->cmdnum}, got {$data[0]}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$toRead = $data[2];
|
||||||
|
$buffer = "";
|
||||||
|
while ($toRead > 0)
|
||||||
|
{
|
||||||
|
$buffer .= fread($this->pipes[1], $toRead);
|
||||||
|
$toRead = $data[2] - strlen($buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($data[1])
|
||||||
|
{
|
||||||
|
case 'w':
|
||||||
|
case 'p':
|
||||||
|
case 't':
|
||||||
|
case 'e':
|
||||||
|
$this->oob[$data[1]][] = $buffer;
|
||||||
|
continue;
|
||||||
|
case 'm':
|
||||||
|
$output .= $buffer;
|
||||||
|
continue;
|
||||||
|
case 'l':
|
||||||
|
$errcode = $buffer;
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($errcode != 0)
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"command '{$this->lastcmd}' returned error code $errcode: ".
|
||||||
|
implode(" ", $this->oob['e'])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a command over stdio and returns its result
|
||||||
|
*
|
||||||
|
* @param array Array of arguments
|
||||||
|
* @param array Array of options as key-value pairs. Multiple options
|
||||||
|
* can be defined in sub-arrays, like
|
||||||
|
* "r" => array("123...", "456...")
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function exec(array $args, array $options = array())
|
||||||
|
{
|
||||||
|
$this->_write($args, $options);
|
||||||
|
return $this->_readStdout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last out-of-band output for a previously executed
|
||||||
|
* command as associative array with 'e' (error), 'w' (warning),
|
||||||
|
* 'p' (progress) and 't' (ticker, unparsed) as keys
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getLastOutOfBandOutput()
|
||||||
|
{
|
||||||
|
return $this->oob;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
249
src/IDF/Scm/Monotone/Usher.php
Normal file
249
src/IDF/Scm/Monotone/Usher.php
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
<?php
|
||||||
|
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||||
|
/*
|
||||||
|
# ***** BEGIN LICENSE BLOCK *****
|
||||||
|
# This file is part of InDefero, an open source project management application.
|
||||||
|
# Copyright (C) 2010 Céondo Ltd and contributors.
|
||||||
|
#
|
||||||
|
# InDefero is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# InDefero is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
#
|
||||||
|
# ***** END LICENSE BLOCK ***** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects with the admininistrative interface of usher,
|
||||||
|
* the monotone proxy. This class contains only static methods because
|
||||||
|
* there is really no state to keep between each invocation, as usher
|
||||||
|
* closes the connection after every command.
|
||||||
|
*
|
||||||
|
* @author Thomas Keller <me@thomaskeller.biz>
|
||||||
|
*/
|
||||||
|
class IDF_Scm_Monotone_Usher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Without giving a specific state, returns an array of all servers.
|
||||||
|
* When a state is given, the array contains only servers which are
|
||||||
|
* in the given state.
|
||||||
|
*
|
||||||
|
* @param string $state One of REMOTE, ACTIVE, WAITING, SLEEPING,
|
||||||
|
* STOPPING, STOPPED, SHUTTINGDOWN or SHUTDOWN
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getServerList($state = null)
|
||||||
|
{
|
||||||
|
$conn = self::_triggerCommand("LIST $state");
|
||||||
|
if ($conn == "none")
|
||||||
|
return array();
|
||||||
|
|
||||||
|
return preg_split("/[ ]/", $conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all open connections to the given server, or to
|
||||||
|
* any server if no server is specified.
|
||||||
|
* If there are no connections to list, an empty array is returned.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* array("server1" => array(
|
||||||
|
* array("address" => "192.168.1.0", "port" => "13456"),
|
||||||
|
* ...
|
||||||
|
* ),
|
||||||
|
* "server2" => ...
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function getConnectionList($server = null)
|
||||||
|
{
|
||||||
|
$conn = self::_triggerCommand("LISTCONNECTIONS $server");
|
||||||
|
if ($conn == "none")
|
||||||
|
return array();
|
||||||
|
|
||||||
|
$single_conns = preg_split("/[ ]/", $conn);
|
||||||
|
$ret = array();
|
||||||
|
foreach ($single_conns as $conn)
|
||||||
|
{
|
||||||
|
preg_match("/\(\w+\)([^:]):(\d+)/", $conn, $matches);
|
||||||
|
$ret[$matches[1]][] = (object)array(
|
||||||
|
"server" => $matches[1],
|
||||||
|
"address" => $matches[2],
|
||||||
|
"port" => $matches[3],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the status of a particular server, or of the usher as a whole if
|
||||||
|
* no server is specified.
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return One of REMOTE, SLEEPING, STOPPING, STOPPED for servers or
|
||||||
|
* ACTIVE, WAITING, SHUTTINGDOWN or SHUTDOWN for usher itself
|
||||||
|
*/
|
||||||
|
public static function getStatus($server = null)
|
||||||
|
{
|
||||||
|
return self::_triggerCommand("STATUS $server");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up the name of the server that would be used for an incoming
|
||||||
|
* connection having the given host and pattern.
|
||||||
|
*
|
||||||
|
* @param string $host Host
|
||||||
|
* @param string $pattern Branch pattern
|
||||||
|
* @return server name
|
||||||
|
* @throws IDF_Scm_Exception
|
||||||
|
*/
|
||||||
|
public static function matchServer($host, $pattern)
|
||||||
|
{
|
||||||
|
$ret = self::_triggerCommand("MATCH $host $pattern");
|
||||||
|
if (preg_match("/^OK: (.+)/", $ret, $m))
|
||||||
|
return $m[1];
|
||||||
|
preg_match("/^ERROR: (.+)/", $ret, $m);
|
||||||
|
throw new IDF_Scm_Exception("could not match server: ".$m[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent the given local server from receiving further connections,
|
||||||
|
* and stop it once all connections are closed. The return value will
|
||||||
|
* be the new status of that server: ACTIVE local servers will become
|
||||||
|
* STOPPING, and WAITING and SLEEPING serveres become STOPPED.
|
||||||
|
* Servers in other states are not affected.
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return string State of the server after the command
|
||||||
|
*/
|
||||||
|
public static function stopServer($server)
|
||||||
|
{
|
||||||
|
return self::_triggerCommand("STOP $server");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow a STOPPED or STOPPING server to receive connections again.
|
||||||
|
* The return value is the new status of that server: STOPPING servers
|
||||||
|
* become ACTIVE, and STOPPED servers become SLEEPING. Servers in other
|
||||||
|
* states are not affected.
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return string State of the server after the command
|
||||||
|
*/
|
||||||
|
public static function startServer($server)
|
||||||
|
{
|
||||||
|
return self::_triggerCommand("START $server");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately kill the given local server, dropping any open connections,
|
||||||
|
* and prevent is from receiving new connections and restarting. The named
|
||||||
|
* server will immediately change to state STOPPED.
|
||||||
|
*
|
||||||
|
* @param string $server
|
||||||
|
* @return bool True if successful
|
||||||
|
*/
|
||||||
|
public static function killServer($server)
|
||||||
|
{
|
||||||
|
return self::_triggerCommand("KILL_NOW $server") == "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not accept new connections for any servers, local or remote.
|
||||||
|
*
|
||||||
|
* @return bool True if successful
|
||||||
|
*/
|
||||||
|
public static function shutDown()
|
||||||
|
{
|
||||||
|
return self::_triggerCommand("SHUTDOWN") == "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin accepting connections after a SHUTDOWN.
|
||||||
|
*
|
||||||
|
* @return bool True if successful
|
||||||
|
*/
|
||||||
|
public static function startUp()
|
||||||
|
{
|
||||||
|
return self::_triggerCommand("STARTUP") == "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload the config file, the same as sending SIGHUP.
|
||||||
|
*
|
||||||
|
* @return bool True if successful (after the configuration was reloaded)
|
||||||
|
*/
|
||||||
|
public static function reload()
|
||||||
|
{
|
||||||
|
return self::_triggerCommand("RELOAD") == "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function _triggerCommand($cmd)
|
||||||
|
{
|
||||||
|
$uc = Pluf::f('mtn_usher');
|
||||||
|
if (empty($uc['host']))
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception("usher host is empty");
|
||||||
|
}
|
||||||
|
if (!preg_match('/^\d+$/', $uc['port']) ||
|
||||||
|
$uc['port'] == 0)
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception("usher port is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($uc['user']))
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception("usher user is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($uc['pass']))
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception("usher pass is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
$sock = @fsockopen($uc['host'], $uc['port'], $errno, $errstr);
|
||||||
|
if (!$sock)
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"could not connect to usher: $errstr ($errno)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite($sock, "USERPASS {$uc['user']} {$uc['pass']}\n");
|
||||||
|
if (feof($sock))
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception(
|
||||||
|
"usher closed the connection - probably wrong admin ".
|
||||||
|
"username or password"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite($sock, "$cmd\n");
|
||||||
|
$out = "";
|
||||||
|
while (!feof($sock))
|
||||||
|
{
|
||||||
|
$out .= fgets($sock);
|
||||||
|
}
|
||||||
|
fclose($sock);
|
||||||
|
$out = rtrim($out);
|
||||||
|
|
||||||
|
if ($out == "unknown command")
|
||||||
|
{
|
||||||
|
throw new IDF_Scm_Exception("unknown command: $cmd");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -314,6 +314,159 @@ class IDF_Views_Admin
|
|||||||
),
|
),
|
||||||
$request);
|
$request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usher servers overview
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public $usher_precond = array('Pluf_Precondition::staffRequired');
|
||||||
|
public function usher($request, $match)
|
||||||
|
{
|
||||||
|
$title = __('Usher management');
|
||||||
|
$servers = array();
|
||||||
|
foreach (IDF_Scm_Monotone_Usher::getServerList() as $server)
|
||||||
|
{
|
||||||
|
$servers[] = (object)array(
|
||||||
|
"name" => $server,
|
||||||
|
"status" => IDF_Scm_Monotone_Usher::getStatus($server),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pluf_Shortcuts_RenderToResponse(
|
||||||
|
'idf/gadmin/usher/index.html',
|
||||||
|
array(
|
||||||
|
'page_title' => $title,
|
||||||
|
'servers' => $servers,
|
||||||
|
),
|
||||||
|
$request
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usher control
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public $usherControl_precond = array('Pluf_Precondition::staffRequired');
|
||||||
|
public function usherControl($request, $match)
|
||||||
|
{
|
||||||
|
$title = __('Usher control');
|
||||||
|
$action = $match[1];
|
||||||
|
|
||||||
|
if (!empty($action))
|
||||||
|
{
|
||||||
|
if (!in_array($action, array("reload", "shutdown", "startup")))
|
||||||
|
{
|
||||||
|
throw new Pluf_HTTP_Error404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$msg = null;
|
||||||
|
if ($action == "reload")
|
||||||
|
{
|
||||||
|
IDF_Scm_Monotone_Usher::reload();
|
||||||
|
$msg = __('Usher configuration has been reloaded');
|
||||||
|
}
|
||||||
|
else if ($action == "shutdown")
|
||||||
|
{
|
||||||
|
IDF_Scm_Monotone_Usher::shutDown();
|
||||||
|
$msg = __('Usher has been shut down');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IDF_Scm_Monotone_Usher::startUp();
|
||||||
|
$msg = __('Usher has been started up');
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->user->setMessage($msg);
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::usherControl', array(''));
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pluf_Shortcuts_RenderToResponse(
|
||||||
|
'idf/gadmin/usher/control.html',
|
||||||
|
array(
|
||||||
|
'page_title' => $title,
|
||||||
|
'status' => IDF_Scm_Monotone_Usher::getStatus(),
|
||||||
|
),
|
||||||
|
$request
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usher control
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public $usherServerControl_precond = array('Pluf_Precondition::staffRequired');
|
||||||
|
public function usherServerControl($request, $match)
|
||||||
|
{
|
||||||
|
$server = $match[1];
|
||||||
|
if (!in_array($server, IDF_Scm_Monotone_Usher::getServerList()))
|
||||||
|
{
|
||||||
|
throw new Pluf_HTTP_Error404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = $match[2];
|
||||||
|
if (!in_array($action, array("start", "stop", "kill")))
|
||||||
|
{
|
||||||
|
throw new Pluf_HTTP_Error404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$msg = null;
|
||||||
|
if ($action == "start")
|
||||||
|
{
|
||||||
|
IDF_Scm_Monotone_Usher::startServer($server);
|
||||||
|
$msg = sprintf(__('The server "%s" has been started'), $server);
|
||||||
|
}
|
||||||
|
else if ($action == "stop")
|
||||||
|
{
|
||||||
|
IDF_Scm_Monotone_Usher::stopServer($server);
|
||||||
|
$msg = sprintf(__('The server "%s" has been stopped'), $server);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IDF_Scm_Monotone_Usher::killServer($server);
|
||||||
|
$msg = sprintf(__('The server "%s" has been killed'), $server);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->user->setMessage($msg);
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::usher');
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open connections for a configured server
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public $usherServerConnections_precond = array('Pluf_Precondition::staffRequired');
|
||||||
|
public function usherServerConnections($request, $match)
|
||||||
|
{
|
||||||
|
$server = $match[1];
|
||||||
|
if (!in_array($server, IDF_Scm_Monotone_Usher::getServerList()))
|
||||||
|
{
|
||||||
|
throw new Pluf_HTTP_Error404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = sprintf(__('Open connections for "%s"'), $server);
|
||||||
|
|
||||||
|
$connections = IDF_Scm_Monotone_Usher::getConnectionList($server);
|
||||||
|
if (count($connections) == 0)
|
||||||
|
{
|
||||||
|
$request->user->setMessage(sprintf(
|
||||||
|
__('no connections for server "%s"'), $server
|
||||||
|
));
|
||||||
|
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Admin::usher');
|
||||||
|
return new Pluf_HTTP_Response_Redirect($url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pluf_Shortcuts_RenderToResponse(
|
||||||
|
'idf/gadmin/usher/connections.html',
|
||||||
|
array(
|
||||||
|
'page_title' => $title,
|
||||||
|
'server' => $server,
|
||||||
|
'connections' => $connections,
|
||||||
|
),
|
||||||
|
$request
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function IDF_Views_Admin_bool($field, $item)
|
function IDF_Views_Admin_bool($field, $item)
|
||||||
|
@ -103,7 +103,7 @@ $cfg['mtn_opts'] = array('--no-workspace', '--norc');
|
|||||||
# Then download and configure 'usher'
|
# Then download and configure 'usher'
|
||||||
# (mtn clone mtn://monotone.ca?net.venge.monotone.contrib.usher)
|
# (mtn clone mtn://monotone.ca?net.venge.monotone.contrib.usher)
|
||||||
# which acts as proxy in front of all single project databases.
|
# which acts as proxy in front of all single project databases.
|
||||||
# Usher's server name should be mapped to the project's short name,
|
# Usher's server names should be mapped to the project's short names,
|
||||||
# so you end up with something like this for every project:
|
# so you end up with something like this for every project:
|
||||||
#
|
#
|
||||||
# server "project"
|
# server "project"
|
||||||
@ -137,9 +137,25 @@ $cfg['mtn_remote_url'] = 'mtn://my-host.biz/%s';
|
|||||||
# Note that you need to setup the hook 'get_remote_automate_permitted' for
|
# Note that you need to setup the hook 'get_remote_automate_permitted' for
|
||||||
# each remotely accessible database. A full HOWTO set this up is beyond this
|
# each remotely accessible database. A full HOWTO set this up is beyond this
|
||||||
# scope, please refer to the documentation of monotone and / or ask on the
|
# scope, please refer to the documentation of monotone and / or ask on the
|
||||||
# mailing list / IRC channel (irc.oftc.net/#monotone)
|
# mailing list (monotone-users@nongnu.org) or IRC channel
|
||||||
|
# (irc.oftc.net/#monotone)
|
||||||
#
|
#
|
||||||
$cfg['mtn_db_access'] = 'remote';
|
$cfg['mtn_db_access'] = 'remote';
|
||||||
|
#
|
||||||
|
# If configured, this allows basic control of a running usher process
|
||||||
|
# via the forge administration
|
||||||
|
#
|
||||||
|
# 'host' and 'port' must be set to the specific bits from usher's
|
||||||
|
# configured 'adminaddr', 'user' and 'pass' must match the values set for
|
||||||
|
# the configured 'userpass' combination
|
||||||
|
#
|
||||||
|
#$cfg['mtn_usher'] = array(
|
||||||
|
# 'host' => 'localhost',
|
||||||
|
# 'port' => 12345,
|
||||||
|
# 'user' => 'admin',
|
||||||
|
# 'pass' => 'admin',
|
||||||
|
#);
|
||||||
|
#
|
||||||
|
|
||||||
# Mercurial repositories path
|
# Mercurial repositories path
|
||||||
#$cfg['mercurial_repositories'] = '/home/mercurial/repositories/%s';
|
#$cfg['mercurial_repositories'] = '/home/mercurial/repositories/%s';
|
||||||
|
@ -386,6 +386,29 @@ $ctl[] = array('regex' => '#^/admin/users/(\d+)/$#',
|
|||||||
'model' => 'IDF_Views_Admin',
|
'model' => 'IDF_Views_Admin',
|
||||||
'method' => 'userUpdate');
|
'method' => 'userUpdate');
|
||||||
|
|
||||||
|
if (Pluf::f("mtn_usher", null) !== null)
|
||||||
|
{
|
||||||
|
$ctl[] = array('regex' => '#^/admin/usher/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Admin',
|
||||||
|
'method' => 'usher');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/admin/usher/control/(.*)$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Admin',
|
||||||
|
'method' => 'usherControl');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/admin/usher/server/(.+)/control/(.+)$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Admin',
|
||||||
|
'method' => 'usherServerControl');
|
||||||
|
|
||||||
|
$ctl[] = array('regex' => '#^/admin/usher/server/(.+)/connections/$#',
|
||||||
|
'base' => $base,
|
||||||
|
'model' => 'IDF_Views_Admin',
|
||||||
|
'method' => 'usherServerConnections');
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- UTILITY VIEWS -------------------------------
|
// ---------- UTILITY VIEWS -------------------------------
|
||||||
|
|
||||||
$ctl[] = array('regex' => '#^/register/$#',
|
$ctl[] = array('regex' => '#^/register/$#',
|
||||||
|
@ -43,6 +43,9 @@
|
|||||||
<div id="main-tabs">
|
<div id="main-tabs">
|
||||||
<a href="{url 'IDF_Views_Admin::projects'}"{block tabprojects}{/block}>{trans 'Projects'}</a>
|
<a href="{url 'IDF_Views_Admin::projects'}"{block tabprojects}{/block}>{trans 'Projects'}</a>
|
||||||
<a href="{url 'IDF_Views_Admin::users'}"{block tabusers}{/block}>{trans 'People'}</a>
|
<a href="{url 'IDF_Views_Admin::users'}"{block tabusers}{/block}>{trans 'People'}</a>
|
||||||
|
{if $usherConfigured}
|
||||||
|
<a href="{url 'IDF_Views_Admin::usher'}"{block tabusher}{/block}>{trans 'Usher'}</a>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div id="sub-tabs">{block subtabs}{/block}</div>
|
<div id="sub-tabs">{block subtabs}{/block}</div>
|
||||||
</div>
|
</div>
|
||||||
|
6
src/IDF/templates/idf/gadmin/usher/base.html
Normal file
6
src/IDF/templates/idf/gadmin/usher/base.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{extends "idf/gadmin/base.html"}
|
||||||
|
{block tabprojects} class="active"{/block}
|
||||||
|
{block subtabs}
|
||||||
|
<a {if $inUsher}class="active" {/if}href="{url 'IDF_Views_Admin::usher'}">{trans 'Configured servers'}</a> |
|
||||||
|
<a {if $inUsherControl}class="active" {/if}href="{url 'IDF_Views_Admin::usherControl', array('')}">{trans 'Usher control'}</a>
|
||||||
|
{/block}
|
19
src/IDF/templates/idf/gadmin/usher/connections.html
Normal file
19
src/IDF/templates/idf/gadmin/usher/connections.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{extends "idf/gadmin/usher/base.html"}
|
||||||
|
|
||||||
|
{block docclass}yui-t3{assign $inUsherServerConnections=true}{/block}
|
||||||
|
|
||||||
|
{block body}
|
||||||
|
<table class="recent-issues">
|
||||||
|
<tr>
|
||||||
|
<th>{trans "address"}</th>
|
||||||
|
<th>{trans "port"}</th>
|
||||||
|
</tr>
|
||||||
|
{foreach $connections as $connection}
|
||||||
|
<tr>
|
||||||
|
<td>{$connection.address}</td>
|
||||||
|
<td>{$connection.port}</td>
|
||||||
|
</tr>
|
||||||
|
{/foreach}
|
||||||
|
</table>
|
||||||
|
{/block}
|
||||||
|
|
32
src/IDF/templates/idf/gadmin/usher/control.html
Normal file
32
src/IDF/templates/idf/gadmin/usher/control.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{extends "idf/gadmin/usher/base.html"}
|
||||||
|
|
||||||
|
{block docclass}yui-t3{assign $inUsherControl=true}{/block}
|
||||||
|
|
||||||
|
{block body}
|
||||||
|
<p>
|
||||||
|
{trans 'current server status:'} {$status} |
|
||||||
|
{if $status == "SHUTDOWN"}
|
||||||
|
<a href="{url 'IDF_Views_Admin::usherControl', array('startup')}">{trans 'startup'}</a>
|
||||||
|
{else}
|
||||||
|
<a href="{url 'IDF_Views_Admin::usherControl', array('shutdown')}">{trans 'shutdown'}</a>
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>{trans 'reload server configuration:'}
|
||||||
|
<a href="{url 'IDF_Views_Admin::usherControl', array('reload')}">{trans 'reload'}</a>
|
||||||
|
</p>
|
||||||
|
{/block}
|
||||||
|
|
||||||
|
{block context}
|
||||||
|
<div class="issue-submit-info">
|
||||||
|
<p><strong>{trans 'Status explanation'}</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>ACTIVE n: {trans 'active with n total open connections'}</li>
|
||||||
|
<li>WAITING: {trans 'waiting for new connections'}</li>
|
||||||
|
<li>SHUTTINGDOWN: {trans 'usher is being shut down, not accepting connections'}</li>
|
||||||
|
<li>SHUTDOWN: {trans 'usher is shut down, all local servers are stopped and not accepting connections'}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{/block}
|
||||||
|
|
52
src/IDF/templates/idf/gadmin/usher/index.html
Normal file
52
src/IDF/templates/idf/gadmin/usher/index.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{extends "idf/gadmin/usher/base.html"}
|
||||||
|
|
||||||
|
{block docclass}yui-t3{assign $inUsher=true}{/block}
|
||||||
|
|
||||||
|
{block body}
|
||||||
|
<table class="recent-issues">
|
||||||
|
<tr>
|
||||||
|
<th>{trans "server name"}</th>
|
||||||
|
<th>{trans "status"}</th>
|
||||||
|
<th>{trans "action"}</th>
|
||||||
|
</tr>
|
||||||
|
{foreach $servers as $server}
|
||||||
|
<tr>
|
||||||
|
<td>{$server.name}</td>
|
||||||
|
<td>{$server.status}</td>
|
||||||
|
<td>
|
||||||
|
{if preg_match("/ACTIVE|RUNNING|SLEEPING/", $server.status)}
|
||||||
|
<a href="{url 'IDF_Views_Admin::usherServerControl', array($server.name, 'stop')}">
|
||||||
|
{trans 'stop'}</a>
|
||||||
|
{elseif $server.status == "STOPPED"}
|
||||||
|
<a href="{url 'IDF_Views_Admin::usherServerControl', array($server.name, 'start')}">
|
||||||
|
{trans 'start'}</a>
|
||||||
|
{/if}
|
||||||
|
{if preg_match("/ACTIVE|WAITING|SLEEPING|STOPPING/", $server.status)}
|
||||||
|
| <a href="{url 'IDF_Views_Admin::usherServerControl', array($server.name, 'kill')}">
|
||||||
|
{trans 'kill'}</a>
|
||||||
|
{/if}
|
||||||
|
{if preg_match("/STOPPING|ACTIVE/", $server.status)}
|
||||||
|
| <a href="{url 'IDF_Views_Admin::usherServerConnections', array($server.name)}">
|
||||||
|
{trans 'active connections'}</a>
|
||||||
|
{/if}
|
||||||
|
</tr>
|
||||||
|
{/foreach}
|
||||||
|
</table>
|
||||||
|
{/block}
|
||||||
|
|
||||||
|
{block context}
|
||||||
|
<div class="issue-submit-info">
|
||||||
|
<p><strong>{trans 'Status explanation'}</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>REMOTE: {trans 'remote server without open connections'}</li>
|
||||||
|
<li>ACTIVE n: {trans 'server with n open connections'}</li>
|
||||||
|
<li>WAITING: {trans 'local server running, without open connections'}</li>
|
||||||
|
<li>SLEEPING: {trans 'local server not running, waiting for connections'}</li>
|
||||||
|
<li>STOPPING n: {trans 'local server is about to stop, n connections still open'}</li>
|
||||||
|
<li>STOPPED: {trans 'local server not running, not accepting connections'}</li>
|
||||||
|
<li>SHUTDOWN: {trans 'usher is shut down, not running and not accepting connections'}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{/block}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user