RustとRusotoを使ってさくらのクラウドのオブジェクトストレージAPIでオブジェクトを取得してみた

2021-06-26 追記

2021-05-07 に A New AWS SDK for Rust – Alpha Launch | AWS Developer Tools Blog という記事が出て、今後は awslabs/aws-sdk-rust に移行していくそうです。

今日確認したところでは rusoto/rusoto: AWS SDK for Rust の README に Rusoto is in maintenance mode. と書かれていました。 一方、 awslabs/aws-sdk-rust のほうは Please Note: The SDK is currently released as an alpha and is intended strictly for feedback purposes only. Do not use this SDK for production workloads. と書かれていました。

はじめに

オブジェクトストレージ | さくらのクラウド ドキュメントオブジェクトストレージ サービス基本情報 を読んで Rust と rusoto/rusoto: AWS SDK for Rust で API 経由でオブジェクトを1つ取得してボディーを標準出力に出力する(テキスト形式のオブジェクトを想定しています)サンプルを書いてみたのでメモです。

この記事は個人的に検証してみただけのメモであって公式情報ではないです。

なお オブジェクトストレージ サービス基本情報 に書かれているように2021年3月31日まではオープンベータです。

オブジェクトストレージのAPI によると「オブジェクトストレージはAmazon S3互換APIを備えており」とのことなので今回は Rust と Rusoto で試してみました。

プロジェクトは hnakamur/rusoto-s3-example: さくらのクラウドのオブジェクトストレージからRustとRusotoでオブジェクト取得するサンプル に置きました。

Rusoto について

AWS’ sponsorship of the Rust project | AWS Open Source Blog に Rust で AWS を使うならコミュニティーで開発されている AWS SDK である Rusoto を使うと良いようなことが書かれていたのでこれを試すことにしました(他にもライブラリーあるかもしれませんが調べてないです)。

rusoto - crates.io: Rust Package Registry を見ると This crate for Rusoto is deprecated. と書いてあってあれってなったんですが、 リンクされている Release Rusoto 0.25.0 を見ると rusoto_corerusoto_credential などの複数のクレートに分割されたという話でした。これは2017年と結構前で、今この記事を書いている2021-02-11時点では Release Rusoto 0.46.0 · rusoto/rusoto が最新です。

rusoto_coreCargo.toml[dependencies] を確認すると以下のように tokio 1.0 に対応していました。良いですね。

https://github.com/rusoto/rusoto/blob/rusoto-v0.46.0/rusoto/core/Cargo.toml#L38

tokio = { version = "1.0", features = ["time", "io-util"] }

オブジェクト取得の例を書くための参考情報

Example of reading from rusoto_s3::GetObjectOutput.body asynchronously? · Issue #1352 · rusoto/rusoto というイシューによるとサンプルコードはないけどテストを見ると良いとコメントがあったのでテストコードを見ました。

また amazon s3 - How to save a file downloaded from S3 with Rusoto to my hard drive? - Stack Overflow もかなり参考になりました。

さくらのクラウドのオブジェクストストレージ用に変えた箇所

rusoto_core::Region のドキュメントの AWS-compatible services に書かれているように Region::Custom を使ってリージョン名とエンドポイントの URL を指定すれば OK でした。

リージョン名は オブジェクトストレージのAPI | さくらのクラウド ドキュメント には説明が見当たらないですが、とりあえず us-east-1 を指定してみたら大丈夫でした。が、あくまで今回行けたというだけで今後もこれで大丈夫かは不明です。

エンドポイント URL は今回のサンプルでは https://s3.isk01.sakurastorage.jp としました。 オブジェクトストレージ サービス基本情報サイトの作成 を見るとサイト作成後にエンドポイントが作成されるとのことなので、本番運用する場合はコントロールパネルで確認したエンドポイントURLを指定するような方式にすべきです。 今回はサンプルなのでハードコーディングしています。

オブジェクトのキー指定の際は先頭に / はつけない

例えば /index.html なら index.html とします。

サンプルコード

src/main.rs は以下の通りです。

use anyhow::{Context, Result};
use rusoto_core::credential::ProfileProvider;
use rusoto_core::Region;
use rusoto_s3::{GetObjectOutput, GetObjectRequest, S3Client, S3};
use tokio::io;

const SAKURA_OBJECT_STORAGE_ENDPOINT: &str = "https://s3.isk01.sakurastorage.jp";

fn sakura_object_storage_region(endpoint: String) -> Region {
    Region::Custom {
        name: String::from(Region::UsEast1.name()),
        endpoint: endpoint,
    }
}

async fn get_object(region: Region, bucket_name: String, key: String) -> Result<GetObjectOutput> {
    let s3 = S3Client::new_with(
        rusoto_core::request::HttpClient::new()?,
        ProfileProvider::new()?,
        region,
    );
    let get_obj_req = GetObjectRequest {
        bucket: bucket_name,
        key: key,
        ..Default::default()
    };
    s3.get_object(get_obj_req)
        .await
        .context("get object from object storage")
}

#[tokio::main]
async fn main() {
    let region = sakura_object_storage_region(String::from(SAKURA_OBJECT_STORAGE_ENDPOINT));
    let bucket_name = String::from("gh-action-test");
    let key = String::from("index.html");
    let mut output = get_object(region, bucket_name, key).await.unwrap();
    let body = output.body.take().expect("The object has no body");
    let mut body = body.into_async_read();
    io::copy(&mut body, &mut tokio::io::stdout()).await.unwrap();
}