diff --git a/src/Storage/Device/MinIO.php b/src/Storage/Device/MinIO.php new file mode 100644 index 00000000..e54502d5 --- /dev/null +++ b/src/Storage/Device/MinIO.php @@ -0,0 +1,118 @@ +protocol = $protocol; + $this->headers['host'] = $host; + } + + /** + * Get list of objects in the given path. + * + * @param string $path + * @return array + * + * @throws \Exception + */ + private function listObjects($prefix = '', $maxKeys = 1000, $continuationToken = '') + { + $uri = '/'.$this->getRoot(); + $this->headers['content-type'] = 'text/plain'; + $this->headers['content-md5'] = \base64_encode(md5('', true)); + + $parameters = [ + 'list-type' => 2, + 'prefix' => $prefix, + 'max-keys' => $maxKeys, + ]; + if (! empty($continuationToken)) { + $parameters['continuation-token'] = $continuationToken; + } + $response = parent::call(self::METHOD_GET, $uri, '', $parameters); + + return $response->body; + } + + /** + * Delete files in given path, path must be a directory. Return true on success and false on failure. + * + * @param string $path + * @return bool + * + * @throws \Exception + */ + public function deletePath(string $path): bool + { + $uri = '/'.$this->getRoot(); + $continuationToken = ''; + do { + $objects = $this->listObjects($path, continuationToken: $continuationToken); + $count = (int) ($objects['KeyCount'] ?? 1); + if ($count < 1) { + break; + } + $continuationToken = $objects['NextContinuationToken'] ?? ''; + $body = ''; + + if ($count > 1) { + foreach ($objects['Contents'] as $object) { + $body .= "{$object['Key']}"; + } + } else { + $body .= "{$objects['Contents']['Key']}"; + } + $body .= 'true'; + $body .= ''; + $this->amzHeaders['x-amz-content-sha256'] = \hash('sha256', $body); + $this->headers['content-md5'] = \base64_encode(md5($body, true)); + parent::call(self::METHOD_POST, $uri, $body, ['delete' => '']); + } while (! empty($continuationToken)); + + return true; + } + + /** + * @return string + */ + public function getName(): string + { + return 'MinIO Object Storage'; + } + + /** + * @return string + */ + public function getDescription(): string + { + return 'MinIO Object Storage'; + } +} diff --git a/src/Storage/Device/S3.php b/src/Storage/Device/S3.php index 7b6161c7..0a1575e5 100644 --- a/src/Storage/Device/S3.php +++ b/src/Storage/Device/S3.php @@ -136,6 +136,11 @@ class S3 extends Device */ protected array $amzHeaders; + /** + * @var string + */ + protected string $protocol = 'https'; + /** * S3 Constructor * @@ -724,10 +729,10 @@ private function getSignatureV4(string $method, string $uri, array $parameters = * * @throws \Exception */ - private function call(string $method, string $uri, string $data = '', array $parameters = []) + protected function call(string $method, string $uri, string $data = '', array $parameters = []) { $uri = $this->getAbsolutePath($uri); - $url = 'https://'.$this->headers['host'].$uri.'?'.\http_build_query($parameters, '', '&', PHP_QUERY_RFC3986); + $url = $this->protocol.'://'.$this->headers['host'].$uri.'?'.\http_build_query($parameters, '', '&', PHP_QUERY_RFC3986); $response = new \stdClass; $response->body = ''; $response->headers = []; diff --git a/src/Storage/Storage.php b/src/Storage/Storage.php index cabd18c8..8dbee829 100644 --- a/src/Storage/Storage.php +++ b/src/Storage/Storage.php @@ -21,6 +21,8 @@ class Storage const DEVICE_LINODE = 'linode'; + const DEVICE_MINIO = 'minio'; + /** * Devices. * diff --git a/tests/Storage/Device/MinIOTest.php b/tests/Storage/Device/MinIOTest.php new file mode 100644 index 00000000..9eaed9f0 --- /dev/null +++ b/tests/Storage/Device/MinIOTest.php @@ -0,0 +1,37 @@ +root = 'minio-test-bucket'; + $key = $_SERVER['MINIO_ACCESS_KEY'] ?? ''; + $secret = $_SERVER['MINIO_SECRET'] ?? ''; + $protocol = $_SERVER['MINIO_PROTOCOL'] ?? ''; + $host = $_SERVER['MINIO_HOST'] ?? ''; + $region = $_SERVER['MINIO_REGION'] ?? ''; + $bucket = 'minio-test-bucket'; + + $this->object = new MinIO($this->root, $key, $secret, $protocol, $host, $bucket, $region); + } + + protected function getAdapterName(): string + { + return 'MinIO Object Storage'; + } + + protected function getAdapterType(): string + { + return $this->object->getType(); + } + + protected function getAdapterDescription(): string + { + return 'MinIO Object Storage'; + } +}