├── .gitignore ├── src ├── ServiceProvider.php └── Console │ └── Commands │ └── MultiRegionDeploy.php ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.idea 3 | composer.lock 4 | 5 | -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 18 | $this->commands(MultiRegionDeploy::class); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thebytelab/vapor-multi-region-deploy", 3 | "description": "An artisan command to assist deploying your Laravel Vapor app to multiple AWS regions", 4 | "keywords": [ 5 | "laravel", 6 | "vapor" 7 | ], 8 | "authors": [ 9 | { 10 | "name": "Josh Stevenson-Woods", 11 | "email": "josh@bytelab.solutions" 12 | } 13 | ], 14 | "license": "MIT", 15 | "minimum-stability": "dev", 16 | "prefer-stable": true, 17 | "require": { 18 | "php": "^7.2|^8.0", 19 | "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", 20 | "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0", 21 | "symfony/yaml": "^4.2|^5.0|^6.0", 22 | "symfony/process": "^4.2|^5.0|^6.0" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "TheByteLab\\VaporMultiRegionDeploy\\": "src/" 27 | } 28 | }, 29 | "extra": { 30 | "laravel": { 31 | "providers": [ 32 | "TheByteLab\\VaporMultiRegionDeploy\\ServiceProvider" 33 | ] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Vapor: Multi-Region Deploy 2 | 3 | --- 4 | Provides an artisan command to assist deploying your Laravel Vapor app to multiple AWS regions. 5 | 6 | ## Installation 7 | 8 | ```shell 9 | composer require thebytelab/vapor-multi-region-deploy 10 | ``` 11 | 12 | ## Setup & Usage 13 | 14 | The `vapor:multi-region:deploy` command assumes you have a directory in your project root called `vapor` which contains 15 | all of the vapor.yml manifests following a naming pattern similar to the examples below: 16 | - us-east.vapor.yml 17 | - test.vapor.yml 18 | - node1.vapor.yml 19 | 20 | The steps to getting started: 21 | 1. If you haven't got one already, create a `vapor` directory in your project root. 22 | 2. Move any vapor files to this new folder and rename them to something that makes sense following the pattern above. 23 | 3. Run `php artisan vapor:multi-region:deploy` to deploy your app to multiple Vapor projects or regions. 24 | 25 | ## Advanced usage 26 | 27 | The following options can be used to modify default behaviour, some options are inherited from the `vapor deploy` 28 | command: 29 | 30 | ### `--bin` (string) 31 | 32 | Relative location of the laravel/vapor-cli executable. Defaults to `vendor/bin/vapor` in the project root. 33 | 34 | Example usage: `php artisan vapor:multi-region:deploy --bin=/usr/local/bin/vapor` 35 | 36 | ### `--vapors` (string) 37 | 38 | Relative location to the folder containing the *.vapor.yml files to use. Defaults to looking for the `vapor` folder in 39 | the project root. 40 | 41 | Example usage: `php artisan vapor:multi-region:deploy --vapors=/build/vapor` 42 | 43 | ### `--commit` (string) 44 | 45 | The commit hash that is being deployed. 46 | 47 | Example usage: `php artisan vapor:multi-region:deploy --commit=57566c1419cdacf00ff00f781b62fac670d7aee3` 48 | 49 | ### `--message` (string) 50 | 51 | The message for the commit that is being deployed. 52 | 53 | Example usage: `php artsian vapor:multi-region:deploy --message="Added a new feature"` 54 | 55 | ### `--without-waiting` 56 | 57 | Deploy without waiting for progress. May help speed up deployments to multiple Vapor projects or regions. 58 | 59 | Example usage: `php artisan vapor:multi-region:deploy --without-waiting` 60 | 61 | ### `--fresh-assets` 62 | 63 | Upload a fresh copy of all assets. 64 | 65 | Example usage: `php artisan vapor:multi-region:deploy --fresh-assets` -------------------------------------------------------------------------------- /src/Console/Commands/MultiRegionDeploy.php: -------------------------------------------------------------------------------- 1 | makeError('Function "base_path" is required, try running "composer install"'); 43 | } 44 | $path = base_path('vapor'); 45 | if ($this->hasOption('vapors') && !is_null($this->option('vapors'))) { 46 | $path = $this->option('vapors'); 47 | } 48 | if (!File::exists($path)) { 49 | return $this->makeError('Vapors folder does not exist'); 50 | } 51 | $files = $this->collectVaporFiles($path); 52 | if ($files->isEmpty()) { 53 | return $this->makeError('No vapor files found, make sure they end in ".vapor.yml"'); 54 | } 55 | $bin = base_path('vendor/bin/vapor'); 56 | if ($this->hasOption('bin') && !is_null($this->option('bin'))) { 57 | $bin = $this->option('bin'); 58 | } 59 | if (!File::exists($bin)) { 60 | return $this->makeError('Vapor executable (--bin) does not exist'); 61 | } 62 | 63 | $environment = $this->argument('environment'); 64 | 65 | // Check that the specified environment to deploy to exists within all 66 | // of the vapor files. 67 | foreach ($files as $vaporFile) { 68 | $vaporContent = Yaml::parse($vaporFile->getContents()); 69 | if (!isset($vaporContent['environments'][$environment])) { 70 | return $this->makeError('"' . $vaporFile->getFilename() . '" does not contain the "' . $environment . '" environment'); 71 | } 72 | } 73 | 74 | $command = [$bin, 'deploy', $environment]; 75 | if ($this->option('commit')) { 76 | $command[] = '--commit=' . $this->option('commit'); 77 | } 78 | if ($this->option('message')) { 79 | $command[] = '--message' . $this->option('message'); 80 | } 81 | if ($this->option('without-waiting')) { 82 | $command[] = '--without-waiting'; 83 | } 84 | if ($this->option('fresh-assets')) { 85 | $command[] = '--fresh-assets'; 86 | } 87 | 88 | // Run the deployment for each Vapor file, one after the other. 89 | foreach ($files as $vaporFile) { 90 | $this->info('Starting deployment with manifest: ' . $vaporFile->getFilename()); 91 | $this->newLine(); 92 | 93 | $process = new Process(array_merge($command, ['--manifest=' . $vaporFile->getPathname()])); 94 | $process->setTimeout(null); 95 | $process->run(function ($type, $buffer) { 96 | echo $buffer; 97 | }); 98 | 99 | $this->newLine(); 100 | 101 | if (!$process->isSuccessful()) { 102 | $this->error('Deployment unsuccessful with manifest: ' . $vaporFile->getFilename()); 103 | } 104 | } 105 | } 106 | 107 | /** 108 | * Return error string and exit code. 109 | * 110 | * @param string $error 111 | * @return int 112 | */ 113 | protected function makeError(string $error) 114 | { 115 | $this->error($error); 116 | return 1; 117 | } 118 | 119 | /** 120 | * Create a new collection of Vapor files from the provided folder. 121 | * 122 | * @param string $path 123 | * @return \Illuminate\Support\Collection 124 | */ 125 | protected function collectVaporFiles(string $path) 126 | { 127 | return collect(File::files($path))->filter(function ($value, $key) { 128 | return Str::endsWith($value->getFilename(), '.vapor.yml'); 129 | }); 130 | } 131 | } 132 | --------------------------------------------------------------------------------