S3 AWS SDK for JavaでMinIOのバケットとオブジェクトを操作する

はじめに

前回の記事でMiniIOを動かしてみたのですが、Javaのクライアントをいくつか試して見たいと思って、この記事ではAamazon SDKを使ってみようと思います。
前回同様MiniIOはDockerを用いて立てます。

使ってみる

環境

今回のプログラムを動かす環境は以下の通りです。

$ java --version
openjdk 15 2020-09-15
OpenJDK Runtime Environment (build 15+36-1562)
OpenJDK 64-Bit Server VM (build 15+36-1562, mixed mode, sharing)

$ mvn -v
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 15, vendor: Oracle Corporation, runtime: /home/yuya-hirooka/.sdkman/candidates/java/15-open
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-65-generic", arch: "amd64", family: "unix"

$ lsb_release -a
LSB Version:    core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.2 LTS
Release:    20.04
Codename:   focal

$ uname -srvmpio
Linux 5.4.0-65-generic #73-Ubuntu SMP Mon Jan 18 17:25:17 UTC 2021 x86_64 x86_64 x86_64 GNU/Lin

MiniIOを立てて置く

前述の通り、MiniIOをDockerで起動します。
以下のコマンドを実行します。

$ docker run  -p 9000:9000 minio/minio server /data

 You are running an older version of MinIO released 2 days ago 
 Update: Run `mc admin update` 


Endpoint: http://172.17.0.2:9000  http://127.0.0.1:9000 

Browser Access:
   http://172.17.0.2:9000  http://127.0.0.1:9000

Object API (Amazon S3 compatible):
   Go:         https://docs.min.io/docs/golang-client-quickstart-guide
   Java:       https://docs.min.io/docs/java-client-quickstart-guide
   Python:     https://docs.min.io/docs/python-client-quickstart-guide
   JavaScript: https://docs.min.io/docs/javascript-client-quickstart-guide
   .NET:       https://docs.min.io/docs/dotnet-client-quickstart-guide
Detected default credentials 'minioadmin:minioadmin', please change the credentials immediately using 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD' 

localhost:9000にアクセスし、ログインすると(アクセスキーとシークレットキーは両方共minioadmin)以下のUIが開かれます。

f:id:yuya_hirooka:20210216202307p:plain

プロジェクトを作成する

適当にMavenプロジェクトを作成し、Pomに以下の依存を追加します。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>aws-s3</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>15</maven.compiler.source>
        <maven.compiler.target>15</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-s3</artifactId>
        </dependency>
        <dependency>
            <groupId>jakarta.xml.bind</groupId>
            <artifactId>jakarta.xml.bind-api</artifactId>
            <version>2.3.2</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.amazonaws</groupId>
                <artifactId>aws-java-sdk-bom</artifactId>
                <version>1.11.327</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

