-
Notifications
You must be signed in to change notification settings - Fork 139
Expand file tree
/
Copy pathExecuteTask.php
More file actions
150 lines (128 loc) · 4.54 KB
/
Copy pathExecuteTask.php
File metadata and controls
150 lines (128 loc) · 4.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?php
declare(strict_types=1);
/**
* @author MGriesbach@gmail.com
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace Queue\Queue\Task;
use Cake\Console\CommandInterface;
use Cake\Console\ConsoleIo;
use Cake\Core\Configure;
use Cake\Log\LogTrait;
use Queue\Model\QueueException;
use Queue\Queue\AddInterface;
use Queue\Queue\Task;
/**
* Execute a Local command on the server.
*
* @property \Queue\Model\Table\QueueProcessesTable $QueueProcesses
*/
class ExecuteTask extends Task implements AddInterface {
use LogTrait;
/**
* Add functionality.
* Will create one example job in the queue, which later will be executed using run();
*
* @param string|null $data
*
* @return void
*/
public function add(?string $data): void {
$this->io->out('CakePHP Queue Execute task.');
$this->io->hr();
if (!$data) {
$this->io->out('This will run an shell command on the Server.');
$this->io->out('The task is mainly intended to serve as a kind of buffer for program calls from a CakePHP application.');
$this->io->out(' ');
$this->io->out('Call like this:');
$this->io->out(' bin/cake queue add Execute "*command* *param1* *param2*" ...');
$this->io->out(' ');
$this->io->out('For commands with spaces use " around it. E.g. `bin/cake queue add Execute "sleep 10s"`.');
$this->io->out(' ');
return;
}
// Tokenize like a shell so a quoted command path with embedded
// spaces survives intact:
// bin/cake queue add Execute '"/usr/local/bin/My Tool" arg1 arg2'
// parses to command="/usr/local/bin/My Tool", params=["arg1","arg2"].
// `str_getcsv` with space-as-delimiter respects double-quoted
// strings and strips the surrounding quotes for us. Falls back to
// the simple `explode(' ', $data, 2)` shape for plain inputs.
$tokens = str_getcsv($data, ' ', '"', '\\');
$tokens = array_values(array_filter($tokens, static fn ($t) => $t !== '' && $t !== null));
$command = (string)array_shift($tokens);
$params = $tokens;
$data = [
'command' => $command,
'params' => $params,
];
$this->QueuedJobs->createJob('Queue.Execute', $data);
$this->io->success('OK, job created, now run the worker');
}
/**
* Run function.
* This function is executed, when a worker is executing a task.
* The return parameter will determine, if the task will be marked completed, or be requeued.
*
* @param array<string, mixed> $data The array passed to QueuedJobsTable::createJob()
* @param int $jobId The id of the QueuedJob entity
*
* @throws \Queue\Model\QueueException
*
* @return void
*/
public function run(array $data, int $jobId): void {
$data += [
'command' => null,
'params' => [],
'redirect' => true,
'escape' => true,
'log' => false,
'accepted' => [CommandInterface::CODE_SUCCESS],
];
if (!$data['escape'] && !Configure::read('debug')) {
throw new QueueException('Command escaping must be enabled when debug mode is off for security reasons');
}
$rawCommand = (string)$data['command'];
if (!Configure::read('debug')) {
$allowed = (array)Configure::read('Queue.executeAllowedCommands', []);
if (!$allowed || !in_array($rawCommand, $allowed, true)) {
throw new QueueException(
'Command `' . $rawCommand . '` is not in Queue.executeAllowedCommands allow-list',
);
}
}
$command = $data['escape'] ? escapeshellarg($rawCommand) : $rawCommand;
if ($data['params']) {
$params = $data['params'];
if ($data['escape']) {
foreach ($params as $key => $value) {
$params[$key] = escapeshellarg((string)$value);
}
}
$command .= ' ' . implode(' ', $params);
}
$this->io->out('Executing: `' . $command . '`');
if ($data['redirect']) {
$command .= ' 2>&1';
}
exec($command, $output, $exitCode);
$this->io->nl();
$this->io->out($output);
if ($data['log']) {
$queueProcesses = $this->getTableLocator()->get('Queue.QueueProcesses');
$server = $queueProcesses->buildServerString();
$this->log($server . ': `' . $command . '` exits with `' . $exitCode . '` and returns `' . print_r($output, true) . '`' . PHP_EOL . 'Data : ' . print_r($data, true), 'info');
}
$acceptedReturnCodes = $data['accepted'];
$success = !$acceptedReturnCodes || in_array($exitCode, $acceptedReturnCodes, true);
if (!$success) {
$this->io->error('Error (code ' . $exitCode . ')', ConsoleIo::VERBOSE);
} else {
$this->io->success('Success (code ' . $exitCode . ')', ConsoleIo::VERBOSE);
}
if (!$success) {
throw new QueueException('Failed with error code ' . $exitCode . ': `' . $command . '`');
}
}
}