From fbffb9441f815b71a55a983a18cacbfb653f4ec6 Mon Sep 17 00:00:00 2001 From: Ben Sherred Date: Mon, 11 Dec 2023 19:46:09 +0000 Subject: [PATCH] feat: add command for creating S3 bucket --- composer.json | 1 + docker-compose.yml | 24 +++++++ src/Commands/StorageCreateBucketCommand.php | 80 +++++++++++++++++++++ src/DevToolsServiceProvider.php | 11 ++- tests/StorageCreateBucketCommandTest.php | 53 ++++++++++++++ tests/TestCase.php | 28 ++++++++ 6 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 docker-compose.yml create mode 100644 src/Commands/StorageCreateBucketCommand.php create mode 100644 tests/StorageCreateBucketCommandTest.php diff --git a/composer.json b/composer.json index cb0c368..a7e7594 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,7 @@ "illuminate/support": "^10.0" }, "require-dev": { + "aws/aws-sdk-php": "^3.293", "laravel/pint": "^1.10", "orchestra/testbench": "^8.0", "pestphp/pest": "^2.6", diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..07ca5fd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +services: + minio: + image: 'minio/minio:latest' + ports: + - '9000:9000' + - '8900:8900' + environment: + MINIO_ROOT_USER: minio + MINIO_ROOT_PASSWORD: password + volumes: + - 'minio:/data/minio' + command: 'minio server /data/minio --console-address ":8900"' + healthcheck: + test: + - CMD + - curl + - '-f' + - 'http://localhost:9000/minio/health/live' + retries: 3 + timeout: 5s + +volumes: + minio: + driver: local diff --git a/src/Commands/StorageCreateBucketCommand.php b/src/Commands/StorageCreateBucketCommand.php new file mode 100644 index 0000000..39b0b25 --- /dev/null +++ b/src/Commands/StorageCreateBucketCommand.php @@ -0,0 +1,80 @@ +components->error(string: 'You must set the S3 bucket name before running this command!'); + + return self::FAILURE; + } + + $this->client = $this->client(); + + if ($this->bucketExists(name: $bucketName)) { + $this->components->error(string: "S3 bucket [{$bucketName}] already exists!"); + + return self::FAILURE; + } + + $this->createBucket(name: $bucketName); + + $this->components->info(string: "S3 bucket [{$bucketName}] created successfully."); + + return self::SUCCESS; + } + + protected function client(): S3Client + { + return new S3Client([ + 'credentials' => [ + 'key' => config(key: 'filesystems.disks.s3.key'), + 'secret' => config(key: 'filesystems.disks.s3.secret'), + ], + 'region' => config(key: 'filesystems.disks.s3.region'), + 'version' => 'latest', + 'endpoint' => config(key: 'filesystems.disks.s3.endpoint'), + 'use_path_style_endpoint' => config(key: 'filesystems.disks.s3.use_path_style_endpoint'), + ]); + } + + protected function bucketExists(string $name): bool + { + $result = $this->client->listBuckets(); + + /** @var array $buckets */ + $buckets = $result['Buckets']; + + return array_search(needle: $name, haystack: array_column(array: $buckets, column_key: 'Name')) !== false; + } + + protected function createBucket(string $name): void + { + $this->client->createBucket([ + 'Bucket' => $name, + 'ACL' => 'public-read', + ]); + + $this->client->putBucketPolicy([ + 'Bucket' => $name, + 'Policy' => '{"Version":"2012-10-17","Statement":[{"Sid":"AddPerm","Effect":"Allow","Principal":"*","Action":["s3:GetObject"],"Resource":["arn:aws:s3:::' . $name . '/*"]}]}', + ]); + } +} diff --git a/src/DevToolsServiceProvider.php b/src/DevToolsServiceProvider.php index a20392b..65baa55 100644 --- a/src/DevToolsServiceProvider.php +++ b/src/DevToolsServiceProvider.php @@ -8,7 +8,14 @@ class DevToolsServiceProvider extends ServiceProvider { - public function register(): void {} + public function boot(): void + { + if (!$this->app->runningInConsole()) { + return; + } - public function boot(): void {} + $this->commands([ + Commands\StorageCreateBucketCommand::class, + ]); + } } diff --git a/tests/StorageCreateBucketCommandTest.php b/tests/StorageCreateBucketCommandTest.php new file mode 100644 index 0000000..1dd681f --- /dev/null +++ b/tests/StorageCreateBucketCommandTest.php @@ -0,0 +1,53 @@ +s3Client()->deleteBucket(['Bucket' => $bucket]); +}); + +it(description: 'creates an S3 bucket', closure: function (): void { + expect($this->s3Client()->listBuckets()['Buckets']) + ->toBeEmpty(); + + $this->artisan(command: 'storage:create-bucket') + ->expectsOutputToContain(string: 'S3 bucket [devtools] created successfully.') + ->assertSuccessful(); + + expect($this->s3Client()->listBuckets()['Buckets']) + ->toHaveCount(count: 1); +}); + +it(description: 'shows an error if the bucket already exists', closure: function (): void { + expect($this->s3Client()->listBuckets()['Buckets']) + ->toBeEmpty(); + + $this->artisan(command: 'storage:create-bucket') + ->expectsOutputToContain(string: 'S3 bucket [devtools] created successfully.') + ->assertSuccessful(); + + expect($this->s3Client()->listBuckets()['Buckets']) + ->toHaveCount(count: 1); + + $this->artisan(command: 'storage:create-bucket') + ->expectsOutputToContain(string: 'S3 bucket [devtools] already exists!') + ->assertFailed(); +}); + +it(description: 'shows an error if the bucket name is not set', closure: function (): void { + config()->set(key: 'filesystems.disks.s3.bucket', value: null); + + $this->artisan(command: 'storage:create-bucket') + ->expectsOutputToContain(string: 'You must set the S3 bucket name before running this command!') + ->assertFailed(); + + expect($this->s3Client()->listBuckets()['Buckets']) + ->toBeEmpty(); +}); diff --git a/tests/TestCase.php b/tests/TestCase.php index 366c795..fa40e5b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,6 +4,8 @@ namespace RedExplosion\DevTools\Tests; +use Aws\S3\S3Client; +use Illuminate\Config\Repository; use Orchestra\Testbench\TestCase as Orchestra; use RedExplosion\DevTools\DevToolsServiceProvider; @@ -15,4 +17,30 @@ protected function getPackageProviders($app): array DevtoolsServiceProvider::class, ]; } + + protected function defineEnvironment($app): void + { + tap($app['config'], function (Repository $config): void { + $config->set(key: 'filesystems.disks.s3.key', value: 'minio'); + $config->set(key: 'filesystems.disks.s3.secret', value: 'password'); + $config->set(key: 'filesystems.disks.s3.region', value: 'us-east-1'); + $config->set(key: 'filesystems.disks.s3.bucket', value: 'devtools'); + $config->set(key: 'filesystems.disks.s3.endpoint', value: 'http://localhost:9000'); + $config->set(key: 'filesystems.disks.s3.use_path_style_endpoint', value: true); + }); + } + + public function s3Client(): S3Client + { + return new S3Client([ + 'credentials' => [ + 'key' => config(key: 'filesystems.disks.s3.key'), + 'secret' => config(key: 'filesystems.disks.s3.secret'), + ], + 'region' => config(key: 'filesystems.disks.s3.region'), + 'version' => 'latest', + 'endpoint' => config(key: 'filesystems.disks.s3.endpoint'), + 'use_path_style_endpoint' => config(key: 'filesystems.disks.s3.use_path_style_endpoint'), + ]); + } }