Carinaの開発状況(2月下旬〜3月中旬)

2026-03-20 17:56:56 +0900

前回の記事 ではClaude Codeによるイシュー駆動開発について書いた。あの時点では90個のイシューが登録され67個がクローズされた、というところだったが、あれから約1ヶ月、さらに約280個のイシューがクローズされ、約320個のPRがマージされた。

この1ヶ月でやったことを振り返っておく。


AWS providerの本格整備

aws providerは以前からあったが、スキーマの手書き部分が多く、テストも不十分だった。この1ヶ月で本格的に整備した。

まず、 Smithyモデル からスキーマを自動生成する仕組みを入れた( #461 )。awscc providerのCloudFormationスキーマからの自動生成と同じ考え方で、AWS SDKの元になっているSmithyモデルから、リソーススキーマとread関数を自動生成する。手書きのスキーマは信用できないので、できるだけ自動生成に寄せていきたい。


Acceptance testの拡充

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 )など、テストの信頼性を上げるための改善も多い。


Anonymous resourceの識別と置き換え

Carinaでは、Terraformと異なり、リソースに名前をつけなくてもよい。

# Terraformだと aws_vpc.main のように名前が必要だが、Carinaでは不要
awscc.ec2_vpc {
  cidr_block = "10.0.0.0/16"
}

ただし、内部的には .crn ファイルに書かれたリソースとstateに保存されたリソースを突き合わせるためのidentifierが必要になる。Terraformは人間がつけた名前( aws_vpc.mainmain の部分)を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独自の設計判断が多く、バグも出やすい領域だった。


Create-before-destroyの名前衝突回避とカスケード更新

リソースの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の強化

エディタ上での開発体験を良くするために、LSP(Language Server Protocol)まわりを大幅に改善した。


コード生成の強化

CloudFormationスキーマとSmithyモデルからのコード生成器(codegen)に、バリデーション制約のサポートを追加した。文字列長(minLength/maxLength)、配列長(minItems/maxItems)、正規表現パターン、フォーマット制約(int64、URIなど)、デフォルト値、複数制約の合成(pattern + lengthなど)をサポートしている( #628 #636 )。

これにより、 carina validate で実際にAWSにリクエストを投げる前に、かなり細かいバリデーションができるようになった。


State管理の改善

本番運用を見据えたstate管理の改善も進めた。


その他の新機能


リファクタリング

コードベースが大きくなってきたので、構造的な整理も進めた。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のコードを変更する必要がなくなった。