├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── dynamic-servers.php ├── resources └── stubs │ └── DigitalOceanEventServiceProvider.php.stub └── src ├── DigitalOcean ├── DigitalOceanServer.php ├── DigitalOceanServerProvider.php ├── DigitalOceanServerStatus.php └── Exceptions │ ├── CannotGetDigitalOceanServerDetails.php │ └── CannotRebootServer.php ├── LaravelDynamicServersDigitalOceanServiceProvider.php ├── Listeners └── UpdateServerMeta.php ├── Providers └── DigitalOceanEventServiceProvider.php └── Skeleton.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `:package_name` will be documented in this file. 4 | 5 | ## v0.1.0 - 2022-09-12 6 | 7 | **Full Changelog**: https://github.com/sidis405/laravel-dynamic-servers-digital-ocean/commits/v0.1.0 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) :vendor_name 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Digital Ocean provider for Spatie's Dynamic Servers Package 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/sidis405/laravel-dynamic-servers-digital-ocean.svg?style=flat-square)](https://packagist.org/packages/sidis405/laravel-dynamic-servers-digital-ocean) 4 | [![GitHub Tests Action Status](https://img.shields.io/github/workflow/status/sidis405/laravel-dynamic-servers-digital-ocean/run-tests?label=tests)](https://github.com/sidis405/laravel-dynamic-servers-digital-ocean/actions?query=workflow%3Arun-tests+branch%3Amain) 5 | [![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/sidis405/laravel-dynamic-servers-digital-ocean/Fix%20PHP%20code%20style%20issues?label=code%20style)](https://github.com/sidis405/laravel-dynamic-servers-digital-ocean/actions?query=workflow%3A"Fix+PHP+code+style+issues"+branch%3Amain) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/sidis405/laravel-dynamic-servers-digital-ocean.svg?style=flat-square)](https://packagist.org/packages/sidis405/laravel-dynamic-servers-digital-ocean) 7 | 8 | This package provides a Server Provider for Spatie's Laravel Dynamic Servers Package. 9 | 10 | ## The package is under active development and not suitable for production. Please feel free to contribute. 11 | 12 | ## Installation 13 | 14 | You can install the package via composer: 15 | 16 | ```bash 17 | composer require sidis405/laravel-dynamic-servers-digital-ocean 18 | ``` 19 | 20 | Afterward make sure to publish the EventServiceProvider that comes with this package: 21 | 22 | ```bash 23 | php artisan dynamic-servers-digital-ocean:install 24 | ``` 25 | 26 | ## Usage 27 | 28 | In your config/dynamic-servers.php register the DO provider 29 | ```php 30 | return [ 31 | 'providers' => [ 32 | ... 33 | 34 | 'digital_ocean' => [ 35 | 'class' => Sidis405\LaravelDynamicServersDigitalOcean\DigitalOcean\DigitalOceanServerProvider::class, 36 | 'maximum_servers_in_account' => 20, 37 | 'options' => [ 38 | 'token' => env('DIGITAL_OCEAN_TOKEN'), 39 | 'region' => env('DIGITAL_OCEAN_REGION'), 40 | 'size' => env('DIGITAL_OCEAN_SIZE'), 41 | 'image' => env('DIGITAL_OCEAN_IMAGE'), 42 | 'vpc_uuid' => env('DIGITAL_OCEAN_VPC_UUID'), 43 | ], 44 | ], 45 | ] 46 | ]; 47 | ``` 48 | 49 | In your app/Providers/DynamicServersProvider.php register a new server type using the Digital Ocean provider 50 | ```php 51 | public function register() 52 | { 53 | .... 54 | 55 | $doServer = ServerType::new('do') 56 | ->provider('digital_ocean') 57 | ->configuration(function(Server $server) { 58 | return [ 59 | 'name' => Str::slug($server->name), 60 | 61 | "image" => $server->option('image'), 62 | "vpc_uuid" => $server->option('vpc_uuid'), 63 | "region" => $server->option('region'), 64 | "size" => $server->option('size'), 65 | 66 | "ipv6" => false, 67 | "backups" => false, 68 | "monitoring" => true, 69 | ]; 70 | }); 71 | 72 | DynamicServers::registerServerType($doServer); 73 | } 74 | ``` 75 | 76 | ## Events 77 | After the base package's `CreateServerJob` is executed, a new job, `VerifyServerStartedJob` will be dispatched and will check every 20 seconds to make sure that the provider eventually marks the Droplet as running. 78 | 79 | After it ensures it runs, no other attempt is made to fetch again the server meta. 80 | 81 | Considering that DigitalOcean will return the ip address of a droplet only after it has been full created we need to fetch once more the droplet meta. 82 | 83 | For this, we will use the base package's event 'ServerRunningEvent'. 84 | 85 | This package, publishes a `App\Providers\DigitalOceanEventServiceProvider` in your project. 86 | 87 | By default there is a single listener, configured and it will fetch again the Droplet's meta after the base package has ensured that it is running. 88 | 89 | ```php 90 | protected $listen = [ 91 | ServerRunningEvent::class => [ 92 | UpdateServerMeta::class, 93 | ], 94 | ]; 95 | ``` 96 | 97 | You may customise the listener, disable it or replace it with a your own. 98 | 99 | ## Testing 100 | 101 | ```bash 102 | composer test 103 | ``` 104 | 105 | ## Changelog 106 | 107 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 108 | 109 | ## Contributing 110 | 111 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 112 | 113 | ## Security Vulnerabilities 114 | 115 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 116 | 117 | ## Credits 118 | 119 | - [Sidrit Trandafili](https://github.com/sidis405) 120 | - [All Contributors](../../contributors) 121 | 122 | ## License 123 | 124 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 125 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sidis405/laravel-dynamic-servers-digital-ocean", 3 | "description": "Digital Ocean provider for Spatie's Laravel Dynamic Servers pagkage", 4 | "keywords": [ 5 | "sidis405", 6 | "laravel", 7 | "laravel-dynamic-servers", 8 | "digital-ocean", 9 | "laravel-dynamic-servers-digital-ocean" 10 | ], 11 | "homepage": "https://github.com/sidis405/laravel-dynamic-servers-digital-ocean", 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Sidrit Trandafili", 16 | "email": "forge405@gmail.com", 17 | "role": "Developer" 18 | } 19 | ], 20 | "require": { 21 | "php": "^8.1", 22 | "spatie/laravel-dynamic-servers": "^0.0.5", 23 | "spatie/laravel-package-tools": "^1.13.0", 24 | "illuminate/contracts": "^9.0" 25 | }, 26 | "require-dev": { 27 | "laravel/pint": "^1.0", 28 | "nunomaduro/collision": "^6.0", 29 | "nunomaduro/larastan": "^2.0.1", 30 | "orchestra/testbench": "^7.0", 31 | "pestphp/pest": "^1.21", 32 | "pestphp/pest-plugin-laravel": "^1.1", 33 | "phpstan/extension-installer": "^1.1", 34 | "phpstan/phpstan-deprecation-rules": "^1.0", 35 | "phpstan/phpstan-phpunit": "^1.0", 36 | "phpunit/phpunit": "^9.5", 37 | "spatie/laravel-ray": "^1.26" 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "Sidis405\\LaravelDynamicServersDigitalOcean\\": "src", 42 | "Sidis405\\LaravelDynamicServersDigitalOcean\\Database\\Factories\\": "database/factories" 43 | } 44 | }, 45 | "autoload-dev": { 46 | "psr-4": { 47 | "Sidis405\\LaravelDynamicServersDigitalOcean\\Tests\\": "tests" 48 | } 49 | }, 50 | "scripts": { 51 | "analyse": "vendor/bin/phpstan analyse", 52 | "test": "vendor/bin/pest", 53 | "test-coverage": "vendor/bin/pest --coverage", 54 | "format": "vendor/bin/pint" 55 | }, 56 | "config": { 57 | "sort-packages": true, 58 | "allow-plugins": { 59 | "pestphp/pest-plugin": true, 60 | "phpstan/extension-installer": true 61 | } 62 | }, 63 | "extra": { 64 | "laravel": { 65 | "providers": [ 66 | "Sidis405\\LaravelDynamicServersDigitalOcean\\LaravelDynamicServersDigitalOceanServiceProvider" 67 | ] 68 | } 69 | }, 70 | "minimum-stability": "dev", 71 | "prefer-stable": true 72 | } 73 | -------------------------------------------------------------------------------- /config/dynamic-servers.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'digital_ocean' => [ 6 | 'class' => Sidis405\LaravelDynamicServersDigitalOcean\DigitalOcean\DigitalOceanServerProvider::class, 7 | 'maximum_servers_in_account' => 20, 8 | 'options' => [ 9 | 'token' => env('DIGITAL_OCEAN_TOKEN'), 10 | 'region' => env('DIGITAL_OCEAN_REGION'), 11 | 'size' => env('DIGITAL_OCEAN_SIZE'), 12 | 'image' => env('DIGITAL_OCEAN_IMAGE'), 13 | 'vpc_uuid' => env('DIGITAL_OCEAN_VPC_UUID'), 14 | ], 15 | ], 16 | ], 17 | ]; 18 | -------------------------------------------------------------------------------- /resources/stubs/DigitalOceanEventServiceProvider.php.stub: -------------------------------------------------------------------------------- 1 | [ 13 | UpdateServerMeta::class, 14 | ], 15 | ]; 16 | 17 | /** 18 | * Register any events for your application. 19 | * 20 | * @return void 21 | */ 22 | public function boot() 23 | { 24 | parent::boot(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/DigitalOcean/DigitalOceanServer.php: -------------------------------------------------------------------------------- 1 | where('type', 'public') 19 | ->first()['ip_address'] ?? ''; 20 | 21 | return new self( 22 | $payload['id'], 23 | $payload['name'], 24 | $ip, 25 | DigitalOceanServerStatus::from($payload['status']), 26 | ); 27 | } 28 | 29 | public function toArray(): array 30 | { 31 | return [ 32 | 'id' => $this->uuid, 33 | 'title' => $this->title, 34 | 'ip' => $this->ip, 35 | 'status' => $this->status->value, 36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/DigitalOcean/DigitalOceanServerProvider.php: -------------------------------------------------------------------------------- 1 | request()->post('/droplets', $this->server->configuration); 17 | 18 | if (! $response->successful()) { 19 | throw new Exception($response->json('message')); 20 | } 21 | 22 | $digitalOceanServer = DigitalOceanServer::fromApiPayload($response->json('droplet')); 23 | 24 | $this->server->addMeta('server_properties', $digitalOceanServer->toArray()); 25 | } 26 | 27 | public function updateServerMeta(): void 28 | { 29 | $digitalOceanServer = $this->getServer(); 30 | 31 | $this->server->addMeta('server_properties', $digitalOceanServer->toArray()); 32 | } 33 | 34 | public function hasStarted(): bool 35 | { 36 | $digitalOceanServer = $this->getServer(); 37 | 38 | return $digitalOceanServer->status === DigitalOceanServerStatus::Active; 39 | } 40 | 41 | public function stopServer(): void 42 | { 43 | $serverId = $this->server->meta('server_properties.id'); 44 | 45 | $response = $this->request()->post("/droplets/{$serverId}/actions", [ 46 | 'type' => 'shutdown', 47 | ]); 48 | 49 | if (! $response->successful()) { 50 | throw new Exception($response->json('message')); 51 | } 52 | } 53 | 54 | public function hasStopped(): bool 55 | { 56 | $digitalOceanServer = $this->getServer(); 57 | 58 | return $digitalOceanServer->status === DigitalOceanServerStatus::Off; 59 | } 60 | 61 | public function deleteServer(): void 62 | { 63 | $serverId = $this->server->meta('server_properties.id'); 64 | 65 | $response = $this->request()->delete("/droplets/{$serverId}"); 66 | 67 | if (! $response->successful()) { 68 | throw new Exception($response->json('message', 'Could not delete server')); 69 | } 70 | } 71 | 72 | public function hasBeenDeleted(): bool 73 | { 74 | $serverId = $this->server->meta('server_properties.id'); 75 | 76 | $response = $this->request()->get("/droplets/{$serverId}"); 77 | 78 | return $response->failed(); 79 | } 80 | 81 | public function getServer(): DigitalOceanServer 82 | { 83 | $serverId = $this->server->meta('server_properties.id'); 84 | 85 | $response = $this->request()->get("/droplets/{$serverId}"); 86 | 87 | if (! $response->successful()) { 88 | throw CannotGetDigitalOceanServerDetails::make($this->server, $response); 89 | } 90 | 91 | return DigitalOceanServer::fromApiPayload($response->json('droplet')); 92 | } 93 | 94 | public function rebootServer(): void 95 | { 96 | $serverId = $this->server->meta('server_properties.id'); 97 | 98 | $response = $this->request()->post("/droplets/{$serverId}/actions", [ 99 | 'type' => 'reboot', 100 | ]); 101 | 102 | if (! $response->successful()) { 103 | throw CannotRebootServer::make($this->server, $response); 104 | } 105 | } 106 | 107 | public function currentServerCount(): int 108 | { 109 | $response = $this->request()->get('droplets'); 110 | 111 | if (! $response->successful()) { 112 | throw CannotGetDigitalOceanServerDetails::make($this->server, $response); 113 | } 114 | 115 | return count($response->json('droplets')); 116 | } 117 | 118 | protected function request(): PendingRequest 119 | { 120 | return Http::withToken( 121 | $this->server->option('token') 122 | )->baseUrl('https://api.digitalocean.com/v2'); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/DigitalOcean/DigitalOceanServerStatus.php: -------------------------------------------------------------------------------- 1 | json('error.error_message'); 16 | 17 | return new self("Could not refresh details for DigitalOcean server id {$server->id}: $reason"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/DigitalOcean/Exceptions/CannotRebootServer.php: -------------------------------------------------------------------------------- 1 | json('error.error_message'); 16 | 17 | return new self("Could not reboot server for DigitalOcean server id {$server->id}: $reason"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/LaravelDynamicServersDigitalOceanServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('laravel-dynamic-servers-digital-ocean') 15 | ->publishesServiceProvider('DigitalOceanEventServiceProvider') 16 | ->hasInstallCommand(function (InstallCommand $command) { 17 | $command 18 | ->copyAndRegisterServiceProviderInApp() 19 | ->endWith(function (InstallCommand $installCommand) { 20 | $installCommand->line(''); 21 | $installCommand->info("We've added app\Providers\DigitalOceanEventServiceProvider to your project."); 22 | $installCommand->info('Feel free to customize it to your needs.'); 23 | }); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Listeners/UpdateServerMeta.php: -------------------------------------------------------------------------------- 1 | server->addMeta( 12 | 'server_properties', 13 | $event->server 14 | ->serverProvider() 15 | ->getServer() 16 | ->toArray() 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Providers/DigitalOceanEventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 13 | UpdateServerMeta::class, 14 | ], 15 | ]; 16 | 17 | /** 18 | * Register any events for your application. 19 | * 20 | * @return void 21 | */ 22 | public function boot() 23 | { 24 | parent::boot(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Skeleton.php: -------------------------------------------------------------------------------- 1 |