mazyu36の日記

某SIer所属のクラウドエンジニアのブログ

CDKコントリビュート時に立ちはだかるRosetta (jsii-rosetta) について

CDKコントリビュートネタです。

CDKのREADMEのコードスニペットjsii-rosetta (以降Rosetta) でコンパイルされ、他言語に変換されています。 このコードスニペットですが適当に書くと、Pull Request起票時のCIにおいてコンパイルに失敗しエラーになります。

私自身つい最近まで雰囲気で書いており、トライアンドエラーで修正していました。

ただいい加減仕組みを把握しておいたほうが良いかと思い、コントリビュートガイドを見て理解したことをまとめておきます。

目次

Rosettaの概要

READMEに記載のコードスニペットRosettaにより、TypeScript -> 他言語に変換されます。

例えば上記スライドに記載のコードスニペット(TypeScript)は、以下のようにCDKのドキュメントとして掲載されています。

引用元: Provisioning clusters

Pythonバージョンは上記をもとにRosettaによりコンパイル・自動変換され以下のように、掲載されています。

引用元: Provisioning clusters

このコードスニペットRosettaによる処理はCDKコントリビュートのPull Request起票時のCIにも行われます。そのため不適切な記載の場合、コンパイルに失敗してCIが落ちます。

PRのレビューを受けるためには、CIを成功させる必要がありますが、このRosettaにより阻止されることが往々にしてあります(コードスニペットで誤った記載であっても、IDEでは検知が難しくミスに気づきにくい)。

Rosettaのチェックを一発で通すのが、CDKコントリビュートの最難関という説もあるとかないとか...(無いです)

※以下のbuildspecで動作するように設定されています。 github.com

このRosettaを通す上で肝になるのが、2ポチ目に記載しているfixtureになります。

fixtureとは

fixtureとはコードのテンプレートのようなものです。fixtureのファイルは packages/aws-cdk-lib/rosetta 配下にモジュールごとに存在しています(αモジュールは各モジュールのディレクトリ内にrosettaが存在します)。

fixture は 上記スライドのように /// here という箇所に、スニペットのコードを挿入する形で解決がされます。

つまりfixtureにコードスニペットを挿入したときに、問題ないコードになっていれば良いということになります。

上記の例のfixture の /// hereコードスニペットを挿入すると以下のようになります。これは正常なコードなので問題なく、コンパイルに成功します(import文が過剰な点は問題なし)。

import { Construct } from 'constructs';
import { CfnOutput, Fn, Size, Stack } from 'aws-cdk-lib';
import * as eks from 'aws-cdk-lib/aws-eks';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as kms from 'aws-cdk-lib/aws-kms';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as autoscaling from 'aws-cdk-lib/aws-autoscaling';

class Context extends Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);

    /// here にコードスニペットを挿入
    new eks.Cluster(this, 'HelloEKS', {
      version: eks.KubernetesVersion.V1_30,
    });
  }
}

Rosettaコンパイルに失敗する例

fixture の /// hereコードスニペットを挿入した場合に、不適切なコードとなる場合、Rosettaコンパイルに失敗します。

スライドの例だと以下の理由のため失敗します。

  • 1つ目の例は import の形式に沿っていないためエラーになる。(eksをインポートしているので、Clusterではなくeks.Clusterとしないといけない)
  • 2つ目の例は import していない ecr を使おうとしているためエラーになる。

特に難しい話ではなく、通常のCDK実装時と同様の話ですね。fixtureとコードスニペットが分断されているという点のみ意識すれば問題ないです。

fixtureの関連付けについて

コードスニペットの中で、関連付けるfixtureを指定できます。

  • 未指定の場合はdefault.ts-fixtureが関連づけられます。ほとんどはこっちです。
  • 一方で個別にfixtureファイルを作成し、コードスニペットで関連付けを指定することもできます。図の例で言うとcdk8schart.ts-fixtureを作成し、コードスニペットで指定する形になります。

なお、後者の個別作成はimport文が大量に必要など、特殊な事情がない限りは避けてdefaultを使用すべきであることがコントリビュートガイド上述べられています。

Utilize the default.ts-fixture that already exists rather than writing new .ts-fixture files. This is because values stored in .ts-fixture files do not surface to the examples visible in the docs, so while they help successful compilation, they do not help users understand the example.

fixtureの内容はドキュメント上では明確にはわからないのが理由です。fixtureはあくまでコンパイルのための補助ツールであり、fixtureの内容がなくともユーザーが理解できるようなドキュメントとすべきでしょう。

その他Rosettaの推奨事項

コントリビュートガイド上で述べられているその他推奨事項についても記載しておきます。

なお、モジュールによっては守られてないものもあったりします...

Types from the documented module should be un-qualified

日本語にするのが少々難しいですが、「ドキュメント化対象のモジュールは非修飾の形で記載すべし」といったところでしょうか。

説明文がなんとなくv1の時の名残な気もしていますが、ガイドに記載の通りaws-cdk-lib/core配下のものは直接記載すること、と理解しています。

Durationであれば、cdk.Duration などではなく Duration のみとするイメージです。

// An example in the aws-cdk-lib library, which defines Duration
Duration.minutes(15);

Types from other modules should be qualified:

今度は逆に「ドキュメント化対象でないモジュールは修飾の形で記載すべし」というものです。

これはaws-cdk-lib/aws-s3など各サービスのモジュールの方は、s3.Bucketなどのようにどのモジュールの型なのかを明記すること、と理解しています。

// An example in the aws-cdk-lib library, using something from aws-cdk-lib/aws-s3
const bucket = new s3.Bucket(this, 'Bucket');
// ...rest of the example...

Make use of declare statements directly in examples for values that are necessary for compilation but unimportant to the example

「重要でないリソース宣言だがコンパイルに必要なものは declare で宣言して省略せよ」というものです。

例えば以下の例は pipelines.CodePipelineaddStage メソッドを使って、Stageを紐づけられると言う説明で使用するコードスニペットです。

この説明の時、pipelines.CodePipelineStage のプロパティは重要ではないため、declareで省略するというようなイメージです。

// An example about adding a stage to a pipeline in the aws-cdk-lib/pipelines library
declare const pipeline: pipelines.CodePipeline;
declare const myStage: Stage;
pipeline.addStage(myStage);

終わりに

Rosettaをよく理解しないまま雰囲気でやっていましたが、コントリビュートガイドにバッチリ書いてありました。やはりガイドはしっかり読み込んだほうが良いですね。