Skip to content

Commit c599112

Browse files
committed
Finalize server management API
1 parent 1be7805 commit c599112

File tree

6 files changed

+163
-17
lines changed

6 files changed

+163
-17
lines changed

app/Exceptions/Http/Connection/DaemonConnectionException.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Pterodactyl\Exceptions\Http\Connection;
44

5+
use Illuminate\Http\Response;
56
use GuzzleHttp\Exception\GuzzleException;
67
use Pterodactyl\Exceptions\DisplayException;
78

@@ -10,7 +11,7 @@ class DaemonConnectionException extends DisplayException
1011
/**
1112
* @var int
1213
*/
13-
private $statusCode = 500;
14+
private $statusCode = Response::HTTP_GATEWAY_TIMEOUT;
1415

1516
/**
1617
* Throw a displayable exception caused by a daemon connection error.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Controllers\Api\Application\Servers;
4+
5+
use Pterodactyl\Models\Server;
6+
use Pterodactyl\Services\Servers\StartupModificationService;
7+
use Pterodactyl\Transformers\Api\Application\ServerTransformer;
8+
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
9+
use Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerStartupRequest;
10+
11+
class StartupController extends ApplicationApiController
12+
{
13+
/**
14+
* @var \Pterodactyl\Services\Servers\StartupModificationService
15+
*/
16+
private $modificationService;
17+
18+
/**
19+
* StartupController constructor.
20+
*
21+
* @param \Pterodactyl\Services\Servers\StartupModificationService $modificationService
22+
*/
23+
public function __construct(StartupModificationService $modificationService)
24+
{
25+
parent::__construct();
26+
27+
$this->modificationService = $modificationService;
28+
}
29+
30+
/**
31+
* Update the startup and environment settings for a specific server.
32+
*
33+
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerStartupRequest $request
34+
* @return array
35+
*
36+
* @throws \Illuminate\Validation\ValidationException
37+
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
38+
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
39+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
40+
*/
41+
public function index(UpdateServerStartupRequest $request): array
42+
{
43+
$server = $this->modificationService->handle($request->getModel(Server::class), $request->validated());
44+
45+
return $this->fractal->item($server)
46+
->transformWith($this->getTransformer(ServerTransformer::class))
47+
->toArray();
48+
}
49+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
4+
5+
use Pterodactyl\Models\Server;
6+
use Pterodactyl\Services\Acl\Api\AdminAcl;
7+
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
8+
9+
class UpdateServerStartupRequest extends ApplicationApiRequest
10+
{
11+
/**
12+
* @var string
13+
*/
14+
protected $resource = AdminAcl::RESOURCE_SERVERS;
15+
16+
/**
17+
* @var int
18+
*/
19+
protected $permission = AdminAcl::WRITE;
20+
21+
/**
22+
* Validation rules to run the input aganist.
23+
*
24+
* @return array
25+
*/
26+
public function rules(): array
27+
{
28+
$data = Server::getUpdateRulesForId($this->getModel(Server::class)->id);
29+
30+
return [
31+
'startup' => $data['startup'],
32+
'environment' => 'present|array',
33+
'egg' => $data['egg_id'],
34+
'pack' => $data['pack_id'],
35+
'image' => $data['image'],
36+
'skip_scripts' => 'present|boolean',
37+
];
38+
}
39+
40+
/**
41+
* Return the validated data in a format that is expected by the service.
42+
*
43+
* @return array
44+
*/
45+
public function validated()
46+
{
47+
$data = parent::validated();
48+
49+
return collect($data)->only(['startup', 'environment', 'skip_scripts'])->merge([
50+
'egg_id' => array_get($data, 'egg'),
51+
'pack_id' => array_get($data, 'pack'),
52+
'docker_image' => array_get($data, 'image'),
53+
])->toArray();
54+
}
55+
}

app/Services/Servers/StartupModificationService.php

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use GuzzleHttp\Exception\RequestException;
88
use Illuminate\Database\ConnectionInterface;
99
use Pterodactyl\Traits\Services\HasUserLevels;
10+
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
1011
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
1112
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
1213
use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface;
@@ -26,6 +27,11 @@ class StartupModificationService
2627
*/
2728
private $connection;
2829

