Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package example
import com.azure.storage.blob.BlobServiceClient
import example.azure.BlobServiceClientBuilderCustomizer
import io.micronaut.context.ApplicationContext
import spock.lang.PendingFeature
import spock.lang.Specification

import static io.micronaut.objectstorage.azure.AzureBlobStorageConfiguration.PREFIX

class BlobServiceClientBuilderCustomizerSpec extends Specification {

@PendingFeature
void "it can customize the Azure client"() {
given:
ApplicationContext ctx = ApplicationContext.run((PREFIX + '.default.endpoint'): "https://127.0.0.1:10000/devstoreaccount1")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2017-2023 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.objectstorage.aws;

import io.micronaut.objectstorage.InputStreamMapper;
import io.micronaut.objectstorage.ObjectStorageException;
import io.micronaut.objectstorage.ObjectStorageOperations;
import io.micronaut.objectstorage.bucket.BucketOperations;
import jakarta.inject.Singleton;
import software.amazon.awssdk.awscore.exception.AwsServiceException;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.Bucket;
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
import software.amazon.awssdk.services.s3.model.DeleteBucketRequest;
import software.amazon.awssdk.services.s3.model.DeleteObjectResponse;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;

import java.util.Set;
import java.util.stream.Collectors;

/**
* AWS bucket operations.
*
* @since 2.2.0
* @author Jonas Konrad
*/
@Singleton
public class AwsS3BucketOperations implements BucketOperations<
PutObjectRequest.Builder, PutObjectResponse, DeleteObjectResponse> {
private final S3Client s3Client;
private final InputStreamMapper inputStreamMapper;

public AwsS3BucketOperations(S3Client s3Client, InputStreamMapper inputStreamMapper) {
this.s3Client = s3Client;
this.inputStreamMapper = inputStreamMapper;
}

@Override
public void createBucket(String name) {
try {
s3Client.createBucket(CreateBucketRequest.builder()
.bucket(name)
.build());
} catch (AwsServiceException | SdkClientException e) {
String msg = String.format("Error when trying to create a bucket with name [%s] on Amazon S3", name);
throw new ObjectStorageException(msg, e);
}
}

@Override
public void deleteBucket(String name) {
try {
s3Client.deleteBucket(DeleteBucketRequest.builder()
.bucket(name)
.build());
} catch (AwsServiceException | SdkClientException e) {
String msg = String.format("Error when trying to delete a bucket with name [%s] on Amazon S3", name);
throw new ObjectStorageException(msg, e);
}
}

@Override
public Set<String> listBuckets() {
try {
return s3Client.listBuckets().buckets().stream()
.map(Bucket::name)
.collect(Collectors.toSet());
} catch (AwsServiceException | SdkClientException e) {
throw new ObjectStorageException("Error when trying to list buckets on Amazon S3", e);
}
}

@Override
public ObjectStorageOperations<PutObjectRequest.Builder, PutObjectResponse, DeleteObjectResponse> storageForBucket(String bucket) {
AwsS3Configuration cfg = new AwsS3Configuration("");
cfg.setBucket(bucket);
return new AwsS3Operations(cfg, s3Client, inputStreamMapper);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.micronaut.objectstorage.aws

import io.micronaut.objectstorage.BucketOperationsSpecification
import io.micronaut.objectstorage.bucket.BucketOperations
import io.micronaut.test.support.TestPropertyProvider
import jakarta.inject.Inject

abstract class AbstractAwsS3BucketSpec extends BucketOperationsSpecification implements TestPropertyProvider {
@Inject
AwsS3BucketOperations awsBucketOperations

@Override
Map<String, String> getProperties() {
[:]
}

@Override
BucketOperations<?, ?, ?> getBucketOperations() {
return awsBucketOperations
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.micronaut.objectstorage.aws

import io.micronaut.test.extensions.spock.annotation.MicronautTest
import spock.lang.Requires

@MicronautTest
@Requires({ env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY && env.AWS_REGION })
class AwsS3BucketCloudSpec extends AbstractAwsS3BucketSpec {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.micronaut.objectstorage.aws

import io.micronaut.test.extensions.spock.annotation.MicronautTest
import org.testcontainers.containers.localstack.LocalStackContainer
import org.testcontainers.utility.DockerImageName
import spock.lang.AutoCleanup
import spock.lang.IgnoreIf
import spock.lang.Shared

import static org.testcontainers.containers.localstack.LocalStackContainer.Service.S3

@MicronautTest
@IgnoreIf({ env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY && env.AWS_REGION })
class AwsS3BucketLocalStackSpec extends AbstractAwsS3BucketSpec {
@Shared
@AutoCleanup
public LocalStackContainer localstack = new LocalStackContainer(DockerImageName.parse('localstack/localstack:1.3.1'))
.withServices(S3)

@Override
Map<String, String> getProperties() {
localstack.start()
super.getProperties() + [
'aws.accessKeyId' : localstack.accessKey,
'aws.secretKey' : localstack.secretKey,
'aws.region' : localstack.region,
'aws.services.s3.endpoint-override': localstack.getEndpointOverride(S3)
] as Map
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2017-2023 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.objectstorage.azure;

import com.azure.core.http.rest.Response;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.models.BlobContainerItem;
import com.azure.storage.blob.models.BlockBlobItem;
import com.azure.storage.blob.options.BlobParallelUploadOptions;
import io.micronaut.context.annotation.EachBean;
import io.micronaut.objectstorage.ObjectStorageOperations;
import io.micronaut.objectstorage.bucket.BucketOperations;

import java.util.Set;
import java.util.stream.Collectors;

/**
* Azure bucket operations.
*
* @since 2.2.0
* @author Jonas Konrad
*/
@EachBean(BlobServiceClient.class)
public class AzureBlobBucketOperations
implements BucketOperations<BlobParallelUploadOptions, BlockBlobItem, Response<Void>> {

private final BlobServiceClient blobServiceClient;

public AzureBlobBucketOperations(BlobServiceClient blobServiceClient) {
this.blobServiceClient = blobServiceClient;
}

@Override
public void createBucket(String name) {
blobServiceClient.createBlobContainer(name);
}

@Override
public void deleteBucket(String name) {
blobServiceClient.deleteBlobContainer(name);
}

@Override
public Set<String> listBuckets() {
return blobServiceClient.listBlobContainers().stream()
.map(BlobContainerItem::getName)
.collect(Collectors.toSet());
}

@Override
public ObjectStorageOperations<BlobParallelUploadOptions, BlockBlobItem, Response<Void>> storageForBucket(String bucket) {
return new AzureBlobStorageOperations(blobServiceClient.getBlobContainerClient(bucket));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2017-2022 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.objectstorage.azure;

import io.micronaut.context.annotation.EachProperty;
import io.micronaut.context.annotation.Parameter;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.objectstorage.configuration.AbstractObjectStorageConfiguration;
import io.micronaut.objectstorage.configuration.ObjectStorageConfiguration;

/**
* Azure endpoint configuration properties. This is only relevant if you want to use
* {@link io.micronaut.objectstorage.bucket.BucketOperations}.
*
* @since 2.2.0
* @author Jonas Konrad
*/
@EachProperty(AzureBlobStorageEndpointConfiguration.PREFIX)
public class AzureBlobStorageEndpointConfiguration extends AbstractObjectStorageConfiguration {

public static final String NAME = "azure-bucket-operations";

public static final String PREFIX = ObjectStorageConfiguration.PREFIX + '.' + NAME;

@NonNull
private String endpoint;

public AzureBlobStorageEndpointConfiguration(@Parameter String name) {
super(name);
}

/**
* The blob service endpoint, in the format of https://{accountName}.blob.core.windows.net.
*
* @return the endpoint.
*/
@NonNull
public String getEndpoint() {
return endpoint;
}

/**
* @param endpoint The blob service endpoint to set, in the format of https://{accountName}.blob.core.windows.net.
*/
public void setEndpoint(@NonNull String endpoint) {
this.endpoint = endpoint;
}

/**
* Whether to enable or disable this object storage.
* @since 2.0.2
*/
@Override
public boolean isEnabled() {
return this.enabled;
}
}
Loading