前回の記事 ではClaude Codeによるイシュー駆動開発について書いた。あの時点では90個のイシューが登録され67個がクローズされた、というところだったが、あれから約1ヶ月、さらに約280個のイシューがクローズされ、約320個のPRがマージされた。
この1ヶ月でやったことを振り返っておく。
aws providerは以前からあったが、スキーマの手書き部分が多く、テストも不十分だった。この1ヶ月で本格的に整備した。
まず、 Smithyモデル からスキーマを自動生成する仕組みを入れた( #461 )。awscc providerのCloudFormationスキーマからの自動生成と同じ考え方で、AWS SDKの元になっているSmithyモデルから、リソーススキーマとread関数を自動生成する。手書きのスキーマは信用できないので、できるだけ自動生成に寄せていきたい。
AWS providerとAWSCC providerの両方で、acceptance testを大幅に増やした。前回の記事時点ではAWSCC providerの39ファイルだけだったが、AWS providerのテスト追加とシナリオの拡充で172ファイルになった(AWSCC: 120、AWS: 52)。基本的なCRUDに加えて、タグの削除、in-place update、create-before-destroy、属性の削除といったシナリオを整備している。
特に大きかったのは、in-place updateのテストの追加。リソースを作成した後、属性を変更してもう一度applyし、replaceではなくin-place updateが正しく行われることを確認するテスト。これを入れたことで、AWSCC providerのupdate処理にあったバグが大量に見つかった。
multi-stepテスト(create → update → destroy)のインフラも整備した。テストスクリプトのシグナルハンドリング( #793 )、destroy失敗時の検出( #855 )、orphanedリソースの防止( #812 )など、テストの信頼性を上げるための改善も多い。
Carinaでは、Terraformと異なり、リソースに名前をつけなくてもよい。
# Terraformだと aws_vpc.main のように名前が必要だが、Carinaでは不要
awscc.ec2_vpc {
cidr_block = "10.0.0.0/16"
}
ただし、内部的には
.crn
ファイルに書かれたリソースとstateに保存されたリソースを突き合わせるためのidentifierが必要になる。Terraformは人間がつけた名前(
aws_vpc.main
の
main
の部分)をidentifierとして使うが、Carinaのanonymous resourceにはそれがない。
そこで、スキーマのcreate-onlyプロパティ(作成後に変更できない属性)の値からハッシュを計算してidentifierとしている。ただ、create-onlyプロパティが変更された場合、ハッシュが変わってidentifierも変わる。するとdifferは、
.crn
ファイルにある「新しいidentifierのリソース」とstateにある「古いidentifierのリソース」を同じリソースだと認識できず、「新しいリソースの作成」と「古いリソースの削除」という無関係な2つの操作として扱ってしまう。本来は「同じリソースの置き換え(Replace)」として認識させる必要がある。
そこで、
reconcile_anonymous_identifiers()
という仕組みを入れた(
#540
)。create-onlyプロパティの一部が一致し一部が異なる場合、同じリソースの変更であると判定し、stateにある旧identifierを引き継ぐことでReplaceとして扱う。
さらに、create-onlyプロパティを持たないリソース(EC2 EIPなど)に対しては、全属性の SimHash (locality-sensitive hash)をidentifierとして使うようにした( #592 )。通常のハッシュは1ビットでも入力が変わると出力が大きく変わるが、SimHashは入力の類似度を保存する。属性を1つ変更しても数ビットしか変わらないので、Hamming距離で同一リソースかどうかを判定できる。ただし、tagsのようなMap属性をそのまま1つのfeature(ハッシュの入力単位)としてハッシュすると、tag1つの変更でも相対的な変化が大きくなりすぎて閾値を超えてしまう問題があった。Map/List値を個々のエントリに展開(flatten)してそれぞれ別のfeatureとすることで修正した( #885 )。
Terraformにはanonymous resourceの概念がないので、Carina独自の設計判断が多く、バグも出やすい領域だった。
リソースのReplace(置き換え)を実行するcreate-before-destroyでも、Terraformにはない仕組みを入れた。
Terraformではcreate-before-destroyの際、名前の衝突は人間が
name_prefix
等で回避する必要がある。Carinaでは、リソースの
name_attribute
(S3なら
bucket_name
、IAMなら
role_name
)をスキーマから自動判定し、replaceの際に一時名を自動生成する(
#405
)。名前がupdatable(create-onlyでない)なら、旧リソース削除後に本来の名前にリネームする。create-onlyな場合はリネームできないので一時名のまま残る。
依存リソースのカスケードアップデートも入れた( #404 )。create-before-destroyでは「新リソース作成→依存リソース更新→旧リソース削除」という順序で実行する必要があるが、この中間ステップの依存リソース更新をdifferが依存グラフから検出してplanに含める。
エディタ上での開発体験を良くするために、LSP(Language Server Protocol)まわりを大幅に改善した。
route_table_id
に対してRouteTableを返すリソースだけを候補に出す
CloudFormationスキーマとSmithyモデルからのコード生成器(codegen)に、バリデーション制約のサポートを追加した。文字列長(minLength/maxLength)、配列長(minItems/maxItems)、正規表現パターン、フォーマット制約(int64、URIなど)、デフォルト値、複数制約の合成(pattern + lengthなど)をサポートしている( #628 〜 #636 )。
これにより、
carina validate
で実際にAWSにリクエストを投げる前に、かなり細かいバリデーションができるようになった。
本番運用を見据えたstate管理の改善も進めた。
--lock=false
オプション — state lockのスキップ(
#893
)
.crn
ファイルから消えたリソースの自動検出・削除(
#853
)
List<Struct>
属性に対するブロック構文のサポートと、codegenでの自動block_name生成(
#381
、
#759
)
コードベースが大きくなってきたので、構造的な整理も進めた。main.rs(4000行)、awscc provider.rs(3500行)、differ.rs、diagnostics.rs、completion.rsなどの大きなファイルを分割し、carina-coreからvalidation、resolver、deps、config_loaderなどのモジュールを抽出した( #382 〜 #392 、 #673 、 #787 )。
また、CLIやLSPのコードに
"aws"
や
"awscc"
といったプロバイダー名が直接書かれていたのを、
ProviderFactory
トレイト経由で動的に解決するように変更した(
#364
、
#367
)。新しいプロバイダーを追加してもCLIやLSPのコードを変更する必要がなくなった。