Skip to content

Instantly share code, notes, and snippets.

@carcus88
Created August 23, 2011 16:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save carcus88/1165610 to your computer and use it in GitHub Desktop.
Save carcus88/1165610 to your computer and use it in GitHub Desktop.
CakeShell - SOS JobScheduler CakeShell job for executing cakePHP shells
function spooler_process() {
try {
var commandline;
var parameters;
var subprocess = spooler_task.create_subprocess();
if (spooler_task.params.value("shell_command") != null && spooler_task.params.value("shell_command").length > 0) {
commandline = spooler_task.params.value("shell_command");
} else {
throw "no value for shell command was given for parameter [shell_command]";
}
/* these are samples for shell commands:
if ( String(java.lang.System.getProperty("os.name")).toLowerCase().substring(0,3) === "win" ) {
// Windows: the command to be executed, modify this to an executable script for your platform
commandline = "cmd.exe /V:ON /C " + spooler.directory + "samples/shell.webservice.command/webservice_shell.cmd";
} else {
// Unix: the command to be executed, modify this to an executable script for your platform
commandline = spooler.directory + "samples/shell.webservice.command/webservice_shell.sh";
}
*/
// retrieve parameters from order payload (<job order="yes"/>) or from task parameters
if ( spooler_job.order_queue != null ) {
parameters = spooler_task.order.payload;
} else {
parameters = spooler_task.params;
}
if (parameters != null) {
var parameter_names = parameters.names().split(";");
// Sort params by name since at this stage they are random order
// This is because the shell does not take param key=value pairs, it takes params in order so
// send params as param_1 => value param_2 => value etc and they will be kept in order
parameter_names.sort();
for(var i=0; i<parameter_names.length; i++) {
if (parameter_names[i].toLowerCase() == "shell_command") continue;
// .. either append parameter names and values to the comamnd line (this needs more checking for vulnerabilities)
// spooler_log.debug3( "parameter " + parameter_names[i] + "=" + parameters.value(parameter_names[i]) );
// commandline += " " + parameter_names[i] + "=\"" + parameters.value(parameter_names[i]) + "\"";
// .. or move parameters to environment variables which is more safe
subprocess.environment( parameter_names[i] ) = parameters.value( parameter_names[i] );
// .. and for this sample append parameter names to the command line
commandline += " \""+parameters.value( parameter_names[i] )+"\"";
}
}
spooler_log.info( ".. executing command line: " + commandline );
subprocess.start( commandline );
// wait until the subprocess terminates, timeout is restricted by <job timeout="..."/> or subprocess.wait_for_termination(60)
subprocess.wait_for_termination();
if (subprocess.terminated) {
spooler_log.info( "exit code=" + subprocess.exit_code );
spooler_log.info( "termination signal=" + subprocess.termination_signal );
}
return !(spooler_job.order_queue == null);
} catch (e) {
spooler_log.warn( String(e) );
return false;
}
}
<?xml version="1.0" encoding="iso-8859-1"?>
<spooler>
<config>
<web_services>
<web_service debug="no" name="CakeShell" url_path="/CakeShell"
request_xslt_stylesheet="config/scheduler_soap_request_modified.xslt"
response_xslt_stylesheet="config/scheduler_soap_response.xslt">
</web_service>
<jobs>
<job name="CakeShell" title="EzCallout CakeShell" tasks="100">
<description>
<include file="jobs\JobSchedulerWebServiceShellJob.xml"/>
</description>
<params>
<param name="shell_command" value="&quot;c:\path\to\cake\console\cake.bat&quot; -console .\console -app &quot;c:\your_app\app&quot; "/>
</params>
<script language="javascript">
<include file="jobs\cake_shell_generic.js"/>
</script>
</job>
<config>
<spooler>
<?php
/**
* The nusoap client is used to connect to the SOS Job Scheduler SOAP interface
*
* Note the SOS job scheulder interface uses a modified XSLT transformation (scheduler_soap_request_modified.xslt)
* to translate SOAP XML into the Job XML format.
* This modification was necessary since real SOAP calls use prefixes and the default XSLT (scheduler_soap_request.xslt)
* did not match or support this properly.
*
* @internal To debug SOAP calls use $client->setGlobalDebugLevel(9);
*/
/**
* SOAP client
*/
App::import('Vendor', 'nusoap', array('file' => 'nusoap/nusoap.php'));
/**
* Class to communicate with SOS Job Scheduler
*
*/
class SchedulerRpcComponent extends Object {
/**
* Contains the controller this component is intitialized in
*
* @var object
*/
public $controller;
/**
* The host the job scheduler is running on
*
* @var string
*/
public $host = 'localhost';
/**
* The port the job scheduler is running on
*
* @var string
*/
public $port = "4444";
/**
* The last error message recieved from the server
* @var string
*/
public $lastErrorMessage = null;
/**
* Get the last error message
* @return string The last error message stored in $this->lastErrorMessage
*/
public function getLastErrorMessage() {
return $this->lastErrorMessage;
}
/**
* Call any cake shell
*
* @param string $shell_name The shell name to call
* @param string $shell_function The shell function to execute
* @param array $params List of parameters to send to the command
* @returns mixed Task ID on sucess, false on error
* @since 1.0
*/
public function cake_shell($shell_name, $shell_function, $params) {
$webservices_uri = "http://" . $this->host . ":" . $this->port . '/CakeShell';
$sendParams = array();
$sendParams = array($shell_name,$shell_function);
foreach($params as $i=>$v) {
$sendParams[] = $v;
}
$allParams = array( 'job' => '',
'param' => $sendParams);
$allParams = '<job>CakeShell</job>';
foreach ($sendParams as $i=>$v) {
$allParams .= "\n<param><name>{$i}</name><value>{$v}</value></param>";
}
$namespace = 'http://www.sos-berlin.com/scheduler';
$client = new nusoap_client($webservices_uri, false, null, null, null, null, 10, 30);
$resp = $client->call('startJob',$allParams,$namespace);
$this->lastErrorMessage = $client->getError();
if ( $this->lastErrorMessage ) {
return false;
} else {
$taskID = $client->return;
if (is_numeric($taskID)) {
return $taskID;
} else {
return false;
}
}
}
/**
* Check if a task is complete
*
* @param integer $taskID Task ID to check
* @return boolean True if the task is complete, otherwise false
*/
public function isTaskComplete($taskID) {
$cmd = "<show_task id=\"{$taskID}\"/>\r\n";
$xml = $this->_sCommand($cmd);
if ( ! $xml ) {
$this->lastErrorMessage = 'XML command failed. ({$cmd})';
return false;
}
// Convert the XML to an object and check the results for a valid end_time
$data = simplexml_load_string($xml);
if ( ! $data
|| ! property_exists($data, 'answer')
|| ! property_exists($data->answer, 'task')) {
return false;
}
if ( (string) $data->answer->task['end_time']) {
return true;
}
}
/**
* Execute a socket command on the scheduler
*
* NOTE: needs to be tested more on commands that return more then 1024 bytes
*
* @link http://localhost:4444/doc/en/xml_commands.xml
* @param string $cmd Command to execute
* @return mixed XML return value of the command on success, otherwise false
*/
public function _sCommand($cmd) {
$s = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($s, $this->host, $this->port);
$bw = socket_write($s, $cmd);
$a = '';
do {
$b = '';
$b = socket_read($s, 1024, PHP_BINARY_READ);
if($b != "") {
$a .= $b;
}
} while( ! strstr($b, "\x00"));
if ( ! $a
|| substr($a,0,5) != '<?xml' ) {
return false;
}
socket_close($s);
return $a;
}
}
?>
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sos="http://www.sos-berlin.com/scheduler"
exclude-result-prefixes="soapenv sos" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="jobchain"></xsl:param>
<xsl:template match="/service_request/content/soapenv:Envelope | /soapenv:Envelope">
<xsl:apply-templates select="soapenv:Body"/>
</xsl:template>
<xsl:template match="soapenv:Body">
<xsl:apply-templates select="*" mode="soapBody"/>
</xsl:template>
<xsl:template match="*" mode="soapBody" priority="0.5">
<!-- creat soap error for web service client if the below templates were not matched -->
<service_response>
<content>
<soapenv:Envelope>
<soapenv:Body>
<soapenv:Fault>
<faultcode>SOAP-ENV:Client</faultcode>
<faultstring>unknown command: <xsl:value-of select="name(.)"/></faultstring>
</soapenv:Fault>
</soapenv:Body>
</soapenv:Envelope>
</content>
</service_response>
</xsl:template>
<xsl:template match="sos:addOrder" mode="soapBody" priority="2">
<add_order>
<xsl:attribute name="web_service">
<xsl:value-of select="/service_request/web_service/@name"/>
</xsl:attribute>
<xsl:if test="id">
<xsl:attribute name="id">
<xsl:value-of select="id"/>
</xsl:attribute>
</xsl:if>
<xsl:attribute name="job_chain">
<xsl:choose>
<xsl:when test="string-length(jobchain)">
<xsl:value-of select="jobchain"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$jobchain"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:if test="priority">
<xsl:attribute name="priority">
<xsl:value-of select="priority"/>
</xsl:attribute>
</xsl:if>
<xsl:if test="replace">
<xsl:attribute name="replace">
<xsl:if test="replace='true'">yes</xsl:if>
<xsl:if test="not(replace='true')">no</xsl:if>
</xsl:attribute>
</xsl:if>
<xsl:if test="title">
<xsl:attribute name="title">
<xsl:value-of select="title"/>
</xsl:attribute>
</xsl:if>
<xsl:if test="param">
<params>
<xsl:apply-templates select="param"/>
</params>
</xsl:if>
<xsl:if test="payload">
<payload>
<xsl:copy-of select="payload/*"/>
</payload>
</xsl:if>
<xsl:if test="xml_payload">
<xml_payload>
<xsl:copy-of select="xml_payload/*"/>
</xml_payload>
</xsl:if>
</add_order>
</xsl:template>
<xsl:template match="sos:startJob" mode="soapBody" priority="2">
<start_job>
<xsl:attribute name="web_service">
<xsl:value-of select="/service_request/web_service/@name"/>
</xsl:attribute>
<xsl:if test="name">
<xsl:attribute name="name">
<xsl:value-of select="name"/>
</xsl:attribute>
</xsl:if>
<xsl:attribute name="job">
<xsl:value-of select="job"/>
</xsl:attribute>
<xsl:if test="param">
<params>
<xsl:apply-templates select="param"/>
</params>
</xsl:if>
<xsl:if test="after">
<xsl:attribute name="after">
<xsl:value-of select="after"/>
</xsl:attribute>
</xsl:if>
<xsl:if test="at">
<xsl:attribute name="at">
<xsl:value-of select="at"/>
</xsl:attribute>
</xsl:if>
</start_job>
</xsl:template>
<xsl:template match="sos:modifyJob" mode="soapBody" priority="2">
<modify_job>
<xsl:attribute name="job">
<xsl:value-of select="job"/>
</xsl:attribute>
<xsl:attribute name="cmd">
<xsl:value-of select="cmd"/>
</xsl:attribute>
</modify_job>
</xsl:template>
<xsl:template match="sos:showOrder" mode="soapBody" priority="2">
<show_order>
<xsl:attribute name="job_chain">
<xsl:value-of select="jobChain"/>
</xsl:attribute>
<xsl:attribute name="order">
<xsl:value-of select="order"/>
</xsl:attribute>
</show_order>
</xsl:template>
<xsl:template match="param">
<param>
<xsl:attribute name="name">
<xsl:value-of select="name"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="value"/>
</xsl:attribute>
</param>
</xsl:template>
</xsl:stylesheet>
<?php
// Add this to your Shell and you can call call $this->loadComponent('mycomponent');
// now you can share components between controllers and shells (yippie)
/**
* Lazy loading of a component
*
* @var $component string Component to load
* @return boolean True on sucess, otherwise false
*/
public function loadComponent($component) {
if (!property_exists($this,'Controller')) {
App::import('Core', 'Controller');
$this->Controller =& new Controller();
$this->Controller->ext = '.thtml'; // Use old style template names
}
if ( property_exists($this, $component)) {
return true;
}
App::import('Component',$component);
$cn = $component.'Component';
$this->$component = new $cn;
if(method_exists($this->$component,'startup')){
$this->$component->startup($this->Controller);
}
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment