Skip to content

Commit 5ed164e

Browse files
committed
Implement server creation though the API.
Also implements auto-deployment to specific locations and ports.
1 parent 97ee95b commit 5ed164e

File tree

24 files changed

+927
-223
lines changed

24 files changed

+927
-223
lines changed

app/Contracts/Repository/AllocationRepositoryInterface.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,26 @@ public function getUnassignedAllocationIds(int $node): array;
5757
* @return array
5858
*/
5959
public function getAssignedAllocationIds(int $server): array;
60+
61+
/**
62+
* Return a concated result set of node ips that already have at least one
63+
* server assigned to that IP. This allows for filtering out sets for
64+
* dedicated allocation IPs.
65+
*
66+
* If an array of nodes is passed the results will be limited to allocations
67+
* in those nodes.
68+
*
69+
* @param array $nodes
70+
* @return array
71+
*/
72+
public function getDiscardableDedicatedAllocations(array $nodes = []): array;
73+
74+
/**
75+
* Return a single allocation from those meeting the requirements.
76+
*
77+
* @param array $nodes
78+
* @param array $ports
79+
* @param bool $dedicated
80+
* @return \Pterodactyl\Models\Allocation|null
81+
public function getRandomAllocation(array $nodes, array $ports, bool $dedicated = false);
6082
}

app/Contracts/Repository/NodeRepositoryInterface.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Pterodactyl\Contracts\Repository;
44

5+
use Generator;
56
use Pterodactyl\Models\Node;
67
use Illuminate\Support\Collection;
78
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
@@ -62,4 +63,15 @@ public function getNodeServers(int $id): Node;
6263
* @return \Illuminate\Support\Collection
6364
*/
6465
public function getNodesForServerCreation(): Collection;
66+
67+
/**
68+
* Return the IDs of all nodes that exist in the provided locations and have the space
69+
* available to support the additional disk and memory provided.
70+
*
71+
* @param array $locations
72+
* @param int $disk
73+
* @param int $memory
74+
* @return \Generator
75+
*/
76+
public function getNodesWithResourceUse(array $locations, int $disk, int $memory): Generator;
6577
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Pterodactyl\Exceptions\Service\Deployment;
4+
5+
use Pterodactyl\Exceptions\DisplayException;
6+
7+
class NoViableAllocationException extends DisplayException
8+
{
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Pterodactyl\Exceptions\Service\Deployment;
4+
5+
use Pterodactyl\Exceptions\DisplayException;
6+
7+
class NoViableNodeException extends DisplayException
8+
{
9+
}

app/Http/Controllers/Admin/ServersController.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,14 +251,17 @@ public function create()
251251
* @param \Pterodactyl\Http\Requests\Admin\ServerFormRequest $request
252252
* @return \Illuminate\Http\RedirectResponse
253253
*
254+
* @throws \Illuminate\Validation\ValidationException
254255
* @throws \Pterodactyl\Exceptions\DisplayException
256+
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
255257
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
256258
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
257-
* @throws \Illuminate\Validation\ValidationException
259+
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException
260+
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException
258261
*/
259262
public function store(ServerFormRequest $request)
260263
{
261-
$server = $this->service->create($request->except('_token'));
264+
$server = $this->service->handle($request->except('_token'));
262265
$this->alert->success(trans('admin/server.alerts.server_created'))->flash();
263266

264267
return redirect()->route('admin.servers.view', $server->id);

app/Http/Controllers/Api/Application/Servers/ServerController.php

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,23 @@
44

55
use Illuminate\Http\Response;
66
use Pterodactyl\Models\Server;
7+
use Illuminate\Http\JsonResponse;
8+
use Pterodactyl\Services\Servers\ServerCreationService;
79
use Pterodactyl\Services\Servers\ServerDeletionService;
810
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
911
use Pterodactyl\Transformers\Api\Application\ServerTransformer;
1012
use Pterodactyl\Http\Requests\Api\Application\Servers\GetServersRequest;
1113
use Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest;
14+
use Pterodactyl\Http\Requests\Api\Application\Servers\StoreServerRequest;
1215
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
1316

1417
class ServerController extends ApplicationApiController
1518
{
19+
/**
20+
* @var \Pterodactyl\Services\Servers\ServerCreationService
21+
*/
22+
private $creationService;
23+
1624
/**
1725
* @var \Pterodactyl\Services\Servers\ServerDeletionService
1826
*/
@@ -26,13 +34,18 @@ class ServerController extends ApplicationApiController
2634
/**
2735
* ServerController constructor.
2836
*
37+
* @param \Pterodactyl\Services\Servers\ServerCreationService $creationService
2938
* @param \Pterodactyl\Services\Servers\ServerDeletionService $deletionService
3039
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
3140
*/
32-
public function __construct(ServerDeletionService $deletionService, ServerRepositoryInterface $repository)
33-
{
41+
public function __construct(
42+
ServerCreationService $creationService,
43+
ServerDeletionService $deletionService,
44+
ServerRepositoryInterface $repository
45+
) {
3446
parent::__construct();
3547

48+
$this->creationService = $creationService;
3649
$this->deletionService = $deletionService;
3750
$this->repository = $repository;
3851
}
@@ -52,6 +65,29 @@ public function index(GetServersRequest $request): array
5265
->toArray();
5366
}
5467

68+
/**
69+
* Create a new server on the system.
70+
*
71+
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\StoreServerRequest $request
72+
* @return \Illuminate\Http\JsonResponse
73+
*
74+
* @throws \Illuminate\Validation\ValidationException
75+
* @throws \Pterodactyl\Exceptions\DisplayException
76+
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
77+
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
78+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
79+
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException
80+
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException
81+
*/
82+
public function store(StoreServerRequest $request): JsonResponse
83+
{
84+
$server = $this->creationService->handle($request->validated(), $request->getDeploymentObject());
85+
86+
return $this->fractal->item($server)
87+
->transformWith($this->getTransformer(ServerTransformer::class))
88+
->respond(201);
89+
}
90+
5591
/**
5692
* Show a single server transformed for the application API.
5793
*
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
4+
5+
use Pterodactyl\Models\Server;
6+
use Illuminate\Validation\Rule;
7+
use Pterodactyl\Services\Acl\Api\AdminAcl;
8+
use Illuminate\Contracts\Validation\Validator;
9+
use Pterodactyl\Models\Objects\DeploymentObject;
10+
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
11+
12+
class StoreServerRequest extends ApplicationApiRequest
13+
{
14+
/**
15+
* @var string
16+
*/
17+
protected $resource = AdminAcl::RESOURCE_SERVERS;
18+
19+
/**
20+
* @var int
21+
*/
22+
protected $permission = AdminAcl::WRITE;
23+
24+
/**
25+
* Rules to be applied to this request.
26+
*
27+
* @return array
28+
*/
29+
public function rules(): array
30+
{
31+
$rules = Server::getCreateRules();
32+
33+
return [
34+
'name' => $rules['name'],
35+
'description' => array_merge(['nullable'], $rules['description']),
36+
'user' => $rules['owner_id'],
37+
'egg' => $rules['egg_id'],
38+
'pack' => $rules['pack_id'],
39+
'docker_image' => $rules['image'],
40+
'startup' => $rules['startup'],
41+
'environment' => 'required|array',
42+
'skip_scripts' => 'sometimes|boolean',
43+
44+
// Resource limitations
45+
'limits' => 'required|array',
46+
'limits.memory' => $rules['memory'],
47+
'limits.swap' => $rules['swap'],
48+
'limits.disk' => $rules['disk'],
49+
'limits.io' => $rules['io'],
50+
'limits.cpu' => $rules['cpu'],
51+
52+
// Automatic deployment rules
53+
'deploy' => 'sometimes|required|array',
54+
'deploy.locations' => 'array',
55+
'deploy.locations.*' => 'integer|min:1',
56+
'deploy.dedicated_ip' => 'required_with:deploy,boolean',
57+
'deploy.port_range' => 'array',
58+
'deploy.port_range.*' => 'string',
59+
60+
'start_on_completion' => 'sometimes|boolean',
61+
];
62+
}
63+
64+
/**
65+
* Normalize the data into a format that can be consumed by the service.
66+
*
67+
* @return array
68+
*/
69+
public function validated()
70+
{
71+
$data = parent::validated();
72+
73+
return [
74+
'name' => array_get($data, 'name'),
75+
'description' => array_get($data, 'description'),
76+
'owner_id' => array_get($data, 'user'),
77+
'egg_id' => array_get($data, 'egg'),
78+
'pack_id' => array_get($data, 'pack'),
79+
'image' => array_get($data, 'docker_image'),
80+
'startup' => array_get($data, 'startup'),
81+
'environment' => array_get($data, 'environment'),
82+
'memory' => array_get($data, 'limits.memory'),
83+
'swap' => array_get($data, 'limits.swap'),
84+
'disk' => array_get($data, 'limits.disk'),
85+
'io' => array_get($data, 'limits.io'),
86+
'cpu' => array_get($data, 'limits.cpu'),
87+
'skip_scripts' => array_get($data, 'skip_scripts', false),
88+
'allocation_id' => array_get($data, 'allocation.default'),
89+
'allocation_additional' => array_get($data, 'allocation.additional'),
90+
'start_on_completion' => array_get($data, 'start_on_completion', false),
91+
];
92+
}
93+
94+
/*
95+
* Run validation after the rules above have been applied.
96+
*
97+
* @param \Illuminate\Contracts\Validation\Validator $validator
98+
*/
99+
public function withValidator(Validator $validator)
100+
{
101+
$validator->sometimes('allocation.default', [
102+
'required', 'integer', 'bail',
103+
Rule::exists('allocations', 'id')->where(function ($query) {
104+
$query->where('node_id', $this->input('node_id'));
105+
$query->whereNull('server_id');
106+
}),
107+
], function ($input) {
108+
return ! ($input->deploy);
109+
});
110+
111+
$validator->sometimes('allocation.additional.*', [
112+
'integer',
113+
Rule::exists('allocations', 'id')->where(function ($query) {
114+
$query->where('node_id', $this->input('node_id'));
115+
$query->whereNull('server_id');
116+
}),
117+
], function ($input) {
118+
return ! ($input->deploy);
119+
});
120+
121+
$validator->sometimes('deploy.locations', 'present', function ($input) {
122+
return $input->deploy;
123+
});
124+
125+
$validator->sometimes('deploy.port_range', 'present', function ($input) {
126+
return $input->deploy;
127+
});
128+
}
129+
130+
/**
131+
* Return a deployment object that can be passed to the server creation service.
132+
*
133+
* @return \Pterodactyl\Models\Objects\DeploymentObject|null
134+
*/
135+
public function getDeploymentObject()
136+
{
137+
if (is_null($this->input('deploy'))) {
138+
return null;
139+
}
140+
141+
$object = new DeploymentObject;
142+
$object->setDedicated($this->input('deploy.dedicated_ip', false));
143+
$object->setLocations($this->input('deploy.locations', []));
144+
$object->setPorts($this->input('deploy.port_range', []));
145+
146+
return $object;
147+
}
148+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace Pterodactyl\Models\Objects;
4+
5+
class DeploymentObject
6+
{
7+
/**
8+
* @var bool
9+
*/
10+
private $dedicated = false;
11+
12+
/**
13+
* @var array
14+
*/
15+
private $locations = [];
16+
17+
/**
18+
* @var array
19+
*/
20+
private $ports = [];
21+
22+
/**
23+
* @return bool
24+
*/
25+
public function isDedicated(): bool
26+
{
27+
return $this->dedicated;
28+
}
29+
30+
/**
31+
* @param bool $dedicated
32+
* @return $this
33+
*/
34+
public function setDedicated(bool $dedicated)
35+
{
36+
$this->dedicated = $dedicated;
37+
38+
return $this;
39+
}
40+
41+
/**
42+
* @return array
43+
*/
44+
public function getLocations(): array
45+
{
46+
return $this->locations;
47+
}
48+
49+
/**
50+
* @param array $locations
51+
* @return $this
52+
*/
53+
public function setLocations(array $locations)
54+
{
55+
$this->locations = $locations;
56+
57+
return $this;
58+
}
59+
60+
/**
61+
* @return array
62+
*/
63+
public function getPorts(): array
64+
{
65+
return $this->ports;
66+
}
67+
68+
/**
69+
* @param array $ports
70+
* @return $this
71+
*/
72+
public function setPorts(array $ports)
73+
{
74+
$this->ports = $ports;
75+
76+
return $this;
77+
}
78+
}

0 commit comments

Comments
 (0)