🦀

AWS SDK for Rust (Developer Preview) を触ってみた

2021/12/05に公開

はじめに

<2023-11-28 追記>
AWS SDK for Rust は GA しました。What's new はこちらでリリースブログはこちらです。以下の内容は GA 前の情報なのでこれから使う方は最新情報をあたるようにして下さい。
<追記おわり>

この記事は Rust Advent Calendar 2021 の 5 日目の記事です。

AWS re: Invent 2021 Werner Vogeles Keynote で AWS SDK for Rust の Developer Preview が発表されました。what's new のリンクはこちらで GitHub のリポジトリはこちらです。早速試していきます。
※ Developer Preview なので本番導入は控えておいた方がいいでしょう。

実際にやってみた

やること

今コンテナの勉強中なのでコンテナに関係することを試してみます。具体的には ECR からコンテナイメージの詳細情報を取得する batch-get-image を Rust から実行してみます。なぜこれにしたかというと CLI で実行した場合 Output の例にもあるように imageManifest が文字列のまま表示されるので、改行して表示したいなと思ったからです。また私は Rust に明るくないので難しい処理をいきなりするのは大変だろうと考えたのもあります。

環境構築

README にも書いていますが Rust のバージョンは 1.54 以上が必要です。昨日別の記事で紹介したように私は開発する時 VSCode Remote Containers を使っているので、ここでも Rust のコンテナイメージを使うことにします。Dockerfile はこんな感じです。

FROM rust:1.55.0
RUN rustup component add rust-src rls rust-analysis rustfmt

コンテナの中で VSCode を開いて cargo new します。Getting Started をみたところ ECR と Tokio が必要そうなので Cargo.toml を編集します。

[package]
name = "my-first-rust-sdk"
version = "0.1.0"
authors = []
edition = "2018"

[dependencies]
aws-config = "0.2.0"
aws-sdk-ecr = "0.2.0"
tokio = { version = "1", features = ["full"] }

examples を覗く

examples に各 AWS サービスの API を呼ぶ例が載っています。ECR の例 はこんな感じですね。

use aws_config::meta::region::RegionProviderChain;
use aws_sdk_ecr::Region;
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
struct Opt {
    /// The region
    #[structopt(short, long)]
    region: Option<String>,

    #[structopt(long)]
    repository: String,

    #[structopt(short, long)]
    verbose: bool,
}
#[tokio::main]
async fn main() -> Result<(), aws_sdk_ecr::Error> {
    let Opt {
        region,
        repository,
        verbose,
    } = Opt::from_args();
    if verbose {
        tracing_subscriber::fmt::init();
    }
    let region_provider = RegionProviderChain::first_try(region.map(Region::new))
        .or_default_provider()
        .or_else(Region::new("us-west-2"));
    let shared_config = aws_config::from_env().region(region_provider).load().await;
    let client = aws_sdk_ecr::Client::new(&shared_config);
    let rsp = client
        .list_images()
        .repository_name(&repository)
        .send()
        .await?;
    let images = rsp.image_ids.unwrap_or_default();
    println!("found {} images", images.len());
    for image in images {
        println!(
            "image: {}:{}",
            image.image_tag.unwrap(),
            image.image_digest.unwrap()
        );
    }
    Ok(())
}

この例は list-images を実行しています。structopt は調べてみると Rust のコマンドラインパーサみたいですね。リージョンはデフォルトのプロバイダチェーンからとってきてなければ us-west-2 になるみたいです。今回は us-east-1 に hello というテスト用の ECR リポジトリを用意したのでそれを使います。

クライアントの作成

とりあえず触ってみることにするのでクライアントはこんな感じにします。

let shared_config = aws_config::load_from_env().await;
let client = aws_sdk_ecr::Client::new(&shared_config);

引数の渡し方

batch-get-image の引数に何が必要か見てみます。 repository-nameimage-ids が必要なようです。

次に Rust SDK での BatchGetImage のドキュメントをみてみます。さっきみた example の感じ的には fn repository_name(self, inp: impl Into<String>)fn image_ids(self, inp: impl Into<ImageIdentifier>) を使えば良さそうです。ところがここで impl Into<ImageIdentifier> ってなんなんだろうと詰まってしまいました。

ImageIdentifier のページをみるとこの構造体には non_exhaustive attribute がついているので普通に struct を宣言することができません。

// こんな感じで宣言できない
let image-identifier = aws_sdk_ecr::model::ImageIdentifier { image_tag: "latest".to_string() }

しばらく悩んでたんですが気分を変えて S3 の example をみてどうすればいいかわかりました。builder を使えば良さそうです。Rust には Builder というデザインパターンがあることを初めて知りました。

ImageIdentifier の Builder を作ってから image_tag を指定します。今回 ECR には world という名前のタグがついたイメージを保存してありますので image_ids に以下のように書けます。

let rsp = client
    .batch_get_image()
    .repository_name("hello")
    .image_ids(
        aws_sdk_ecr::model::ImageIdentifier::builder()
            .set_image_tag(Some("world".to_string()))
            .build(),
    )
    .send()
    .await?;

マニフェストを表示するまで

あとはレスポンスからイメージマニフェストだけ取り出せばいいです。最終的にこんな感じのコードにしました。

#[tokio::main]
pub async fn get_image_manifest() -> Result<String, aws_sdk_ecr::Error> {
    let shared_config = aws_config::load_from_env().await;
    let client = aws_sdk_ecr::Client::new(&shared_config);
    let rsp = client
        .batch_get_image()
        .repository_name("hello")
        .image_ids(
            aws_sdk_ecr::model::ImageIdentifier::builder()
                .set_image_tag(Some("world".to_string()))
                .build(),
        )
        .send()
        .await?;
    let image = &rsp.images.unwrap_or_default()[0];
    let manifest = image.image_manifest.clone().unwrap();
    Ok(manifest)
}

fn main() {
    let manifest = get_image_manifest();
    match manifest {
        Ok(m) => println!("{}", m),
        Err(e) => println!("{:?}", e),
    }
}

これによりこんな感じで改行されたマニフェストを表示できます。

{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
   "config": {
      "mediaType": "application/vnd.docker.container.image.v1+json",
      "size": 6144,
      "digest": "sha256:23ec2b3c5d992269b1d5257f8c9dc1c479a6272661f7af9e6ffe6d92f0c0c7a7"
   },
   "layers": [
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 26712436,
         "digest": "sha256:ad3e435fd20eac564bc5199d427f4ee1703faa28f75c285b27adacb62d34c44b"
      },
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 832,
         "digest": "sha256:32ab836c5190871415440d5c00f3c8841f126f70e1559632401d65f325ebf7bf"
      },
   ]
}

おわりに

Rust の SDK がどうこうというよりシンプルに Rust の文法や概念をよくわかってないなときづいたので、いまは Rust の本を読み直してます。

Discussion