Merge branch 'master' into develop

This commit is contained in:
Dane Everitt 2019-08-04 13:49:26 -07:00
commit 81143e231a
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
41 changed files with 303 additions and 190 deletions

View file

@ -3,6 +3,35 @@ This file is a running track of new features and fixes to each version of the pa
This project follows [Semantic Versioning](http://semver.org) guidelines.
## v0.7.15 (Derelict Dermodactylus)
### Fixed
* Fixes support for PHP 7.3 when running `composer install` commands due to a dependency that needed updating.
* Automatic allocation field when creating a new node (or updating one) should now properly remeber its old
value when showing an error state.
* Mass deleting files now executes properly and doesn't result in a JS console error.
* Scrolling on email settings page now works.
* Database host management will now properly display an error message to the user when there is any type of MySQL related
error encountered during creation or update.
* Two-factor tokens generated when a company name has a space in it will now properly be parsed on iOS authenticator devices.
* Fixed 500 error when trying to request subuser's from a server in the application API.
* Creating a node allocation via the API no longer requires an alias field be passed through in the request.
* Bulk power management for servers via the CLI no longer fails when servers span multiple nodes.
### Added
* Server listing view now displays the total used disk space for each server.
* Client API endpoint to list all servers now supports an additional `?filter=subuser-of|all|admin|owner` parameter to
return different groupings of servers. The default value is `subuser-of` which will include all of the user's servers
that they are the owner of, as well as all servers they're a subuser of.
* Added back ability to toggle OOM killer status on a per-server basis.
* Added `LOCK TABLES` permission for generated database users.
### Changed
* Updated Paper egg to not download `server.properties` each time. [parkervcp/eggs#260](https://github.com/parkervcp/eggs/issues/260)
* Insurgency egg now uses the proper dedicated server ID.
* Teamspeak egg updated with improved installation process and grabbing latest versions.
* OOM killer disabled by default on all new servers.
* Passwords generated for MySQL now include special characters and are 24 characters in length.
## v0.7.14 (Derelict Dermodactylus)
### Fixed
* **[SECURITY]** Fixes an XSS vulnerability when performing certain actions in the file manager.

View file

@ -102,7 +102,10 @@ class BulkPowerActionCommand extends Command
$bar->clear();
try {
$this->powerRepository->setServer($server)->sendSignal($action);
$this->powerRepository
->setNode($server->node)
->setServer($server)
->sendSignal($action);
} catch (RequestException $exception) {
$this->output->error(trans('command/messages.server.power.action_failed', [
'name' => $server->name,

View file

@ -2,6 +2,7 @@
namespace Pterodactyl\Http\Controllers\Admin;
use Exception;
use PDOException;
use Illuminate\View\View;
use Pterodactyl\Models\DatabaseHost;
@ -118,17 +119,22 @@ class DatabaseController extends Controller
* @param \Pterodactyl\Http\Requests\Admin\DatabaseHostFormRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function create(DatabaseHostFormRequest $request): RedirectResponse
{
try {
$host = $this->creationService->handle($request->normalize());
} catch (PDOException $ex) {
$this->alert->danger($ex->getMessage())->flash();
} catch (Exception $exception) {
if ($exception instanceof PDOException || $exception->getPrevious() instanceof PDOException) {
$this->alert->danger(
sprintf('There was an error while trying to connect to the host or while executing a query: "%s"', $exception->getMessage())
)->flash();
return redirect()->route('admin.databases');
redirect()->route('admin.databases')->withInput($request->validated());
} else {
throw $exception;
}
}
$this->alert->success('Successfully created a new database host on the system.')->flash();
@ -143,8 +149,7 @@ class DatabaseController extends Controller
* @param \Pterodactyl\Models\DatabaseHost $host
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function update(DatabaseHostFormRequest $request, DatabaseHost $host): RedirectResponse
{
@ -153,9 +158,17 @@ class DatabaseController extends Controller
try {
$this->updateService->handle($host->id, $request->normalize());
$this->alert->success('Database host was updated successfully.')->flash();
} catch (PDOException $ex) {
$this->alert->danger($ex->getMessage())->flash();
} catch (Exception $exception) {
// Catch any SQL related exceptions and display them back to the user, otherwise just
// throw the exception like normal and move on with it.
if ($exception instanceof PDOException || $exception->getPrevious() instanceof PDOException) {
$this->alert->danger(
sprintf('There was an error while trying to connect to the host or while executing a query: "%s"', $exception->getMessage())
)->flash();
$redirect->withInput($request->normalize());
} else {
throw $exception;
}
}
return $redirect;

View file

@ -516,7 +516,7 @@ class ServersController extends Controller
$this->buildModificationService->handle($server, $request->only([
'allocation_id', 'add_allocations', 'remove_allocations',
'memory', 'swap', 'io', 'cpu', 'disk',
'database_limit', 'allocation_limit',
'database_limit', 'allocation_limit', 'oom_disabled',
]));
$this->alert->success(trans('admin/server.alerts.build_updated'))->flash();
@ -589,8 +589,7 @@ class ServersController extends Controller
* @param int $server
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Exception
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Throwable
*/
public function resetDatabasePassword(Request $request, $server)
{
@ -599,7 +598,7 @@ class ServersController extends Controller
['id', '=', $request->input('database')],
]);
$this->databasePasswordService->handle($database, str_random(24));
$this->databasePasswordService->handle($database);
return response('', 204);
}

View file

@ -87,12 +87,11 @@ class DatabaseController extends ApplicationApiController
* @param \Pterodactyl\Http\Requests\Api\Application\Servers\Databases\ServerDatabaseWriteRequest $request
* @return \Illuminate\Http\Response
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function resetPassword(ServerDatabaseWriteRequest $request): Response
{
$this->databasePasswordService->handle($request->getModel(Database::class), str_random(24));
$this->databasePasswordService->handle($request->getModel(Database::class));
return response('', 204);
}

View file

@ -35,9 +35,28 @@ class ClientController extends ClientApiController
*/
public function index(GetServersRequest $request): array
{
$servers = $this->repository
// Check for the filter parameter on the request.
switch ($request->input('filter')) {
case 'all':
$filter = User::FILTER_LEVEL_ALL;
break;
case 'admin':
$filter = User::FILTER_LEVEL_ADMIN;
break;
case 'owner':
$filter = User::FILTER_LEVEL_OWNER;
break;
case 'subuser-of':
default:
$filter = User::FILTER_LEVEL_SUBUSER;
break;
}
$servers = $this->repository->
->setSearchTerm($request->input('query'))
->filterUserAccessServers($request->user(), User::FILTER_LEVEL_ALL);
->filterUserAccessServers(
$request->user(), $filter, config('pterodactyl.paginate.frontend.servers')
);
return $this->fractal->collection($servers)
->transformWith($this->getTransformer(ServerTransformer::class))

View file

@ -40,7 +40,7 @@ class StoreAllocationRequest extends ApplicationApiRequest
return [
'allocation_ip' => $data['ip'],
'allocation_ports' => $data['ports'],
'allocation_alias' => $data['alias'],
'allocation_alias' => $data['alias'] ?? null,
];
}
}

View file

@ -41,6 +41,7 @@ class StoreServerRequest extends ApplicationApiRequest
'startup' => $rules['startup'],
'environment' => 'present|array',
'skip_scripts' => 'sometimes|boolean',
'oom_disabled' => 'sometimes|boolean',
// Resource limitations
'limits' => 'required|array',

View file

@ -18,6 +18,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest
return [
'allocation' => $rules['allocation_id'],
'oom_disabled' => $rules['oom_disabled'],
'limits' => 'sometimes|array',
'limits.memory' => $this->requiredToOptional('memory', $rules['memory'], true),

View file

@ -28,6 +28,16 @@ class Server extends Model implements CleansAttributes, ValidableContract
*/
protected $table = 'servers';
/**
* Default values when creating the model. We want to switch to disabling OOM killer
* on server instances unless the user specifies otherwise in the request.
*
* @var array
*/
protected $attributes = [
'oom_disabled' => true,
];
/**
* The attributes that should be mutated to dates.
*
@ -53,6 +63,7 @@ class Server extends Model implements CleansAttributes, ValidableContract
'swap' => 'required',
'io' => 'required',
'cpu' => 'required',
'oom_disabled' => 'sometimes',
'disk' => 'required',
'nest_id' => 'required',
'egg_id' => 'required',
@ -79,6 +90,7 @@ class Server extends Model implements CleansAttributes, ValidableContract
'swap' => 'numeric|min:-1',
'io' => 'numeric|between:10,1000',
'cpu' => 'numeric|min:0',
'oom_disabled' => 'boolean',
'disk' => 'numeric|min:0',
'allocation_id' => 'bail|unique:servers|exists:allocations,id',
'nest_id' => 'exists:nests,id',
@ -107,7 +119,7 @@ class Server extends Model implements CleansAttributes, ValidableContract
'disk' => 'integer',
'io' => 'integer',
'cpu' => 'integer',
'oom_disabled' => 'integer',
'oom_disabled' => 'boolean',
'allocation_id' => 'integer',
'nest_id' => 'integer',
'egg_id' => 'integer',
@ -164,11 +176,11 @@ class Server extends Model implements CleansAttributes, ValidableContract
/**
* Gets the subusers associated with a server.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
* @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
*/
public function subusers()
{
return $this->hasMany(Subuser::class);
return $this->hasManyThrough(User::class, Subuser::class, 'server_id', 'id', 'id', 'user_id');
}
/**

View file

@ -153,7 +153,7 @@ class DatabaseRepository extends EloquentRepository implements DatabaseRepositor
public function assignUserToDatabase(string $database, string $username, string $remote): bool
{
return $this->run(sprintf(
'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX, EXECUTE ON `%s`.* TO `%s`@`%s`',
'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX, LOCK TABLES, EXECUTE ON `%s`.* TO `%s`@`%s`',
$database,
$username,
$remote

View file

@ -2,8 +2,9 @@
namespace Pterodactyl\Services\Databases;
use Webmozart\Assert\Assert;
use Exception;
use Pterodactyl\Models\Database;
use Illuminate\Support\Facades\Log;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Extensions\DynamicDatabaseConnection;
@ -55,24 +56,30 @@ class DatabasePasswordService
* Updates a password for a given database.
*
* @param \Pterodactyl\Models\Database|int $database
* @param string $password
* @return bool
* @return string
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function handle($database, string $password): bool
public function handle(Database $database): string
{
if (! $database instanceof Database) {
Assert::integerish($database);
$password = str_random(24);
// Given a random string of characters, randomly loop through the characters and replace some
// with special characters to avoid issues with MySQL password requirements on some servers.
try {
for ($i = 0; $i < random_int(2, 6); $i++) {
$character = ['!', '@', '=', '.', '+', '^'][random_int(0, 5)];
$database = $this->repository->find($database);
$password = substr_replace($password, $character, random_int(0, 23), 1);
}
} catch (Exception $exception) {
// Just log the error and hope for the best at this point.
Log::error($exception);
}
$this->connection->transaction(function () use ($database, $password) {
$this->dynamic->set('dynamic', $database->database_host_id);
$this->connection->beginTransaction();
$updated = $this->repository->withoutFreshModel()->update($database->id, [
$this->repository->withoutFreshModel()->update($database->id, [
'password' => $this->encrypter->encrypt($password),
]);
@ -80,10 +87,8 @@ class DatabasePasswordService
$this->repository->createUser($database->username, $database->remote, $password);
$this->repository->assignUserToDatabase($database->database, $database->username, $database->remote);
$this->repository->flush();
});
unset($password);
$this->connection->commit();
return $updated;
return $password;
}
}

View file

@ -65,13 +65,11 @@ class HostCreationService
* @param array $data
* @return \Pterodactyl\Models\DatabaseHost
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function handle(array $data): DatabaseHost
{
$this->connection->beginTransaction();
return $this->connection->transaction(function () use ($data) {
$host = $this->repository->create([
'password' => $this->encrypter->encrypt(array_get($data, 'password')),
'name' => array_get($data, 'name'),
@ -85,8 +83,8 @@ class HostCreationService
// Confirm access using the provided credentials before saving data.
$this->dynamic->set('dynamic', $host);
$this->databaseManager->connection('dynamic')->select('SELECT 1 FROM dual');
$this->connection->commit();
return $host;
});
}
}

View file

@ -71,10 +71,9 @@ class HostUpdateService
*
* @param int $hostId
* @param array $data
* @return mixed
* @return \Pterodactyl\Models\DatabaseHost
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function handle(int $hostId, array $data): DatabaseHost
{
@ -84,13 +83,12 @@ class HostUpdateService
unset($data['password']);
}
$this->connection->beginTransaction();
return $this->connection->transaction(function () use ($data, $hostId) {
$host = $this->repository->update($hostId, $data);
$this->dynamic->set('dynamic', $host);
$this->databaseManager->connection('dynamic')->select('SELECT 1 FROM dual');
$this->connection->commit();
return $host;
});
}
}

View file

@ -85,6 +85,7 @@ class BuildModificationService
}
$server = $this->repository->withFreshModel()->update($server->id, [
'oom_disabled' => array_get($data, 'oom_disabled'),
'memory' => array_get($data, 'memory'),
'swap' => array_get($data, 'swap'),
'io' => array_get($data, 'io'),
@ -97,6 +98,7 @@ class BuildModificationService
$allocations = $this->allocationRepository->findWhere([['server_id', '=', $server->id]]);
$build['oom_disabled'] = $server->oom_disabled;
$build['memory'] = (int) $server->memory;
$build['swap'] = (int) $server->swap;
$build['io'] = (int) $server->io;

View file

@ -70,6 +70,7 @@ class ServerConfigurationStructureService
return $item->pluck('port');
})->toArray(),
'env' => $this->environment->handle($server),
'oom_disabled' => $server->oom_disabled,
'memory' => (int) $server->memory,
'swap' => (int) $server->swap,
'io' => (int) $server->io,

View file

@ -227,7 +227,7 @@ class ServerCreationService
'disk' => array_get($data, 'disk'),
'io' => array_get($data, 'io'),
'cpu' => array_get($data, 'cpu'),
'oom_disabled' => false,
'oom_disabled' => array_get($data, 'oom_disabled', true),
'allocation_id' => array_get($data, 'allocation_id'),
'nest_id' => array_get($data, 'nest_id'),
'egg_id' => array_get($data, 'egg_id'),

View file

@ -71,7 +71,7 @@ class TwoFactorSetupService
'totp_secret' => $this->encrypter->encrypt($secret),
]);
$company = $this->config->get('app.name');
$company = preg_replace('/\s/', '', $this->config->get('app.name'));
return sprintf(
'otpauth://totp/%1$s:%2$s?secret=%3$s&issuer=%1$s',

View file

@ -45,7 +45,7 @@
"barryvdh/laravel-ide-helper": "^2.5",
"codedungeon/phpunit-result-printer": "^0.17.1",
"filp/whoops": "^2.1",
"friendsofphp/php-cs-fixer": "^2.11.1",
"friendsofphp/php-cs-fixer": "^2.15.1",
"fzaninotto/faker": "^1.6",
"laravel/dusk": "^3.0",
"martinlindhe/laravel-vue-i18n-generator": "^0.1.28",

57
composer.lock generated
View file

@ -46,9 +46,9 @@
"authors": [
{
"name": "Gijs Jorissen",
"role": "Developer",
"email": "hello@appstract.team",
"homepage": "https://appstract.team",
"role": "Developer"
"homepage": "https://appstract.team"
}
],
"description": "Handy Blade directives",
@ -1069,12 +1069,12 @@
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/ivanakimov/hashids.php.git",
"url": "https://github.com/vinkla/hashids.git",
"reference": "b6c61142bfe36d43740a5419d11c351dddac0458"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ivanakimov/hashids.php/zipball/b6c61142bfe36d43740a5419d11c351dddac0458",
"url": "https://api.github.com/repos/vinkla/hashids/zipball/b6c61142bfe36d43740a5419d11c351dddac0458",
"reference": "b6c61142bfe36d43740a5419d11c351dddac0458",
"shasum": ""
},
@ -2633,8 +2633,8 @@
"authors": [
{
"name": "Antonio Carlos Ribeiro",
"email": "acr@antoniocarlosribeiro.com",
"role": "Creator & Designer"
"role": "Creator & Designer",
"email": "acr@antoniocarlosribeiro.com"
}
],
"description": "A One Time Password Authentication package, compatible with Google Authenticator.",
@ -2801,9 +2801,9 @@
"authors": [
{
"name": "Dries Vints",
"role": "Maintainer",
"email": "dries.vints@gmail.com",
"homepage": "http://driesvints.com",
"role": "Maintainer"
"homepage": "http://driesvints.com"
}
],
"description": "Prologue Alerts is a package that handles global site messages.",
@ -3302,9 +3302,9 @@
"authors": [
{
"name": "Jarek Tkaczyk",
"role": "Developer",
"email": "jarek@softonsofa.com",
"homepage": "https://softonsofa.com/",
"role": "Developer"
"homepage": "https://softonsofa.com/"
}
],
"description": "Flexible Searchable, Mappable, Metable, Validation and more extensions for Laravel Eloquent ORM.",
@ -3404,9 +3404,9 @@
"authors": [
{
"name": "Jarek Tkaczyk",
"role": "Developer",
"email": "jarek@softonsofa.com",
"homepage": "http://softonsofa.com/",
"role": "Developer"
"homepage": "http://softonsofa.com/"
}
],
"description": "Laravel Eloquent hooks system.",
@ -5750,16 +5750,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v2.13.1",
"version": "v2.15.1",
"source": {
"type": "git",
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
"reference": "54814c62d5beef3ba55297b9b3186ed8b8a1b161"
"reference": "20064511ab796593a3990669eff5f5b535001f7c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/54814c62d5beef3ba55297b9b3186ed8b8a1b161",
"reference": "54814c62d5beef3ba55297b9b3186ed8b8a1b161",
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/20064511ab796593a3990669eff5f5b535001f7c",
"reference": "20064511ab796593a3990669eff5f5b535001f7c",
"shasum": ""
},
"require": {
@ -5768,7 +5768,7 @@
"doctrine/annotations": "^1.2",
"ext-json": "*",
"ext-tokenizer": "*",
"php": "^5.6 || >=7.0 <7.3",
"php": "^5.6 || ^7.0",
"php-cs-fixer/diff": "^1.3",
"symfony/console": "^3.4.17 || ^4.1.6",
"symfony/event-dispatcher": "^3.0 || ^4.0",
@ -5780,21 +5780,18 @@
"symfony/process": "^3.0 || ^4.0",
"symfony/stopwatch": "^3.0 || ^4.0"
},
"conflict": {
"hhvm": "*"
},
"require-dev": {
"johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0",
"justinrainbow/json-schema": "^5.0",
"keradus/cli-executor": "^1.1",
"keradus/cli-executor": "^1.2",
"mikey179/vfsstream": "^1.6",
"php-coveralls/php-coveralls": "^2.1",
"php-cs-fixer/accessible-object": "^1.0",
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.0.1",
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.0.1",
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.1",
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.1",
"phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1",
"phpunitgoodpractices/traits": "^1.5.1",
"symfony/phpunit-bridge": "^4.0"
"phpunitgoodpractices/traits": "^1.8",
"symfony/phpunit-bridge": "^4.3"
},
"suggest": {
"ext-mbstring": "For handling non-UTF8 characters in cache signature.",
@ -5837,7 +5834,7 @@
}
],
"description": "A tool to automatically fix PHP code style",
"time": "2018-10-21T00:32:10+00:00"
"time": "2019-06-01T10:32:12+00:00"
},
{
"name": "fzaninotto/faker",
@ -6998,8 +6995,8 @@
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
"role": "lead",
"email": "sebastian@phpunit.de"
}
],
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
@ -7271,8 +7268,8 @@
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
"role": "lead",
"email": "sebastian@phpunit.de"
}
],
"description": "The PHP Unit Testing framework.",

View file

@ -9,7 +9,7 @@ return [
| change this value if you are not maintaining your own internal versions.
*/
'version' => '0.7.14',
'version' => '0.7.15',
/*
|--------------------------------------------------------------------------

View file

@ -3,12 +3,12 @@
"meta": {
"version": "PTDL_v1"
},
"exported_at": "2019-02-27T22:23:55-05:00",
"exported_at": "2019-08-01T04:49:37-04:00",
"name": "Paper",
"author": "parker@pterodactyl.io",
"description": "High performance Spigot fork that aims to fix gameplay and mechanics inconsistencies.",
"image": "quay.io\/pterodactyl\/core:java",
"startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}",
"startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}",
"config": {
"files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"server-port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}",
"startup": "{\r\n \"done\": \")! For help, type \",\r\n \"userInteraction\": [\r\n \"Go to eula.txt for more info.\"\r\n ]\r\n}",
@ -17,7 +17,7 @@
},
"scripts": {
"installation": {
"script": "#!\/bin\/ash\r\n# Paper Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napk add --no-cache --update curl jq\r\n\r\nif [ -n \"${DL_PATH}\" ]; then\r\n echo -e \"using supplied download url\"\r\n DOWNLOAD_URL=`eval echo $(echo ${DL_PATH} | sed -e 's\/{{\/${\/g' -e 's\/}}\/}\/g')`\r\nelse\r\n VER_EXISTS=`curl -s https:\/\/papermc.io\/api\/v1\/paper | jq -r --arg VERSION $MINECRAFT_VERSION '.versions[] | IN($VERSION)' | grep true`\r\n LATEST_PAPER_VERSION=`curl -s https:\/\/papermc.io\/api\/v1\/paper | jq -r '.versions' | jq -r '.[0]'`\r\n \r\n if [ \"${VER_EXISTS}\" == \"true\" ]; then\r\n echo -e \"Version is valid. Using version ${MINECRAFT_VERSION}\"\r\n else\r\n echo -e \"Using the latest paper version\"\r\n MINECRAFT_VERSION=${LATEST_PAPER_VERSION}\r\n fi\r\n \r\n BUILD_EXISTS=`curl -s https:\/\/papermc.io\/api\/v1\/paper\/${MINECRAFT_VERSION} | jq -r --arg BUILD ${BUILD_NUMBER} '.builds.all[] | IN($BUILD)' | grep true`\r\n LATEST_PAPER_BUILD=`curl -s https:\/\/papermc.io\/api\/v1\/paper\/${MINECRAFT_VERSION} | jq -r '.builds.latest'`\r\n \r\n if [ \"${BUILD_EXISTS}\" == \"true\" ] || [ ${BUILD_NUMBER} == \"latest\" ]; then\r\n echo -e \"Build is valid. Using version ${BUILD_NUMBER}\"\r\n else\r\n echo -e \"Using the latest paper build\"\r\n BUILD_NUMBER=${LATEST_PAPER_BUILD}\r\n fi\r\n \r\n echo \"Version being downloaded\"\r\n echo -e \"MC Version: ${MINECRAFT_VERSION}\"\r\n echo -e \"Build: ${BUILD_NUMBER}\"\r\n DOWNLOAD_URL=https:\/\/papermc.io\/api\/v1\/paper\/${MINECRAFT_VERSION}\/${BUILD_NUMBER}\/download \r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"running curl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\r\n\r\ncurl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\r\n\r\necho -e \"Downloading MC server.properties\"\r\ncurl -o server.properties https:\/\/raw.githubusercontent.com\/parkervcp\/eggs\/master\/minecraft_java\/server.properties",
"script": "#!\/bin\/ash\r\n# Paper Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napk add --no-cache --update curl jq\r\n\r\nif [ -n \"${DL_PATH}\" ]; then\r\n echo -e \"using supplied download url\"\r\n DOWNLOAD_URL=`eval echo $(echo ${DL_PATH} | sed -e 's\/{{\/${\/g' -e 's\/}}\/}\/g')`\r\nelse\r\n VER_EXISTS=`curl -s https:\/\/papermc.io\/api\/v1\/paper | jq -r --arg VERSION $MINECRAFT_VERSION '.versions[] | IN($VERSION)' | grep true`\r\n LATEST_PAPER_VERSION=`curl -s https:\/\/papermc.io\/api\/v1\/paper | jq -r '.versions' | jq -r '.[0]'`\r\n \r\n if [ \"${VER_EXISTS}\" == \"true\" ]; then\r\n echo -e \"Version is valid. Using version ${MINECRAFT_VERSION}\"\r\n else\r\n echo -e \"Using the latest paper version\"\r\n MINECRAFT_VERSION=${LATEST_PAPER_VERSION}\r\n fi\r\n \r\n BUILD_EXISTS=`curl -s https:\/\/papermc.io\/api\/v1\/paper\/${MINECRAFT_VERSION} | jq -r --arg BUILD ${BUILD_NUMBER} '.builds.all[] | IN($BUILD)' | grep true`\r\n LATEST_PAPER_BUILD=`curl -s https:\/\/papermc.io\/api\/v1\/paper\/${MINECRAFT_VERSION} | jq -r '.builds.latest'`\r\n \r\n if [ \"${BUILD_EXISTS}\" == \"true\" ] || [ ${BUILD_NUMBER} == \"latest\" ]; then\r\n echo -e \"Build is valid. Using version ${BUILD_NUMBER}\"\r\n else\r\n echo -e \"Using the latest paper build\"\r\n BUILD_NUMBER=${LATEST_PAPER_BUILD}\r\n fi\r\n \r\n echo \"Version being downloaded\"\r\n echo -e \"MC Version: ${MINECRAFT_VERSION}\"\r\n echo -e \"Build: ${BUILD_NUMBER}\"\r\n DOWNLOAD_URL=https:\/\/papermc.io\/api\/v1\/paper\/${MINECRAFT_VERSION}\/${BUILD_NUMBER}\/download \r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\necho -e \"running curl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\"\r\n\r\nif [ -f ${SERVER_JARFILE} ]; then\r\n mv ${SERVER_JARFILE} ${SERVER_JARFILE}.old\r\nfi\r\n\r\ncurl -o ${SERVER_JARFILE} ${DOWNLOAD_URL}\r\n\r\nif [ ! -f server.properties ]; then\r\n echo -e \"Downloading MC server.properties\"\r\n curl -o server.properties https:\/\/raw.githubusercontent.com\/parkervcp\/eggs\/master\/minecraft_java\/server.properties\r\nfi",
"container": "alpine:3.9",
"entrypoint": "ash"
}

View file

@ -8,7 +8,7 @@
"author": "support@pterodactyl.io",
"description": "Garrys Mod, is a sandbox physics game created by Garry Newman, and developed by his company, Facepunch Studios.",
"image": "quay.io\/pterodactyl\/core:source",
"startup": ".\/srcds_run -game garrysmod -console -port {{SERVER_PORT}} +ip 0.0.0.0 +map {{SRCDS_MAP}} +gamemode {{GAMEMODE}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}} +host_workshop_collection {{WORKSHOP_ID}} +maxplayers {{MAX_PLAYERS}} -tickrate {{TICKRATE}}",
"startup": ".\/srcds_run -game garrysmod -console -port {{SERVER_PORT}} +ip 0.0.0.0 +host_workshop_collection {{WORKSHOP_ID}} +map {{SRCDS_MAP}} +gamemode {{GAMEMODE}} -strictportbind -norestart +sv_setsteamaccount {{STEAM_ACC}} +maxplayers {{MAX_PLAYERS}} -tickrate {{TICKRATE}}",
"config": {
"files": "{}",
"startup": "{\r\n \"done\": \"gameserver Steam ID\",\r\n \"userInteraction\": []\r\n}",

View file

@ -27,10 +27,10 @@
"name": "Game ID",
"description": "The ID corresponding to the game to download and run using SRCDS.",
"env_variable": "SRCDS_APPID",
"default_value": "17705",
"default_value": "237410",
"user_viewable": 1,
"user_editable": 0,
"rules": "required|regex:\/^(17705)$\/"
"rules": "required|regex:\/^(237410)$\/"
},
{
"name": "Game Name",

View file

@ -3,21 +3,21 @@
"meta": {
"version": "PTDL_v1"
},
"exported_at": "2019-05-01T16:35:15+00:00",
"exported_at": "2019-07-05T11:59:29-04:00",
"name": "Teamspeak3 Server",
"author": "support@pterodactyl.io",
"description": "VoIP software designed with security in mind, featuring crystal clear voice quality, endless customization options, and scalabilty up to thousands of simultaneous users.",
"image": "quay.io/parkervcp/pterodactyl-images:base_debian",
"startup": ".\/ts3server_minimal_runscript.sh default_voice_port={{SERVER_PORT}} query_port={{SERVER_PORT}} license_accepted=1",
"startup": "./ts3server default_voice_port={{SERVER_PORT}} query_port={{SERVER_PORT}} filetransfer_ip=0.0.0.0 filetransfer_port={{FILE_TRANSFER}} license_accepted=1",
"config": {
"files": "{}",
"startup": "{\"done\": \"listening on 0.0.0.0:\", \"userInteraction\": []}",
"logs": "{\"custom\": true, \"location\": \"logs\/ts3.log\"}",
"startup": "{\r\n \"done\": \"listening on 0.0.0.0:\",\r\n \"userInteraction\": []\r\n}",
"logs": "{\r\n \"custom\": true,\r\n \"location\": \"logs/ts3.log\"\r\n}",
"stop": "^C"
},
"scripts": {
"installation": {
"script": "#!\/bin\/ash\n# TS3 Installation Script\n#\n# Server Files: \/mnt\/server\napk update\napk add tar curl\n\ncd \/mnt\/server\n\ncurl http:\/\/dl.4players.de\/ts\/releases\/${TS_VERSION}\/teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2 | tar xj --strip-components=1",
"script": "#!/bin/ash\r\n# TS3 Installation Script\r\n#\r\n# Server Files: /mnt/server\r\napk add --no-cache tar curl jq\r\n\r\nif [ -z ${TS_VERSION} ] || [ ${TS_VERSION} == latest ]; then\r\n TS_VERSION=$(wget https://teamspeak.com/versions/server.json -qO - | jq -r '.linux.x86_64.version')\r\nfi\r\n\r\ncd /mnt/server\r\n\r\n\r\necho -e \"getting files from http://files.teamspeak-services.com/releases/server/${TS_VERSION}/teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2\"\r\ncurl http://files.teamspeak-services.com/releases/server/${TS_VERSION}/teamspeak3-server_linux_amd64-${TS_VERSION}.tar.bz2 | tar xj --strip-components=1",
"container": "alpine:3.9",
"entrypoint": "ash"
}
@ -27,10 +27,19 @@
"name": "Server Version",
"description": "The version of Teamspeak 3 to use when running the server.",
"env_variable": "TS_VERSION",
"default_value": "3.7.1",
"default_value": "latest",
"user_viewable": 1,
"user_editable": 1,
"rules": "required|regex:\/^([0-9_\\.-]{5,10})$\/"
"rules": "required|string|max:6"
},
{
"name": "File Transfer Port",
"description": "The Teamspeak file transfer port",
"env_variable": "FILE_TRANSFER",
"default_value": "30033",
"user_viewable": 1,
"user_editable": 0,
"rules": "required|integer|between:1,65535"
}
]
}

View file

@ -101,9 +101,11 @@
currentCpu = parseFloat(((info.proc.cpu.total / cpuMax) * 100).toFixed(2).toString());
}
element.find('[data-action="memory"]').html(parseInt(info.proc.memory.total / (1024 * 1024)));
element.find('[data-action="disk"]').html(parseInt(info.proc.disk.used));
element.find('[data-action="cpu"]').html(currentCpu);
} else {
element.find('[data-action="memory"]').html('--');
element.find('[data-action="disk"]').html('--');
element.find('[data-action="cpu"]').html('--');
}
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -397,8 +397,10 @@ class ActionsClass {
{
let formattedItems = "";
let i = 0;
let self = this;
$.each(selectedItems, function(key, value) {
formattedItems += ("<code>" + this.sanitizedString(value) + "</code>, ");
formattedItems += ("<code>" + self.sanitizedString(value) + "</code>, ");
i++;
return i < 5;
});
@ -411,7 +413,7 @@ class ActionsClass {
swal({
type: 'warning',
title: '',
text: 'Are you sure you want to delete the following files: ' + this.sanitizedString(formattedItems) + '?',
text: 'Are you sure you want to delete the following files: ' + formattedItems + '?',
html: true,
showCancelButton: true,
showConfirmButton: true,

View file

@ -74,9 +74,11 @@
}
element.find('[data-action="memory"]').html(parseInt(data.proc.memory.total / (1024 * 1024)));
element.find('[data-action="cpu"]').html(currentCpu);
element.find('[data-action="disk"]').html(parseInt(data.proc.disk.used));
} else {
element.find('[data-action="memory"]').html('--');
element.find('[data-action="cpu"]').html('--');
element.find('[data-action="disk"]').html('--');
}
}
}).fail(function (jqXHR) {

View file

@ -16,6 +16,7 @@ return [
'connection' => 'Connection',
'memory' => 'Memory',
'cpu' => 'CPU',
'disk' => 'Disk',
'status' => 'Status',
'search' => 'Search',
'suspended' => 'Suspended',

View file

@ -57,10 +57,10 @@
<td><a href="{{ route('admin.servers.view', $server->id) }}">{{ $server->name }}</a></td>
<td><a href="{{ route('admin.users.view', $server->owner_id) }}">{{ $server->user->username }}</a></td>
<td>{{ $server->nest->name }} ({{ $server->egg->name }})</td>
<td class="text-center"><span data-action="memory">NaN</span> / {{ $server->memory === 0 ? '∞' : $server->memory }} MB</td>
<td class="text-center">{{ $server->disk }} MB</td>
<td class="text-center"><span data-action="cpu" data-cpumax="{{ $server->cpu }}">NaN</span> %</td>
<td class="text-center" data-action="status">NaN</td>
<td class="text-center"><span data-action="memory">--</span> / {{ $server->memory === 0 ? '∞' : $server->memory }} MB</td>
<td class="text-center"><span data-action="disk">--</span> / {{ $server->disk === 0 ? '∞' : $server->disk }} MB </td>
<td class="text-center"><span data-action="cpu" data-cpumax="{{ $server->cpu }}">--</span> %</td>
<td class="text-center" data-action="status">--</td>
</tr>
@endforeach
</table>

View file

@ -67,8 +67,8 @@
<div class="form-group col-xs-12">
<label for="public" class="control-label">Allow Automatic Allocation <sup><a data-toggle="tooltip" data-placement="top" title="Allow automatic allocation to this Node?">?</a></sup></label>
<div>
<input type="radio" name="public" value="1" {{ (old('public', $node->public) === '1') ? 'checked' : '' }} id="public_1" checked> <label for="public_1" style="padding-left:5px;">Yes</label><br />
<input type="radio" name="public" value="0" {{ (old('public', $node->public) === '0') ? 'checked' : '' }} id="public_0"> <label for="public_0" style="padding-left:5px;">No</label>
<input type="radio" name="public" value="1" {{ (old('public', $node->public)) ? 'checked' : '' }} id="public_1" checked> <label for="public_1" style="padding-left:5px;">Yes</label><br />
<input type="radio" name="public" value="0" {{ (old('public', $node->public)) ? '' : 'checked' }} id="public_0"> <label for="public_0" style="padding-left:5px;">No</label>
</div>
</div>
<div class="form-group col-xs-12">

View file

@ -85,6 +85,22 @@
</div>
<p class="text-muted small">This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available.</p>
</div>
<div class="form-group">
<label for="cpu" class="control-label">OOM Killer</label>
<div>
<div class="radio radio-danger radio-inline">
<input type="radio" id="pOomKillerEnabled" value="0" name="oom_disabled" @if(!$server->oom_disabled)checked @endif>
<label for="pOomKillerEnabled">Enabled</label>
</div>
<div class="radio radio-success radio-inline">
<input type="radio" id="pOomKillerDisabled" value="1" name="oom_disabled" @if($server->oom_disabled)checked @endif>
<label for="pOomKillerDisabled">Disabled</label>
</div>
<p class="text-muted small">
Enabling OOM killer may cause server processes to exit unexpectedly.
</p>
</div>
</div>
</div>
</div>
</div>

View file

@ -111,9 +111,7 @@
@endsection
@section('footer-scripts')
{!! Theme::js('js/laroute.js?t={cache-version}') !!}
{!! Theme::js('vendor/jquery/jquery.min.js?t={cache-version}') !!}
{!! Theme::js('vendor/sweetalert/sweetalert.min.js?t={cache-version}') !!}
@parent
<script>
function saveSettings() {

View file

@ -44,6 +44,7 @@
<th>@lang('strings.connection')</th>
<th class="text-center hidden-sm hidden-xs">@lang('strings.memory')</th>
<th class="text-center hidden-sm hidden-xs">@lang('strings.cpu')</th>
<th class="text-center hidden-sm hidden-xs">@lang('strings.disk')</th>
<th class="text-center">@lang('strings.relation')</th>
<th class="text-center">@lang('strings.status')</th>
</tr>
@ -55,6 +56,7 @@
<td><code>{{ $server->getRelation('allocation')->alias }}:{{ $server->getRelation('allocation')->port }}</code></td>
<td class="text-center hidden-sm hidden-xs"><span data-action="memory">--</span> / {{ $server->memory === 0 ? '∞' : $server->memory }} MB</td>
<td class="text-center hidden-sm hidden-xs"><span data-action="cpu" data-cpumax="{{ $server->cpu }}">--</span> %</td>
<td class="text-center hidden-sm hidden-xs"><span data-action="disk">--</span> / {{ $server->disk === 0 ? '∞' : $server->disk }} MB </td>
<td class="text-center">
@if($server->user->id === Auth::user()->id)
<span class="label bg-purple">@lang('strings.owner')</span>

View file

@ -3,6 +3,8 @@
namespace Tests\Unit\Commands\Server;
use Mockery as m;
use Pterodactyl\Models\Node;
use GuzzleHttp\Psr7\Response;
use Pterodactyl\Models\Server;
use Illuminate\Validation\Factory;
use Tests\Unit\Commands\CommandTestCase;
@ -38,8 +40,13 @@ class BulkPowerActionCommandTest extends CommandTestCase
*/
public function testSendAction()
{
/** @var \Pterodactyl\Models\Server[] $servers */
$servers = factory(Server::class)->times(2)->make();
foreach ($servers as &$server) {
$server->setRelation('node', factory(Node::class)->make());
}
$this->repository->shouldReceive('getServersForPowerActionCount')
->once()
->with([], [])
@ -51,7 +58,7 @@ class BulkPowerActionCommandTest extends CommandTestCase
->andReturn($servers);
for ($i = 0; $i < count($servers); $i++) {
$this->powerRepository->shouldReceive('setServer->sendSignal')
$this->powerRepository->shouldReceive('setNode->setServer->sendSignal')
->once()
->with('kill')
->andReturnNull();
@ -70,6 +77,7 @@ class BulkPowerActionCommandTest extends CommandTestCase
public function testSendWithFilters()
{
$server = factory(Server::class)->make();
$server->setRelation('node', $node = factory(Node::class)->make());
$this->repository->shouldReceive('getServersForPowerActionCount')
->once()
@ -81,10 +89,9 @@ class BulkPowerActionCommandTest extends CommandTestCase
->with([1, 2], [3, 4])
->andReturn([$server]);
$this->powerRepository->shouldReceive('setServer->sendSignal')
->once()
->with('kill')
->andReturnNull();
$this->powerRepository->expects('setNode')->with($node)->andReturnSelf();
$this->powerRepository->expects('setServer')->with($server)->andReturnSelf();
$this->powerRepository->expects('sendSignal')->with('kill')->andReturn(new Response);
$display = $this->runCommand($this->getCommand(), [
'action' => 'kill',
@ -103,6 +110,7 @@ class BulkPowerActionCommandTest extends CommandTestCase
public function testSendWithEmptyOptions()
{
$server = factory(Server::class)->make();
$server->setRelation('node', factory(Node::class)->make());
$this->repository->shouldReceive('getServersForPowerActionCount')
->once()
@ -110,7 +118,7 @@ class BulkPowerActionCommandTest extends CommandTestCase
->andReturn(1);
$this->repository->shouldReceive('getServersForPowerAction')->once()->with([], [])->andReturn([$server]);
$this->powerRepository->shouldReceive('setServer->sendSignal')->once()->with('kill')->andReturnNull();
$this->powerRepository->shouldReceive('setNode->setServer->sendSignal')->once()->with('kill')->andReturnNull();
$display = $this->runCommand($this->getCommand(), [
'action' => 'kill',

View file

@ -48,43 +48,35 @@ class DatabasePasswordServiceTest extends TestCase
/**
* Test that a password can be updated.
*
* @dataProvider useModelDataProvider
*/
public function testPasswordIsChanged(bool $useModel)
public function testPasswordIsChanged()
{
$model = factory(Database::class)->make();
if (! $useModel) {
$this->repository->shouldReceive('find')->with(1234)->once()->andReturn($model);
}
$this->connection->expects('transaction')->with(m::on(function ($closure) {
return is_null($closure());
}));
$this->dynamic->shouldReceive('set')->with('dynamic', $model->database_host_id)->once()->andReturnNull();
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->encrypter->shouldReceive('encrypt')->with('test123')->once()->andReturn('enc123');
$this->encrypter->expects('encrypt')->with(m::on(function ($string) {
preg_match_all('/[!@+=^-]/', $string, $matches, PREG_SET_ORDER);
$this->assertTrue(count($matches) >= 2 && count($matches) <= 6, "Failed asserting that [{$string}] contains 2 to 6 special characters.");
$this->assertTrue(strlen($string) === 24, "Failed asserting that [{$string}] is 24 characters in length.");
return true;
}))->andReturn('enc123');
$this->repository->shouldReceive('withoutFreshModel')->withNoArgs()->once()->andReturnSelf();
$this->repository->shouldReceive('update')->with($model->id, ['password' => 'enc123'])->once()->andReturn(true);
$this->repository->shouldReceive('dropUser')->with($model->username, $model->remote)->once()->andReturn(true);
$this->repository->shouldReceive('createUser')->with($model->username, $model->remote, 'test123')->once()->andReturn(true);
$this->repository->shouldReceive('createUser')->with($model->username, $model->remote, m::any())->once()->andReturn(true);
$this->repository->shouldReceive('assignUserToDatabase')->with($model->database, $model->username, $model->remote)->once()->andReturn(true);
$this->repository->shouldReceive('flush')->withNoArgs()->once()->andReturn(true);
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturn(true);
$response = $this->getService()->handle($useModel ? $model : 1234, 'test123');
$response = $this->getService()->handle($model);
$this->assertNotEmpty($response);
$this->assertTrue($response);
}
/**
* Data provider to determine if a model should be passed or an int.
*
* @return array
*/
public function useModelDataProvider(): array
{
return [[false], [true]];
}
/**

View file

@ -60,18 +60,20 @@ class HostCreationServiceTest extends TestCase
{
$model = factory(DatabaseHost::class)->make();
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->encrypter->shouldReceive('encrypt')->with('test123')->once()->andReturn('enc123');
$this->repository->shouldReceive('create')->with(m::subset([
$this->connection->expects('transaction')->with(m::on(function ($closure) {
return ! is_null($closure());
}))->andReturn($model);
$this->encrypter->expects('encrypt')->with('test123')->andReturn('enc123');
$this->repository->expects('create')->with(m::subset([
'password' => 'enc123',
'username' => $model->username,
'node_id' => $model->node_id,
]))->once()->andReturn($model);
]))->andReturn($model);
$this->dynamic->shouldReceive('set')->with('dynamic', $model)->once()->andReturnNull();
$this->databaseManager->shouldReceive('connection')->with('dynamic')->once()->andReturnSelf();
$this->databaseManager->shouldReceive('select')->with('SELECT 1 FROM dual')->once()->andReturnNull();
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$this->dynamic->expects('set')->with('dynamic', $model)->andReturnNull();
$this->databaseManager->expects('connection')->with('dynamic')->andReturnSelf();
$this->databaseManager->expects('select')->with('SELECT 1 FROM dual')->andReturnNull();
$response = $this->getService()->handle([
'password' => 'test123',

View file

@ -60,14 +60,15 @@ class HostUpdateServiceTest extends TestCase
{
$model = factory(DatabaseHost::class)->make();
$this->encrypter->shouldReceive('encrypt')->with('test123')->once()->andReturn('enc123');
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->repository->shouldReceive('update')->with(1234, ['password' => 'enc123'])->once()->andReturn($model);
$this->connection->expects('transaction')->with(m::on(function ($closure) {
return ! is_null($closure());
}))->andReturn($model);
$this->dynamic->shouldReceive('set')->with('dynamic', $model)->once()->andReturnNull();
$this->databaseManager->shouldReceive('connection')->with('dynamic')->once()->andReturnSelf();
$this->databaseManager->shouldReceive('select')->with('SELECT 1 FROM dual')->once()->andReturnNull();
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$this->encrypter->expects('encrypt')->with('test123')->andReturn('enc123');
$this->repository->expects('update')->with(1234, ['password' => 'enc123'])->andReturn($model);
$this->dynamic->expects('set')->with('dynamic', $model)->andReturnNull();
$this->databaseManager->expects('connection')->with('dynamic')->andReturnSelf();
$this->databaseManager->expects('select')->with('SELECT 1 FROM dual')->andReturnNull();
$response = $this->getService()->handle(1234, ['password' => 'test123']);
$this->assertNotEmpty($response);
@ -81,13 +82,14 @@ class HostUpdateServiceTest extends TestCase
{
$model = factory(DatabaseHost::class)->make();
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->repository->shouldReceive('update')->with(1234, ['username' => 'test'])->once()->andReturn($model);
$this->connection->expects('transaction')->with(m::on(function ($closure) {
return ! is_null($closure());
}))->andReturn($model);
$this->dynamic->shouldReceive('set')->with('dynamic', $model)->once()->andReturnNull();
$this->databaseManager->shouldReceive('connection')->with('dynamic')->once()->andReturnSelf();
$this->databaseManager->shouldReceive('select')->with('SELECT 1 FROM dual')->once()->andReturnNull();
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$this->repository->expects('update')->with(1234, ['username' => 'test'])->andReturn($model);
$this->dynamic->expects('set')->with('dynamic', $model)->andReturnNull();
$this->databaseManager->expects('connection')->with('dynamic')->andReturnSelf();
$this->databaseManager->expects('select')->with('SELECT 1 FROM dual')->andReturnNull();
$response = $this->getService()->handle(1234, ['password' => '', 'username' => 'test']);
$this->assertNotEmpty($response);