30+
/**
31+
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface
32+
*/
33+
private $eggRepository;
34+
2935
/**
3036
* @var \Pterodactyl\Services\Servers\EnvironmentService
3137
*/
@@ -51,6 +57,7 @@ class StartupModificationService
5157
*
5258
* @param \Illuminate\Database\ConnectionInterface $connection
5359
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository
60+
* @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $eggRepository
5461
* @param \Pterodactyl\Services\Servers\EnvironmentService $environmentService
5562
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
5663
* @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository
@@ -59,13 +66,15 @@ class StartupModificationService
5966
public function __construct(
6067
ConnectionInterface $connection,
6168
DaemonServerRepositoryInterface $daemonServerRepository,
69+
EggRepositoryInterface $eggRepository,
6270
EnvironmentService $environmentService,
6371
ServerRepositoryInterface $repository,
6472
ServerVariableRepositoryInterface $serverVariableRepository,
6573
VariableValidatorService $validatorService
6674
) {
6775
$this->daemonServerRepository = $daemonServerRepository;
6876
$this->connection = $connection;
77+
$this->eggRepository = $eggRepository;
6978
$this->environmentService = $environmentService;
7079
$this->repository = $repository;
7180
$this->serverVariableRepository = $serverVariableRepository;
@@ -77,13 +86,14 @@ public function __construct(
7786
*
7887
* @param \Pterodactyl\Models\Server $server
7988
* @param array $data
89+
* @return \Pterodactyl\Models\Server
8090
*
8191
* @throws \Illuminate\Validation\ValidationException
92+
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
8293
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
8394
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
84-
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
8595
*/
86-
public function handle(Server $server, array $data)
96+
public function handle(Server $server, array $data): Server
8797
{
8898
$this->connection->beginTransaction();
8999
if (! is_null(array_get($data, 'environment'))) {
@@ -119,6 +129,8 @@ public function handle(Server $server, array $data)
119129
}
120130

121131
$this->connection->commit();
132+
133+
return $server;
122134
}
123135

124136
/**
@@ -133,21 +145,30 @@ public function handle(Server $server, array $data)
133145
*/
134146
private function updateAdministrativeSettings(array $data, Server &$server, array &$daemonData)
135147
{
148+
if (
149+
is_digit(array_get($data, 'egg_id'))
150+
&& $data['egg_id'] != $server->egg_id
151+
&& is_null(array_get($data, 'nest_id'))
152+
) {
153+
$egg = $this->eggRepository->setColumns(['id', 'nest_id'])->find($data['egg_id']);
154+
$data['nest_id'] = $egg->nest_id;
155+
}
156+
136157
$server = $this->repository->update($server->id, [
137158
'installed' => 0,
138159
'startup' => array_get($data, 'startup', $server->startup),
139160
'nest_id' => array_get($data, 'nest_id', $server->nest_id),
140161
'egg_id' => array_get($data, 'egg_id', $server->egg_id),
141162
'pack_id' => array_get($data, 'pack_id', $server->pack_id) > 0 ? array_get($data, 'pack_id', $server->pack_id) : null,
142-
'skip_scripts' => isset($data['skip_scripts']),
163+
'skip_scripts' => array_get($data, 'skip_scripts') ?? isset($data['skip_scripts']),
143164
'image' => array_get($data, 'docker_image', $server->image),
144165
]);
145166

146167
$daemonData = array_merge($daemonData, [
147168
'build' => ['image' => $server->image],
148169
'service' => array_merge(
149170
$this->repository->getDaemonServiceData($server, true),
150-
['skip_scripts' => isset($data['skip_scripts'])]
171+
['skip_scripts' => $server->skip_scripts]
151172
),
152173
]);
153174
}

routes/api-application.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676

7777
Route::patch('/{server}/details', 'Servers\ServerDetailsController@details')->name('api.application.servers.details');
7878
Route::patch('/{server}/build', 'Servers\ServerDetailsController@build')->name('api.application.servers.build');
79+
Route::patch('/{server}/startup', 'Servers\StartupController@index')->name('api.application.servers.startup');
7980

8081
Route::post('/', 'Servers\ServerController@store');
8182
Route::post('/{server}/suspend', 'Servers\ServerManagementController@suspend')->name('api.application.servers.suspend');

tests/Unit/Services/Servers/StartupModificationServiceTest.php

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
<?php
2-
/*
3-
* Pterodactyl - Panel
4-
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
5-
*
6-
* This software is licensed under the terms of the MIT license.
7-
* https://opensource.org/licenses/MIT
8-
*/
92

103
namespace Tests\Unit\Services\Servers;
114

125
use Mockery as m;
136
use Tests\TestCase;
7+
use Pterodactyl\Models\Egg;
148
use Pterodactyl\Models\User;
159
use GuzzleHttp\Psr7\Response;
1610
use Pterodactyl\Models\Server;
1711
use Illuminate\Database\ConnectionInterface;
1812
use Pterodactyl\Services\Servers\EnvironmentService;
1913
use Pterodactyl\Services\Servers\VariableValidatorService;
14+
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
2015
use Pterodactyl\Services\Servers\StartupModificationService;
2116
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
2217
use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface;
@@ -34,6 +29,11 @@ class StartupModificationServiceTest extends TestCase
3429
*/
3530
private $connection;
3631

32+
/**
33+
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface|\Mockery\Mock
34+
*/
35+
private $eggRepository;
36+
3737
/**
3838
* @var \Pterodactyl\Services\Servers\EnvironmentService|\Mockery\Mock
3939
*/
@@ -63,6 +63,7 @@ public function setUp()
6363

6464
$this->daemonServerRepository = m::mock(DaemonServerRepository::class);
6565
$this->connection = m::mock(ConnectionInterface::class);
66+
$this->eggRepository = m::mock(EggRepositoryInterface::class);
6667
$this->environmentService = m::mock(EnvironmentService::class);
6768
$this->repository = m::mock(ServerRepositoryInterface::class);
6869
$this->serverVariableRepository = m::mock(ServerVariableRepositoryInterface::class);
@@ -96,8 +97,10 @@ public function testStartupModifiedAsNormalUser()
9697

9798
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
9899

99-
$this->getService()->handle($model, ['egg_id' => 123, 'environment' => ['test' => 'abcd1234']]);
100-
$this->assertTrue(true);
100+
$response = $this->getService()->handle($model, ['egg_id' => 123, 'environment' => ['test' => 'abcd1234']]);
101+
102+
$this->assertInstanceOf(Server::class, $response);
103+
$this->assertSame($model, $response);
101104
}
102105

103106
/**
@@ -110,6 +113,11 @@ public function testStartupModificationAsAdminUser()
110113
'image' => 'docker:image',
111114
]);
112115

116+
$eggModel = factory(Egg::class)->make([
117+
'id' => 456,
118+
'nest_id' => 12345,
119+
]);
120+
113121
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
114122
$this->validatorService->shouldReceive('setUserLevel')->with(User::USER_LEVEL_ADMIN)->once()->andReturnNull();
115123
$this->validatorService->shouldReceive('handle')->with(456, ['test' => 'abcd1234'])->once()->andReturn(
@@ -122,9 +130,12 @@ public function testStartupModificationAsAdminUser()
122130
'variable_id' => 1,
123131
], ['variable_value' => 'stored-value'])->once()->andReturnNull();
124132

133+
$this->eggRepository->shouldReceive('setColumns->find')->once()->with($eggModel->id)->andReturn($eggModel);
134+
125135
$this->repository->shouldReceive('update')->with($model->id, m::subset([
126136
'installed' => 0,
127-
'egg_id' => 456,
137+
'nest_id' => $eggModel->nest_id,
138+
'egg_id' => $eggModel->id,
128139
'pack_id' => 789,
129140
'image' => 'docker:image',
130141
]))->once()->andReturn($model);
@@ -152,8 +163,15 @@ public function testStartupModificationAsAdminUser()
152163

153164
$service = $this->getService();
154165
$service->setUserLevel(User::USER_LEVEL_ADMIN);
155-
$service->handle($model, ['docker_image' => 'docker:image', 'egg_id' => 456, 'pack_id' => 789, 'environment' => ['test' => 'abcd1234']]);
156-
$this->assertTrue(true);
166+
$response = $service->handle($model, [
167+
'docker_image' => 'docker:image',
168+
'egg_id' => $eggModel->id,
169+
'pack_id' => 789,
170+
'environment' => ['test' => 'abcd1234'],
171+
]);
172+
173+
$this->assertInstanceOf(Server::class, $response);
174+
$this->assertSame($model, $response);
157175
}
158176

159177
/**
@@ -166,6 +184,7 @@ private function getService(): StartupModificationService
166184
return new StartupModificationService(
167185
$this->connection,
168186
$this->daemonServerRepository,
187+
$this->eggRepository,
169188
$this->environmentService,
170189
$this->repository,
171190
$this->serverVariableRepository,

0 commit comments

Comments
 (0)