GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — develop (#401)
by Dane
03:08
created

ServerRepository::updateStartup()   D

Complexity

Conditions 14
Paths 27

Size

Total Lines 108
Code Lines 62

Duplication

Lines 8
Ratio 7.41 %

Importance

Changes 0
Metric Value
dl 8
loc 108
rs 4.9516
c 0
b 0
f 0
cc 14
eloc 62
nc 27
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Pterodactyl - Panel
4
 * Copyright (c) 2015 - 2017 Dane Everitt <[email protected]>.
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in all
14
 * copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 */
24
25
namespace Pterodactyl\Repositories;
26
27
use DB;
28
use Crypt;
29
use Validator;
30
use Pterodactyl\Models;
31
use Pterodactyl\Services\UuidService;
32
use GuzzleHttp\Exception\ClientException;
33
use GuzzleHttp\Exception\TransferException;
34
use Pterodactyl\Services\DeploymentService;
35
use Pterodactyl\Exceptions\DisplayException;
36
use Pterodactyl\Exceptions\DisplayValidationException;
37
38
class ServerRepository
39
{
40
    /**
41
     * An array of daemon permission to assign to this server.
42
     *
43
     * @var array
44
     */
45
    protected $daemonPermissions = [
46
        's:*',
47
    ];
48
49
    /**
50
     * Generates a SFTP username for a server given a server name.
51
     * format: mumble_67c7a4b0.
52
     *
53
     * @param  string       $name
54
     * @param  null|string  $identifier
55
     * @return string
56
     */
57
    protected function generateSFTPUsername($name, $identifier = null)
58
    {
59
        if (is_null($identifier) || ! ctype_alnum($identifier)) {
60
            $unique = str_random(8);
61
        } else {
62
            if (strlen($identifier) < 8) {
63
                $unique = $identifier . str_random((8 - strlen($identifier)));
64
            } else {
65
                $unique = substr($identifier, 0, 8);
66
            }
67
        }
68
69
        // Filter the Server Name
70
        $name = trim(preg_replace('/[^\w]+/', '', $name), '_');
71
        $name = (strlen($name) < 1) ? str_random(6) : $name;
72
73
        return strtolower(substr($name, 0, 6) . '_' . $unique);
74
    }
75
76
    /**
77
     * Adds a new server to the system.
78
     *
79
     * @param   array  $data
80
     * @return \Pterodactyl\Models\Server
81
     *
82
     * @throws \Pterodactyl\Exceptions\DisplayException
83
     * @throws \Pterodactyl\Exceptions\DisplayValidationException
84
     */
85
    public function create(array $data)
86
    {
87
88
        // Validate Fields
89
        $validator = Validator::make($data, [
90
            'user_id' => 'required|exists:users,id',
91
            'name' => 'required|regex:/^([\w .-]{1,200})$/',
92
            'description' => 'sometimes|nullable|string',
93
            'memory' => 'required|numeric|min:0',
94
            'swap' => 'required|numeric|min:-1',
95
            'io' => 'required|numeric|min:10|max:1000',
96
            'cpu' => 'required|numeric|min:0',
97
            'disk' => 'required|numeric|min:0',
98
            'service_id' => 'required|numeric|min:1|exists:services,id',
99
            'option_id' => 'required|numeric|min:1|exists:service_options,id',
100
            'location_id' => 'required|numeric|min:1|exists:locations,id',
101
            'pack_id' => 'sometimes|nullable|numeric|min:0',
102
            'custom_container' => 'string',
103
            'startup' => 'string',
104
            'auto_deploy' => 'sometimes|required|accepted',
105
            'custom_id' => 'sometimes|required|numeric|unique:servers,id',
106
            'skip_scripts' => 'sometimes|required|boolean',
107
        ]);
108
109
        $validator->sometimes('node_id', 'required|numeric|min:1|exists:nodes,id', function ($input) {
110
            return ! ($input->auto_deploy);
111
        });
112
113
        $validator->sometimes('allocation_id', 'required|numeric|exists:allocations,id', function ($input) {
114
            return ! ($input->auto_deploy);
115
        });
116
117
        $validator->sometimes('allocation_additional.*', 'sometimes|required|numeric|exists:allocations,id', function ($input) {
118
            return ! ($input->auto_deploy);
119
        });
120
121
        // Run validator, throw catchable and displayable exception if it fails.
122
        // Exception includes a JSON result of failed validation rules.
123
        if ($validator->fails()) {
124
            throw new DisplayValidationException(json_encode($validator->errors()));
125
        }
126
127
        $user = Models\User::findOrFail($data['user_id']);
128
129
        $autoDeployed = false;
130
        if (isset($data['auto_deploy']) && $data['auto_deploy']) {
131
            // This is an auto-deployment situation
132
            // Ignore any other passed node data
133
            unset($data['node_id'], $data['allocation_id']);
134
135
            $autoDeployed = true;
136
            $node = DeploymentService::smartRandomNode($data['memory'], $data['disk'], $data['location_id']);
137
            $allocation = DeploymentService::randomAllocation($node->id);
138
        } else {
139
            $node = Models\Node::findOrFail($data['node_id']);
140
        }
141
142
        // Verify IP & Port are a.) free and b.) assigned to the node.
143
        // We know the node exists because of 'exists:nodes,id' in the validation
144
        if (! $autoDeployed) {
145
            $allocation = Models\Allocation::where('id', $data['allocation_id'])->where('node_id', $data['node_id'])->whereNull('server_id')->first();
146
        }
147
148
        // Something failed in the query, either that combo doesn't exist, or it is in use.
149
        if (! $allocation) {
0 ignored issues
show
Bug introduced by
The variable $allocation does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
150
            throw new DisplayException('The selected Allocation ID is either already in use, or unavaliable for this node.');
151
        }
152
153
        // Validate those Service Option Variables
154
        // We know the service and option exists because of the validation.
155
        // We need to verify that the option exists for the service, and then check for
156
        // any required variable fields. (fields are labeled env_<env_variable>)
157
        $option = Models\ServiceOption::where('id', $data['option_id'])->where('service_id', $data['service_id'])->first();
158
        if (! $option) {
159
            throw new DisplayException('The requested service option does not exist for the specified service.');
160
        }
161
162
        // Validate the Pack
163 View Code Duplication
        if (! isset($data['pack_id']) || (int) $data['pack_id'] < 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
164
            $data['pack_id'] = null;
165
        } else {
166
            $pack = Models\Pack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first();
167
            if (! $pack) {
168
                throw new DisplayException('The requested service pack does not seem to exist for this combination.');
169
            }
170
        }
171
172
        // Load up the Service Information
173
        $service = Models\Service::find($option->service_id);
174
175
        // Check those Variables
176
        $variables = Models\ServiceVariable::where('option_id', $data['option_id'])->get();
177
        $variableList = [];
178
        if ($variables) {
179
            foreach ($variables as $variable) {
180
181
                // Is the variable required?
182
                if (! isset($data['env_' . $variable->env_variable])) {
183
                    if ($variable->required) {
184
                        throw new DisplayException('A required service option variable field (env_' . $variable->env_variable . ') was missing from the request.');
185
                    }
186
                    $variableList[] = [
187
                        'id' => $variable->id,
188
                        'env' => $variable->env_variable,
189
                        'val' => $variable->default_value,
190
                    ];
191
                    continue;
192
                }
193
194
                // Check aganist Regex Pattern
195
                if (! is_null($variable->regex) && ! preg_match($variable->regex, $data['env_' . $variable->env_variable])) {
196
                    throw new DisplayException('Failed to validate service option variable field (env_' . $variable->env_variable . ') aganist regex (' . $variable->regex . ').');
197
                }
198
199
                $variableList[] = [
200
                    'id' => $variable->id,
201
                    'env' => $variable->env_variable,
202
                    'val' => $data['env_' . $variable->env_variable],
203
                ];
204
                continue;
205
            }
206
        }
207
208
        // Check Overallocation
209
        if (! $autoDeployed) {
210
            if (is_numeric($node->memory_overallocate) || is_numeric($node->disk_overallocate)) {
211
                $totals = Models\Server::select(DB::raw('SUM(memory) as memory, SUM(disk) as disk'))->where('node_id', $node->id)->first();
212
213
                // Check memory limits
214 View Code Duplication
                if (is_numeric($node->memory_overallocate)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
215
                    $newMemory = $totals->memory + $data['memory'];
216
                    $memoryLimit = ($node->memory * (1 + ($node->memory_overallocate / 100)));
217
                    if ($newMemory > $memoryLimit) {
218
                        throw new DisplayException('The amount of memory allocated to this server would put the node over its allocation limits. This node is allowed ' . ($node->memory_overallocate + 100) . '% of its assigned ' . $node->memory . 'Mb of memory (' . $memoryLimit . 'Mb) of which ' . (($totals->memory / $node->memory) * 100) . '% (' . $totals->memory . 'Mb) is in use already. By allocating this server the node would be at ' . (($newMemory / $node->memory) * 100) . '% (' . $newMemory . 'Mb) usage.');
219
                    }
220
                }
221
222
                // Check Disk Limits
223 View Code Duplication
                if (is_numeric($node->disk_overallocate)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
224
                    $newDisk = $totals->disk + $data['disk'];
225
                    $diskLimit = ($node->disk * (1 + ($node->disk_overallocate / 100)));
226
                    if ($newDisk > $diskLimit) {
227
                        throw new DisplayException('The amount of disk allocated to this server would put the node over its allocation limits. This node is allowed ' . ($node->disk_overallocate + 100) . '% of its assigned ' . $node->disk . 'Mb of disk (' . $diskLimit . 'Mb) of which ' . (($totals->disk / $node->disk) * 100) . '% (' . $totals->disk . 'Mb) is in use already. By allocating this server the node would be at ' . (($newDisk / $node->disk) * 100) . '% (' . $newDisk . 'Mb) usage.');
228
                    }
229
                }
230
            }
231
        }
232
233
        DB::beginTransaction();
234
235
        try {
236
            $uuid = new UuidService;
237
238
            // Add Server to the Database
239
            $server = new Models\Server;
240
            $genUuid = $uuid->generate('servers', 'uuid');
241
            $genShortUuid = $uuid->generateShort('servers', 'uuidShort', $genUuid);
242
243
            if (isset($data['custom_id'])) {
244
                $server->id = $data['custom_id'];
245
            }
246
247
            $server->fill([
248
                'uuid' => $genUuid,
249
                'uuidShort' => $genShortUuid,
250
                'node_id' => $node->id,
251
                'name' => $data['name'],
252
                'description' => $data['description'],
253
                'skip_scripts' => isset($data['skip_scripts']),
254
                'suspended' => false,
255
                'owner_id' => $user->id,
256
                'memory' => $data['memory'],
257
                'swap' => $data['swap'],
258
                'disk' => $data['disk'],
259
                'io' => $data['io'],
260
                'cpu' => $data['cpu'],
261
                'oom_disabled' => isset($data['oom_disabled']),
262
                'allocation_id' => $allocation->id,
263
                'service_id' => $data['service_id'],
264
                'option_id' => $data['option_id'],
265
                'pack_id' => $data['pack_id'],
266
                'startup' => $data['startup'],
267
                'daemonSecret' => $uuid->generate('servers', 'daemonSecret'),
268
                'image' => (isset($data['custom_container']) && ! empty($data['custom_container'])) ? $data['custom_container'] : $option->docker_image,
269
                'username' => $this->generateSFTPUsername($data['name'], $genShortUuid),
270
                'sftp_password' => Crypt::encrypt('not set'),
271
            ]);
272
            $server->save();
273
274
            // Mark Allocation in Use
275
            $allocation->server_id = $server->id;
276
            $allocation->save();
277
278
            // Add Additional Allocations
279
            if (isset($data['allocation_additional']) && is_array($data['allocation_additional'])) {
280
                foreach ($data['allocation_additional'] as $allocation) {
281
                    $model = Models\Allocation::where('id', $allocation)->where('node_id', $data['node_id'])->whereNull('server_id')->first();
282
                    if (! $model) {
283
                        continue;
284
                    }
285
286
                    $model->server_id = $server->id;
287
                    $model->save();
288
                }
289
            }
290
291
            // Add Variables
292
            $environmentVariables = [
293
                'STARTUP' => $data['startup'],
294
            ];
295
296
            foreach ($variableList as $item) {
297
                $environmentVariables[$item['env']] = $item['val'];
298
299
                Models\ServerVariable::create([
300
                    'server_id' => $server->id,
301
                    'variable_id' => $item['id'],
302
                    'variable_value' => $item['val'],
303
                ]);
304
            }
305
306
            $server->load('allocation', 'allocations');
307
            $node->guzzleClient(['X-Access-Token' => $node->daemonSecret])->request('POST', '/servers', [
308
                'json' => [
309
                    'uuid' => (string) $server->uuid,
310
                    'user' => $server->username,
311
                    'build' => [
312
                        'default' => [
313
                            'ip' => $server->allocation->ip,
314
                            'port' => $server->allocation->port,
315
                        ],
316
                        'ports' => $server->allocations->groupBy('ip')->map(function ($item) {
317
                            return $item->pluck('port');
318
                        })->toArray(),
319
                        'env' => $environmentVariables,
320
                        'memory' => (int) $server->memory,
321
                        'swap' => (int) $server->swap,
322
                        'io' => (int) $server->io,
323
                        'cpu' => (int) $server->cpu,
324
                        'disk' => (int) $server->disk,
325
                        'image' => $server->image,
326
                    ],
327
                    'service' => [
328
                        'type' => $service->folder,
329
                        'option' => $option->tag,
330
                        'pack' => (isset($pack)) ? $pack->uuid : null,
331
                        'skip_scripts' => $server->skip_scripts,
332
                    ],
333
                    'keys' => [
334
                        (string) $server->daemonSecret => $this->daemonPermissions,
335
                    ],
336
                    'rebuild' => false,
337
                    'start_on_completion' => isset($data['start_on_completion']),
338
                ],
339
            ]);
340
341
            DB::commit();
342
343
            return $server;
344
        } catch (\Exception $ex) {
345
            DB::rollBack();
346
            throw $ex;
347
        }
348
    }
349
350
    /**
351
     * Update the details for a server.
352
     *
353
     * @param  int    $id
354
     * @param  array  $data
355
     * @return \Pterodactyl\Models\Server
356
     *
357
     * @throws \Pterodactyl\Exceptions\DisplayException
358
     * @throws \Pterodactyl\Exceptions\DisplayValidationException
359
     */
360
    public function updateDetails($id, array $data)
361
    {
362
        $uuid = new UuidService;
363
        $resetDaemonKey = false;
364
365
        // Validate Fields
366
        $validator = Validator::make($data, [
367
            'owner_id' => 'sometimes|required|integer|exists:users,id',
368
            'name' => 'sometimes|required|regex:([\w .-]{1,200})',
369
            'description' => 'sometimes|required|string',
370
            'reset_token' => 'sometimes|required|accepted',
371
        ]);
372
373
        // Run validator, throw catchable and displayable exception if it fails.
374
        // Exception includes a JSON result of failed validation rules.
375
        if ($validator->fails()) {
376
            throw new DisplayValidationException(json_encode($validator->errors()));
377
        }
378
379
        DB::beginTransaction();
380
381
        try {
382
            $server = Models\Server::with('user')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
383
384
            // Update daemon secret if it was passed.
385
            if (isset($data['reset_token']) || (isset($data['owner_id']) && (int) $data['owner_id'] !== $server->user->id)) {
386
                $oldDaemonKey = $server->daemonSecret;
387
                $server->daemonSecret = $uuid->generate('servers', 'daemonSecret');
388
                $resetDaemonKey = true;
389
            }
390
391
            // Save our changes
392
            $server->fill($data)->save();
393
394
            // Do we need to update? If not, return successful.
395
            if (! $resetDaemonKey) {
396
                return DB::commit();
397
            }
398
399
            $res = $server->node->guzzleClient([
400
                'X-Access-Server' => $server->uuid,
401
                'X-Access-Token' => $server->node->daemonSecret,
402
            ])->request('PATCH', '/server', [
403
                'exceptions' => false,
404
                'json' => [
405
                    'keys' => [
406
                        (string) $oldDaemonKey => [],
0 ignored issues
show
Bug introduced by
The variable $oldDaemonKey does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
407
                        (string) $server->daemonSecret => $this->daemonPermissions,
408
                    ],
409
                ],
410
            ]);
411
412
            if ($res->getStatusCode() === 204) {
413
                DB::commit();
414
415
                return $server;
416
            } else {
417
                throw new DisplayException('Daemon returned a a non HTTP/204 error code. HTTP/' + $res->getStatusCode());
418
            }
419
        } catch (\Exception $ex) {
420
            DB::rollBack();
421
            throw $ex;
422
        }
423
    }
424
425
    /**
426
     * Update the container for a server.
427
     *
428
     * @param  int    $id
429
     * @param  array  $data
430
     * @return \Pterodactyl\Models\Server
431
     *
432
     * @throws \Pterodactyl\Exceptions\DisplayValidationException
433
     */
434
    public function updateContainer($id, array $data)
435
    {
436
        $validator = Validator::make($data, [
437
            'docker_image' => 'required|string',
438
        ]);
439
440
        // Run validator, throw catchable and displayable exception if it fails.
441
        // Exception includes a JSON result of failed validation rules.
442
        if ($validator->fails()) {
443
            throw new DisplayValidationException(json_encode($validator->errors()));
444
        }
445
446
        DB::beginTransaction();
447
        try {
448
            $server = Models\Server::findOrFail($id);
449
450
            $server->image = $data['docker_image'];
451
            $server->save();
452
453
            $server->node->guzzleClient([
454
                'X-Access-Server' => $server->uuid,
455
                'X-Access-Token' => $server->node->daemonSecret,
456
            ])->request('PATCH', '/server', [
457
                'json' => [
458
                    'build' => [
459
                        'image' => $server->image,
460
                    ],
461
                ],
462
            ]);
463
464
            DB::commit();
465
466
            return $server;
467
        } catch (\Exception $ex) {
468
            DB::rollBack();
469
            throw $ex;
470
        }
471
    }
472
473
    /**
474
     * Update the build details for a server.
475
     *
476
     * @param  int    $id
477
     * @param  array  $data
478
     * @return \Pterodactyl\Models\Server
479
     *
480
     * @throws \Pterodactyl\Exceptions\DisplayException
481
     * @throws \Pterodactyl\Exceptions\DisplayValidationException
482
     */
483
    public function changeBuild($id, array $data)
484
    {
485
        $validator = Validator::make($data, [
486
            'allocation_id' => 'sometimes|required|exists:allocations,id',
487
            'add_allocations' => 'sometimes|required|array',
488
            'remove_allocations' => 'sometimes|required|array',
489
            'memory' => 'sometimes|required|integer|min:0',
490
            'swap' => 'sometimes|required|integer|min:-1',
491
            'io' => 'sometimes|required|integer|min:10|max:1000',
492
            'cpu' => 'sometimes|required|integer|min:0',
493
            'disk' => 'sometimes|required|integer|min:0',
494
        ]);
495
496
        // Run validator, throw catchable and displayable exception if it fails.
497
        // Exception includes a JSON result of failed validation rules.
498
        if ($validator->fails()) {
499
            throw new DisplayValidationException(json_encode($validator->errors()));
500
        }
501
502
        DB::beginTransaction();
503
504
        try {
505
            $server = Models\Server::with('allocation', 'allocations')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
506
            $newBuild = [];
507
            $newAllocations = [];
0 ignored issues
show
Unused Code introduced by
$newAllocations is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
508
509
            if (isset($data['allocation_id'])) {
510
                if ((int) $data['allocation_id'] !== $server->allocation_id) {
511
                    $selection = $server->allocations->where('id', $data['allocation_id'])->first();
512
                    if (! $selection) {
513
                        throw new DisplayException('The requested default connection is not allocated to this server.');
514
                    }
515
516
                    $server->allocation_id = $selection->id;
517
                    $newBuild['default'] = ['ip' => $selection->ip, 'port' => $selection->port];
518
519
                    $server->load('allocation');
520
                }
521
            }
522
523
            $newPorts = false;
524
            $firstNewAllocation = null;
525
            // Add Assignments
526
            if (isset($data['add_allocations'])) {
527
                foreach ($data['add_allocations'] as $allocation) {
528
                    $model = Models\Allocation::where('id', $allocation)->whereNull('server_id')->first();
529
                    if (! $model) {
530
                        continue;
531
                    }
532
533
                    $newPorts = true;
534
                    $firstNewAllocation = (is_null($firstNewAllocation)) ? $model->id : $firstNewAllocation;
535
                    $model->update([
536
                        'server_id' => $server->id,
537
                    ]);
538
                }
539
540
                $server->load('allocations');
541
            }
542
543
            // Remove Assignments
544
            if (isset($data['remove_allocations'])) {
545
                foreach ($data['remove_allocations'] as $allocation) {
546
                    // Can't remove the assigned IP/Port combo
547
                    if ((int) $allocation === $server->allocation_id) {
548
                        // No New Allocation
549
                        if (is_null($firstNewAllocation)) {
550
                            continue;
551
                        }
552
553
                        // New Allocation, set as the default.
554
                        $server->allocation_id = $firstNewAllocation;
555
                    }
556
557
                    $newPorts = true;
558
                    Models\Allocation::where('id', $allocation)->where('server_id', $server->id)->update([
559
                        'server_id' => null,
560
                    ]);
561
                }
562
563
                $server->load('allocations');
564
            }
565
566
            if ($newPorts) {
567
                $newBuild['ports|overwrite'] = $server->allocations->groupBy('ip')->map(function ($item) {
568
                    return $item->pluck('port');
569
                })->toArray();
570
            }
571
572
            // @TODO: verify that server can be set to this much memory without
573
            // going over node limits.
574 View Code Duplication
            if (isset($data['memory']) && $server->memory !== (int) $data['memory']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
575
                $server->memory = $data['memory'];
576
                $newBuild['memory'] = (int) $server->memory;
577
            }
578
579 View Code Duplication
            if (isset($data['swap']) && $server->swap !== (int) $data['swap']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
580
                $server->swap = $data['swap'];
581
                $newBuild['swap'] = (int) $server->swap;
582
            }
583
584
            // @TODO: verify that server can be set to this much disk without
585
            // going over node limits.
586 View Code Duplication
            if (isset($data['disk']) && $server->disk !== (int) $data['disk']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
587
                $server->disk = $data['disk'];
588
                $newBuild['disk'] = (int) $server->disk;
589
            }
590
591 View Code Duplication
            if (isset($data['cpu']) && $server->cpu !== (int) $data['cpu']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
592
                $server->cpu = $data['cpu'];
593
                $newBuild['cpu'] = (int) $server->cpu;
594
            }
595
596 View Code Duplication
            if (isset($data['io']) && $server->io !== (int) $data['io']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
597
                $server->io = $data['io'];
598
                $newBuild['io'] = (int) $server->io;
599
            }
600
601
            // Try save() here so if it fails we haven't contacted the daemon
602
            // This won't be committed unless the HTTP request succeedes anyways
603
            $server->save();
604
605
            if (! empty($newBuild)) {
606
                $server->node->guzzleClient([
607
                    'X-Access-Server' => $server->uuid,
608
                    'X-Access-Token' => $server->node->daemonSecret,
609
                ])->request('PATCH', '/server', [
610
                    'json' => [
611
                        'build' => $newBuild,
612
                    ],
613
                ]);
614
            }
615
616
            DB::commit();
617
618
            return $server;
619
        } catch (\Exception $ex) {
620
            DB::rollBack();
621
            throw $ex;
622
        }
623
    }
624
625
    /**
626
     * Update the service configuration for a server.
627
     *
628
     * @param  int    $id
629
     * @param  array  $data
630
     * @return void
631
     *
632
     * @throws \GuzzleHttp\Exception\RequestException
633
     * @throws \Pterodactyl\Exceptions\DisplayException
634
     * @throws \Pterodactyl\Exceptions\DisplayValidationException
635
     */
636
    protected function changeService($id, array $data)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $data is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
637
    {
638
639
    }
640
641
    protected function processVariables(Models\Server $server, $data, $admin = false)
642
    {
643
        $server->load('option.variables');
644
645
        if ($admin) {
646
            $server->startup = $data['startup'];
647
            $server->save();
648
        }
649
650
        if ($server->option->variables) {
651
            foreach ($server->option->variables as &$variable) {
652
                $set = isset($data['env_' . $variable->id]);
653
654
                // If user is not an admin and are trying to edit a non-editable field
655
                // or an invisible field just silently skip the variable.
656
                if (! $admin && (! $variable->user_editable || ! $variable->user_viewable)) {
657
                    continue;
658
                }
659
660
                // Perform Field Validation
661
                $validator = Validator::make([
662
                    'variable_value' => ($set) ? $data['env_' . $variable->id] : null,
663
                ], [
664
                    'variable_value' => $variable->rules,
665
                ]);
666
667
                if ($validator->fails()) {
668
                    throw new DisplayValidationException(json_encode(
669
                        collect([
670
                            'notice' => ['There was a validation error with the `' . $variable->name . '` variable.'],
671
                        ])->merge($validator->errors()->toArray())
672
                    ));
673
                }
674
675
                $svar = Models\ServerVariable::firstOrNew([
676
                    'server_id' => $server->id,
677
                    'variable_id' => $variable->id,
678
                ]);
679
680
                // Set the value; if one was not passed set it to the default value
681
                if ($set) {
682
                    $svar->variable_value = $data['env_' . $variable->id];
683
684
                // Not passed, check if this record exists if so keep value, otherwise set default
685
                } else {
686
                    $svar->variable_value = ($svar->exists) ? $svar->variable_value : $variable->default_value;
687
                }
688
689
                $svar->save();
690
            }
691
        }
692
693
        // Reload Variables
694
        $server->load('variables');
695 View Code Duplication
        return $server->option->variables->map(function ($item, $key) use ($server) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
696
            $display = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first();
697
698
            return [
699
                'variable' => $item->env_variable,
700
                'value' => (! is_null($display)) ? $display : $item->default_value,
701
            ];
702
        });
703
    }
704
705
    /**
706
     * Update the startup details for a server.
707
     *
708
     * @param  int    $id
709
     * @param  array  $data
710
     * @param  bool   $admin
711
     * @return bool
712
     *
713
     * @throws \GuzzleHttp\Exception\RequestException
714
     * @throws \Pterodactyl\Exceptions\DisplayException
715
     * @throws \Pterodactyl\Exceptions\DisplayValidationException
716
     */
717
    public function updateStartup($id, array $data, $admin = false)
718
    {
719
        $server = Models\Server::with('variables', 'option.variables')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
720
        $hasServiceChanges = false;
721
722
        if ($admin) {
723
            // User is an admin, lots of things to do here.
724
            $validator = Validator::make($data, [
725
                'startup' => 'required|string',
726
                'skip_scripts' => 'sometimes|required|boolean',
727
                'service_id' => 'required|numeric|min:1|exists:services,id',
728
                'option_id' => 'required|numeric|min:1|exists:service_options,id',
729
                'pack_id' => 'sometimes|nullable|numeric|min:0',
730
            ]);
731
732
            if ((int) $data['pack_id'] < 1) {
733
                $data['pack_id'] = null;
734
            }
735
736
            if ($validator->fails()) {
737
                throw new DisplayValidationException(json_encode($validator->errors()));
738
            }
739
740
            if (
741
                $server->service_id != $data['service_id'] ||
742
                $server->option_id != $data['option_id'] ||
743
                $server->pack_id != $data['pack_id']
744
            ) {
745
                $hasServiceChanges = true;
746
            }
747
        }
748
749
        // If user isn't an administrator, this function is being access from the front-end
750
        // Just try to update specific variables.
751
        if (! $admin || ! $hasServiceChanges) {
752
            return DB::transaction(function () use ($admin, $data, $server) {
753
                $environment = $this->processVariables($server, $data, $admin);
754
755
                $server->node->guzzleClient([
756
                    'X-Access-Server' => $server->uuid,
757
                    'X-Access-Token' => $server->node->daemonSecret,
758
                ])->request('PATCH', '/server', [
759
                    'json' => [
760
                        'build' => [
761
                            'env|overwrite' => $environment->pluck('value', 'variable')->merge(['STARTUP' => $server->startup])->toArray(),
762
                        ],
763
                    ],
764
                ]);
765
766
                return false;
767
            });
768
        }
769
770
        // Validate those Service Option Variables
771
        // We know the service and option exists because of the validation.
772
        // We need to verify that the option exists for the service, and then check for
773
        // any required variable fields. (fields are labeled env_<env_variable>)
774
        $option = Models\ServiceOption::where('id', $data['option_id'])->where('service_id', $data['service_id'])->first();
775
        if (! $option) {
776
            throw new DisplayException('The requested service option does not exist for the specified service.');
777
        }
778
779
        // Validate the Pack
780 View Code Duplication
        if (! isset($data['pack_id']) || (int) $data['pack_id'] < 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
781
            $data['pack_id'] = null;
782
        } else {
783
            $pack = Models\Pack::where('id', $data['pack_id'])->where('option_id', $data['option_id'])->first();
784
            if (! $pack) {
785
                throw new DisplayException('The requested service pack does not seem to exist for this combination.');
786
            }
787
        }
788
789
        return DB::transaction(function () use ($admin, $data, $server) {
790
            $server->installed = 0;
791
            $server->service_id = $data['service_id'];
792
            $server->option_id = $data['option_id'];
793
            $server->pack_id = $data['pack_id'];
794
            $server->skip_scripts = isset($data['skip_scripts']);
795
            $server->save();
796
797
            $server->variables->each->delete();
798
799
            $server->load('service', 'pack');
800
801
            // Send New Environment
802
            $environment = $this->processVariables($server, $data, $admin);
803
804
            $server->node->guzzleClient([
805
                'X-Access-Server' => $server->uuid,
806
                'X-Access-Token' => $server->node->daemonSecret,
807
            ])->request('POST', '/server/reinstall', [
808
                'json' => [
809
                    'build' => [
810
                        'env|overwrite' => $environment->pluck('value', 'variable')->merge(['STARTUP' => $server->startup])->toArray(),
811
                    ],
812
                    'service' => [
813
                        'type' => $server->option->service->folder,
814
                        'option' => $server->option->tag,
815
                        'pack' => (! is_null($server->pack_id)) ? $server->pack->uuid : null,
816
                        'skip_scripts' => $server->skip_scripts,
817
                    ],
818
                ],
819
            ]);
820
821
            return true;
822
        });
823
824
    }
825
826
    /**
827
     * Delete a server from the system permanetly.
828
     *
829
     * @param  int   $id
830
     * @param  bool  $force
831
     * @return void
832
     *
833
     * @throws \Pterodactyl\Exceptions\DisplayException
834
     */
835
    public function delete($id, $force = false)
836
    {
837
        $server = Models\Server::with('node', 'allocations', 'variables')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
838
839
        // Due to MySQL lockouts if the daemon response fails, we need to
840
        // delete the server from the daemon first. If it succeedes and then
841
        // MySQL fails, users just need to force delete the server.
842
        //
843
        // If this is a force delete, continue anyways.
844
        try {
845
            $server->node->guzzleClient([
846
                'X-Access-Token' => $server->node->daemonSecret,
847
                'X-Access-Server' => $server->uuid,
848
            ])->request('DELETE', '/servers');
849
        } catch (ClientException $ex) {
850
            // Exception is thrown on 4XX HTTP errors, so catch and determine
851
            // if we should continue, or if there is a permissions error.
852
            //
853
            // Daemon throws a 404 if the server doesn't exist, if that is returned
854
            // continue with deletion, even if not a force deletion.
855
            $response = $ex->getResponse();
0 ignored issues
show
Unused Code introduced by
$response is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
856
            if ($ex->getResponse()->getStatusCode() !== 404 && ! $force) {
857
                throw new DisplayException($ex->getMessage());
858
            }
859
        } catch (TransferException $ex) {
860
            if (! $force) {
861
                throw new DisplayException($ex->getMessage());
862
            }
863
        } catch (\Exception $ex) {
864
            throw $ex;
865
        }
866
867
        DB::transaction(function () use ($server) {
868
            $server->allocations->each(function ($item) {
869
                $item->server_id = null;
870
                $item->save();
871
            });
872
873
            $server->variables->each->delete();
874
875
            $server->load('subusers.permissions');
876
            $server->subusers->each(function ($subuser) {
877
                $subuser->permissions->each->delete();
878
                $subuser->delete();
879
            });
880
881
            $server->downloads->each->delete();
882
            $server->tasks->each->delete();
883
884
            // Delete Databases
885
            // This is the one un-recoverable point where
886
            // transactions will not save us.
887
            $repository = new DatabaseRepository;
888
            $server->databases->each(function ($item) use ($repository) {
889
                $repository->drop($item->id);
890
            });
891
892
            // Fully delete the server.
893
            $server->delete();
894
        });
895
    }
896
897
    /**
898
     * Toggle the install status of a serve.
899
     *
900
     * @param  int    $id
901
     * @return bool
902
     *
903
     * @throws \Pterodactyl\Exceptions\DisplayException
904
     */
905
    public function toggleInstall($id)
906
    {
907
        $server = Models\Server::findOrFail($id);
908
        if ($server->installed > 1) {
909
            throw new DisplayException('This server was marked as having a failed install or being deleted, you cannot override this.');
910
        }
911
        $server->installed = ! $server->installed;
912
913
        return $server->save();
914
    }
915
916
    /**
917
     * Suspends or unsuspends a server.
918
     *
919
     * @param  int   $id
920
     * @param  bool  $unsuspend
921
     * @return void
922
     */
923
    public function toggleAccess($id, $unsuspend = true)
924
    {
925
        $server = Models\Server::with('node')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
926
927
        DB::transaction(function () use ($server, $unsuspend) {
928
            if (
929
                (! $unsuspend && $server->suspended) ||
930
                ($unsuspend && ! $server->suspended)
931
            ) {
932
                return true;
933
            }
934
935
            $server->suspended = ! $unsuspend;
936
            $server->save();
937
938
            $server->node->guzzleClient([
939
                'X-Access-Token' => $server->node->daemonSecret,
940
                'X-Access-Server' => $server->uuid,
941
            ])->request('POST', ($unsuspend) ? '/server/unsuspend' : '/server/suspend');
942
        });
943
    }
944
945
    /**
946
     * Updates the SFTP password for a server.
947
     *
948
     * @param  int     $id
949
     * @param  string  $password
950
     * @return void
951
     *
952
     * @throws \Pterodactyl\Exceptions\DisplayValidationException
953
     */
954
    public function updateSFTPPassword($id, $password)
955
    {
956
        $server = Models\Server::with('node')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
957
958
        $validator = Validator::make(['password' => $password], [
959
            'password' => 'required|regex:/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})$/',
960
        ]);
961
962
        if ($validator->fails()) {
963
            throw new DisplayValidationException(json_encode($validator->errors()));
964
        }
965
966
        DB::transaction(function () use ($password, $server) {
967
            $server->sftp_password = Crypt::encrypt($password);
968
            $server->save();
969
970
            $server->node->guzzleClient([
971
                'X-Access-Token' => $server->node->daemonSecret,
972
                'X-Access-Server' => $server->uuid,
973
            ])->request('POST', '/server/password', [
974
                'json' => ['password' => $password],
975
            ]);
976
        });
977
    }
978
979
    /**
980
     * Marks a server for reinstallation on the node.
981
     *
982
     * @param  int     $id
983
     * @return void
984
     */
985
    public function reinstall($id)
986
    {
987
        $server = Models\Server::with('node')->findOrFail($id);
0 ignored issues
show
Bug introduced by
The method findOrFail does only exist in Illuminate\Database\Eloquent\Builder, but not in Illuminate\Database\Eloquent\Model.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
988
989
        DB::transaction(function () use ($server) {
990
            $server->installed = 0;
991
            $server->save();
992
993
            $server->node->guzzleClient([
994
                'X-Access-Token' => $server->node->daemonSecret,
995
                'X-Access-Server' => $server->uuid,
996
            ])->request('POST', '/server/reinstall');
997
        });
998
    }
999
}
1000