AWS SDKにはBOMが用意されており、複数モジュール導入する場合でも互換性のあるバージョンを利用することができます。上記のように、dependencyManagementにaws-java-sdk-bom`を追加してやることでこのBOMを利用することができます。
最新のバージョンのBOMはここ からご確認ください。
AWSSDKは利用するモジュールを個別に指定出来るため今回はs3のモジュールだけを依存に追加しています。
もし、すべてのモジュールを依存に追加したい場合はBOMを利用せずに以下の依存をPomに追加することで行なうことができます。

<dependencies>
  <dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk</artifactId>
    <version>1.11.327</version>
  </dependency>
</dependencies>

AWS SDKはJava7以上で利用することが可能です。環境のところでも示しましたが、今回はJava15でSDKクラアントを試してみようと思います。

Java9以降に関しては、JAXBも入れる必要があるようなので依存に追加しています。

クレデンシャル情報をセットする

AWS 認証情報の設定には以下のような方法があります。

  • 環境変数(AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY)として設定する

  • 認証情報の明示的な指定を行なう

BasicAWSCredentials awsCreds = new BasicAWSCredentials("access_key_id", "secret_key_id");
AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
                        .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
                        .build();
  • ~/.aws/credentials(WindowsC:\Users\USERNAME\.aws\credentials)に以下のようなクレデンシャルファイルを置く
[default]
aws_access_key_id = your_access_key_id
aws_secret_access_key = your_secret_access_key

今回は認証情報の明示的な指定して利用するやり方を試してみようと思います。

バケットを操作する

ここまでで諸々の設定は終わったので早速SDKを用いてバケットを操作していきたいと思います。
今回は以下のようなイメージファイルを対象にして、バケットにアップロードダウンロード削除等々をやってみたいと思います。

f:id:yuya_hirooka:20210212112530p:plain

S3クライアントの作成

まずは、ASKのS3クライアントを作成します。

    public static void main(String[] args) {
       AmazonS3ClientBuildern endpointConfiguration = new AwsClientBuilder.EndpointConfiguration("http://localhost:9000/", Regions.DEFAULT_REGION.name());
        AWSCredentials credentials = new BasicAWSCredentials("minioadmin", "minioadmin");

        final AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
                .withEndpointConfiguration(endpointConfiguration)
                .withPathStyleAccessEnabled(true)
                .withCredentials(new AWSStaticCredentialsProvider(credentials))
                .build();
    }

S3のクライアントを作成するためにはAmazonS3ClientBuilderを利用します。withRegion()メソッドにリージョンを指定しますが、今回はローカルのMinIOにつなぎに行きたいため、AwsClientBuilder.EndpointConfigurationを利用して、エンドポイントの設定を行います(ちょっと、ドキュメント見つけられ無かったんですが、ここで指定するリージョンに関してはなんでも良さそうな感じがします)。
クレデンシャルの情報はBasicAWSCredentialsで作成することができます。MinIOのデフォルトのACCESS_KEY_ID/SECRET_ACCESS_KEYはそれぞれminioadminなのでその設定を行っています。
これで、クライアントの作成ができました。今後は特に明示時なければここでインスタンス化したクライアント

バケットを作成してオブジェクトをアップロードする

それでは、まずはバケットを作成してオブジェクトをアップロードしてみます。
bucket01という名前のバケットを作成して前述の画像をアップロードを行なうには以下のようなコードを書きます。

public class Main {

        //クライアント作成は省略

        String bucketName = "bucket01";

        if (!s3Client.doesBucketExistV2(bucketName)) {
            Bucket bucket = s3Client.createBucket(bucketName);
        }

        s3Client.putObject(bucketName, "henoheno.png", new File("/path/to/imageDir/henoheno.png"));
    }
}

エラーハンドリングなどは省略していますがS3クライアントのcreateBucketputObjectメソッドを用いることで、バケットの作成と画像のアップロードが行えます。
コードを実行するとMinIOのUIから作成されたバケットとアップロードされた画像を確認することができます。

f:id:yuya_hirooka:20210216230838p:plain

バケットにポリシーを適用する

ポリシーを作成して、クレデンシャル無しで画像をダウンロード出来るようにしてみます。
現状ではcURL等を用いた画像のダウンロードを行おうとすると、以下のように403の認可エラーが返ってきます。

$ curl http://localhost:9000/bucket01/henoheno.png  -v
*   Trying 127.0.0.1:9000...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 9000 (#0)
> GET /bucket01/henoheno.png HTTP/1.1
> Host: localhost:9000
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
< Accept-Ranges: bytes
< Content-Length: 303
< Content-Security-Policy: block-all-mixed-content
< Content-Type: application/xml
< Server: MinIO
< Vary: Origin
< X-Amz-Request-Id: 16643FECF975B0A1
< X-Xss-Protection: 1; mode=block
< Date: Tue, 16 Feb 2021 14:14:13 GMT
< 
<?xml version="1.0" encoding="UTF-8"?>
* Connection #0 to host localhost left intact
<Error><Code>AccessDenied</Code><Message>Access Denied.</Message><Key>henoheno.png</Key><BucketName>bucket01</BucketName><Resource>/bucket01/henoheno.png</Resource><RequestId>16643FECF975B0A1</RequestId><HostId>7f44b0c0-57d7-4511-ac50-bc78b99478aa</HostId></Error>y

AWS SDK を用いてPolicyを設定する場合S3クライアントのsetBucketPolicyメソッドを利用すると実行行えます。この場合、以下の2つの方法が取れます。

  • JSON形式のポリシーのテキスト文字列を指定する
  • Policy クラスを使用してポリシーを構築する

今回は後者のPolicyクラスを用いるやり方を試してみようと思います。
具体的には以下のようなコードを記述します。

    public static void main(String[] args) {
        //クライアントの作成とバケット&オブジェクトの作成省略

        String bucketName = "bucket01";

        Statement statement = new Statement(Statement.Effect.Allow)
                .withPrincipals(Principal.AllUsers)
                .withActions(S3Actions.GetObject)
                .withResources(new Resource(
                        "arn:aws:s3:::" + bucketName + "/*"));

        s3Client.setBucketPolicy(bucketName, new Policy().withStatements(statement).toJson());
    }

上記のコードで作成されているポリシーはすべてのユーザに対して、Getのリクエストを許可しています。

再度、cURLで画像をダウンロードすると今度はきちんと画像がダウンロード出来ることが確認できます。

$ curl http://localhost:9000/bucket01/henoheno.png  -v --output henoheno.ping
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 127.0.0.1:9000...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 9000 (#0)
> GET /bucket01/henoheno.png HTTP/1.1
> Host: localhost:9000
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Content-Length: 18968
< Content-Security-Policy: block-all-mixed-content
< Content-Type: image/png
< ETag: "6524a1d06fc27ef8a835abd32bb7c34c"
< Last-Modified: Tue, 16 Feb 2021 14:07:52 GMT
< Server: MinIO
< Vary: Origin
< X-Amz-Request-Id: 166440FB47A5C193
< X-Xss-Protection: 1; mode=block
< Date: Tue, 16 Feb 2021 14:33:34 GMT
< 
{ [18968 bytes data]
100 18968  100 18968    0     0  3704k      0 --:--:-- --:--:-- --:--:-- 3704k
* Connection #0 to host localhost left intact

$ ls
henoheno.ping

参考資料