diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..3784ce2 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Outl1ne + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..926ffaf --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# Laravel Google Cloud Queues + +[![Latest Version on Packagist](https://img.shields.io/packagist/v/outl1ne/laravel-google-cloud-queues.svg?style=flat-square)](https://packagist.org/packages/outl1ne/laravel-google-cloud-queues) +[![Total Downloads](https://img.shields.io/packagist/dt/outl1ne/laravel-google-cloud-queues.svg?style=flat-square)](https://packagist.org/packages/outl1ne/laravel-google-cloud-queues) + +Manage Google Cloud queues from Laravel app. Updates queues on Google Cloud depending on the configuration file in a Laravel app. + +## Installation + +Install the package in a Laravel project via Composer: + +``` +composer require outl1ne/laravel-google-cloud-queues +``` + +## Credits + +- [Allan Tatter](https://github.com/allantatter) + +## License + +Laravel Google Cloud Queues is open-sourced software licensed under the [MIT license](LICENSE.md). \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b8cc579 --- /dev/null +++ b/composer.json @@ -0,0 +1,31 @@ +{ + "name": "outl1ne/laravel-google-cloud-queues", + "description": "Manage Google Cloud queues from Laravel app.", + "keywords": [ + "laravel", + "outl1ne" + ], + "license": "MIT", + "require": { + "laravel/framework": "^6.0 || ^7.0 || ^8.0 || ^9.0", + "phpseclib/phpseclib": "~2.0", + "google/cloud-tasks": "^1.10" + }, + "autoload": { + "psr-4": { + "Outl1ne\\LaravelGoogleCloudQueues\\": "src/" + } + }, + "extra": { + "laravel": { + "providers": [ + "Outl1ne\\LaravelGoogleCloudQueues\\ServiceProvider" + ] + } + }, + "config": { + "sort-packages": true + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/config/google-cloud-queues.php b/config/google-cloud-queues.php new file mode 100644 index 0000000..d88e0ef --- /dev/null +++ b/config/google-cloud-queues.php @@ -0,0 +1,42 @@ + env('GOOGLE_CLOUD_PROJECT_ID', 'your-project-id'), + 'key_file_path' => env('GOOGLE_CLOUD_KEY_FILE', null), // optional: /path/to/service-account.json + 'location' => env('GOOGLE_CLOUD_QUEUES_LOCATION', 'europe-west6'), + 'queue_prefix' => env('GOOGLE_CLOUD_QUEUES_PREFIX', 'google-cloud-queue--'), + + 'rate_limits' => [ + 'max_burst_size' => 10, + 'max_concurrent_dispatches' => 10, + 'max_displatches_per_second' => 10, + ], + 'retry_config' => [ + 'max_attempts' => 5, + 'max_retry_duration' => 0, + 'min_backoff' => 1, + 'max_backoff' => 900, + 'max_doublings' => 16, + ], + + 'queues' => [ + [ + 'name' => 'queue1', + // 'rate_limits' => [ + // 'max_burst_size' => 10, + // 'max_concurrent_dispatches' => 75, + // 'max_displatches_per_second' => 20, + // ], + // 'retry_config' => [ + // 'max_attempts' => 5, + // 'max_retry_duration' => 0, + // 'min_backoff' => 1, + // 'max_backoff' => 3700, + // 'max_doublings' => 16, + // ], + ], + [ + 'name' => 'queue2', + ], + ], +]; \ No newline at end of file diff --git a/src/Client.php b/src/Client.php new file mode 100644 index 0000000..11f9873 --- /dev/null +++ b/src/Client.php @@ -0,0 +1,71 @@ +client = $client; + $this->config = $config; + $this->locationName = $this->client::locationName($this->config['project_id'], $this->config['location']); + } + + public function getQueues() + { + $pagedListResponse = $this->client->listQueues($this->locationName); + $queues = []; + + foreach ($pagedListResponse->iteratePages() as $page) { + foreach ($page as $queue) { + $queues[] = $queue; + } + } + + return $queues; + } + + public function createQueue($queueName) + { + $queue = new Queue([ + 'name' => $this->client::queueName($this->config['project_id'], $this->config['location'], $queueName), + ]); + $this->client->createQueue($this->locationName, $queue); + } + + public function updateQueue($queueName, $options) + { + $queue = new Queue; + $queue->setName($this->client::queueName($this->config['project_id'], $this->config['location'], $queueName)); + + $rateLimits = new RateLimits; + $rateLimits->setMaxBurstSize($options['rate_limits']['max_burst_size']); + $rateLimits->setMaxConcurrentDispatches($options['rate_limits']['max_concurrent_dispatches']); + $rateLimits->setMaxDispatchesPerSecond($options['rate_limits']['max_displatches_per_second']); + $queue->setRateLimits($rateLimits); + + $retryConfig = new RetryConfig; + $retryConfig->setMaxAttempts($options['retry_config']['max_attempts']); + $retryConfig->setMaxRetryDuration((new Duration())->setSeconds($options['retry_config']['max_retry_duration'])); + $retryConfig->setMinBackoff((new Duration())->setSeconds($options['retry_config']['min_backoff'])); + $retryConfig->setMaxBackoff((new Duration())->setSeconds($options['retry_config']['max_backoff'])); + $retryConfig->setMaxDoublings($options['retry_config']['max_doublings']); + $queue->setRetryConfig($retryConfig); + + $this->client->updateQueue($queue); + } +} \ No newline at end of file diff --git a/src/Commands/QueueSync.php b/src/Commands/QueueSync.php new file mode 100644 index 0000000..7a8abf0 --- /dev/null +++ b/src/Commands/QueueSync.php @@ -0,0 +1,19 @@ +info('Starting to update queues in Google Cloud.'); + (new Queues)->sync(); + $this->info('Queue sync done.'); + } +} diff --git a/src/Queue.php b/src/Queue.php new file mode 100644 index 0000000..3402da7 --- /dev/null +++ b/src/Queue.php @@ -0,0 +1,35 @@ +queue = $this->parse($queueConfig); + $this->config = $config; + } + + protected function parse($queueConfig) + { + return $queueConfig; + } + + public function getFinalName() + { + return $this->config['queue_prefix']. $this->queue['name']; + } + + public function getQueueConfig() + { + return [ + 'rate_limits' => array_merge($this->config['rate_limits'] ?? [], $this->queue['rate_limits'] ?? []), + 'retry_config' => array_merge($this->config['retry_config'] ?? [], $this->queue['retry_config'] ?? []), + ]; + } +} \ No newline at end of file diff --git a/src/Queues.php b/src/Queues.php new file mode 100644 index 0000000..0b03c0c --- /dev/null +++ b/src/Queues.php @@ -0,0 +1,76 @@ +client = new Client(config('google-cloud-queues'), new CloudTasksClient()); + $this->queues = $this->parseQueues(config('google-cloud-queues'), config('google-cloud-queues.queues')); + } + + protected function parseQueues($config, $queuesConfig) + { + return collect($queuesConfig)->map(fn($queueConfig) => new Queue($queueConfig, $config)); + } + + public function sync() + { + $googleCloudQueues = $this->client->getQueues(); + + $this->createMissingQueues($googleCloudQueues); + $this->updateQueues(); + } + + protected function createMissingQueues($googleCloudQueues) + { + $missingQueues = $this->getMissingQueueNames( + $this->queues, + $this->getExistingQueueNames($googleCloudQueues), + ); + + foreach ($missingQueues as $missingQueue) { + $this->client->createQueue($missingQueue->getFinalName()); + } + } + + protected function updateQueues() + { + foreach ($this->queues as $queue) { + $this->updateQueue($queue); + } + } + + protected function updateQueue($queue) + { + $this->client->updateQueue($queue->getFinalName(), $queue->getQueueConfig()); + } + + protected function getExistingQueueNames($queues) + { + $googleCloudQueueNames = []; + + foreach ($queues as $queue) { + $googleCloudQueueNames[] = end(explode('/', $queue->getName())); + } + + return $googleCloudQueueNames; + } + + protected function getMissingQueueNames($configuredQueues, $existingQueueNames) + { + return $configuredQueues + ->filter(fn($configuredQueue) => !in_array($configuredQueue->getFinalName(), $existingQueueNames)); + } +} \ No newline at end of file diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php new file mode 100644 index 0000000..aab3bee --- /dev/null +++ b/src/ServiceProvider.php @@ -0,0 +1,37 @@ +mergeConfigFrom(__DIR__ . '/../config/google-cloud-queues.php', 'google-cloud-queues'); + + $this->commands([ + Commands\QueueSync::class + ]); + } + + /** + * Perform post-registration booting of services. + * + * @return void + */ + public function boot() + { + if ($this->app->runningInConsole()) { + // Publish config + $this->publishes([ + __DIR__ . '/../config/' => config_path(), + ], 'config'); + } + } +} \ No newline at end of file