Skip to content

Commit 021710a

Browse files
committed
Add bulk power management via CLI
1 parent c6137db commit 021710a

File tree

5 files changed

+182
-0
lines changed

5 files changed

+182
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This project follows [Semantic Versioning](http://semver.org) guidelines.
1919
* Added proper transformer for Packs and re-enabled missing includes on server.
2020
* Added support for using Filesystem as a caching driver, although not recommended.
2121
* Added support for user management of server databases.
22+
* **Added bulk power management CLI interface to send start, stop, kill, restart actions to servers across configurable nodes.**
2223

2324
## v0.7.3 (Derelict Dermodactylus)
2425
### Fixed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
3+
namespace Pterodactyl\Console\Commands\Server;
4+
5+
use Illuminate\Console\Command;
6+
use GuzzleHttp\Exception\RequestException;
7+
use Illuminate\Validation\Factory as ValidatorFactory;
8+
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
9+
use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface;
10+
11+
class BulkPowerActionCommand extends Command
12+
{
13+
/**
14+
* @var \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface
15+
*/
16+
private $powerRepository;
17+
18+
/**
19+
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
20+
*/
21+
private $repository;
22+
23+
/**
24+
* @var \Illuminate\Validation\Factory
25+
*/
26+
private $validator;
27+
28+
/**
29+
* @var string
30+
*/
31+
protected $signature = 'p:server:bulk-power
32+
{action : The action to perform (start, stop, restart, kill)}
33+
{--servers= : A comma seperated list of servers.}
34+
{--nodes= : A comma seperated list of nodes.}';
35+
36+
/**
37+
* @var string
38+
*/
39+
protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.';
40+
41+
/**
42+
* BulkPowerActionCommand constructor.
43+
*
44+
* @param \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface $powerRepository
45+
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
46+
* @param \Illuminate\Validation\Factory $validator
47+
*/
48+
public function __construct(
49+
PowerRepositoryInterface $powerRepository,
50+
ServerRepositoryInterface $repository,
51+
ValidatorFactory $validator
52+
) {
53+
parent::__construct();
54+
55+
$this->powerRepository = $powerRepository;
56+
$this->repository = $repository;
57+
$this->validator = $validator;
58+
}
59+
60+
/**
61+
* Handle the bulk power request.
62+
*
63+
* @throws \Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException
64+
*/
65+
public function handle()
66+
{
67+
$action = $this->argument('action');
68+
$nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes'));
69+
$servers = empty($this->option('servers')) ? [] : explode(',', $this->option('servers'));
70+
71+
$validator = $this->validator->make([
72+
'action' => $action,
73+
'nodes' => $nodes,
74+
'servers' => $servers,
75+
], [
76+
'action' => 'string|in:start,stop,kill,restart',
77+
'nodes' => 'array',
78+
'nodes.*' => 'integer|min:1',
79+
'servers' => 'array',
80+
'servers.*' => 'integer|min:1',
81+
]);
82+
83+
if ($validator->fails()) {
84+
foreach ($validator->getMessageBag()->all() as $message) {
85+
$this->output->error($message);
86+
}
87+
88+
return;
89+
}
90+
91+
$count = $this->repository->getServersForPowerActionCount($servers, $nodes);
92+
if (! $this->confirm(trans('command/messages.server.power.confirm', ['action' => $action, 'count' => $count]))) {
93+
return;
94+
}
95+
96+
$bar = $this->output->createProgressBar($count);
97+
$servers = $this->repository->getServersForPowerAction($servers, $nodes);
98+
99+
foreach ($servers as $server) {
100+
$bar->clear();
101+
102+
try {
103+
$this->powerRepository->setServer($server)->sendSignal($action);
104+
} catch (RequestException $exception) {
105+
$this->output->error(trans('command/messages.server.power.action_failed', [
106+
'name' => $server->name,
107+
'id' => $server->id,
108+
'node' => $server->node->name,
109+
'message' => $exception->getMessage(),
110+
]));
111+
}
112+
113+
$bar->advance();
114+
$bar->display();
115+
}
116+
117+
$this->line('');
118+
}
119+
}

app/Contracts/Repository/ServerRepositoryInterface.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,23 @@ public function filterUserAccessServers(User $user, int $level, bool $paginate =
117117
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
118118
*/
119119
public function getByUuid(string $uuid): Server;
120+
121+
/**
122+
* Return all of the servers that should have a power action performed aganist them.
123+
*
124+
* @param int[] $servers
125+
* @param int[] $nodes
126+
* @param bool $returnCount
127+
* @return int|\Generator
128+
*/
129+
public function getServersForPowerAction(array $servers = [], array $nodes = [], bool $returnCount = false);
130+
131+
/**
132+
* Return the total number of servers that will be affected by the query.
133+
*
134+
* @param int[] $servers
135+
* @param int[] $nodes
136+
* @return int
137+
*/
138+
public function getServersForPowerActionCount(array $servers = [], array $nodes = []): int;
120139
}

app/Repositories/Eloquent/ServerRepository.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,45 @@ public function getByUuid(string $uuid): Server
264264
}
265265
}
266266

267+
/**
268+
* Return all of the servers that should have a power action performed aganist them.
269+
*
270+
* @param int[] $servers
271+
* @param int[] $nodes
272+
* @param bool $returnCount
273+
* @return int|\Generator
274+
*/
275+
public function getServersForPowerAction(array $servers = [], array $nodes = [], bool $returnCount = false)
276+
{
277+
$instance = $this->getBuilder();
278+
279+
if (! empty($nodes) && ! empty($servers)) {
280+
$instance->whereIn('id', $servers)->orWhereIn('node_id', $nodes);
281+
} elseif (empty($nodes) && ! empty($servers)) {
282+
$instance->whereIn('id', $servers);
283+
} elseif (! empty($nodes) && empty($servers)) {
284+
$instance->whereIn('node_id', $nodes);
285+
}
286+
287+
if ($returnCount) {
288+
return $instance->count();
289+
}
290+
291+
return $instance->with('node')->cursor();
292+
}
293+
294+
/**
295+
* Return the total number of servers that will be affected by the query.
296+
*
297+
* @param int[] $servers
298+
* @param int[] $nodes
299+
* @return int
300+
*/
301+
public function getServersForPowerActionCount(array $servers = [], array $nodes = []): int
302+
{
303+
return $this->getServersForPowerAction($servers, $nodes, true);
304+
}
305+
267306
/**
268307
* Return an array of server IDs that a given user can access based
269308
* on owner and subuser permissions.

resources/lang/en/command/messages.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@
3737
],
3838
'server' => [
3939
'rebuild_failed' => 'Rebuild request for ":name" (#:id) on node ":node" failed with error: :message',
40+
'power' => [
41+
'confirm' => 'You are about to perform a :action aganist :count servers. Do you wish to continue?',
42+
'action_failed' => 'Power action request for ":name" (#:id) on node ":node" failed with error: :message',
43+
],
4044
],
4145
'environment' => [
4246
'mail' => [

0 commit comments

Comments
 (0)