
Swampの公式拡張 @swamp/aws/ec2 でAWSリソースの作成・更新・削除をAIエージェントに任せてみた
以前swamp のCLIで AWS EC2 の棚卸しを試してみました。
前回扱ったのはコミュニティ拡張 @webframp/aws/inventory を使った「リソース調査型」の棚卸しでした。今回は公式拡張 @swamp/aws/ec2 を使って、VPCからEC2インスタンスまでを実際に作って消すところまでをエージェントに任せます。@swamp/aws/* は AWS Cloud Control API のラッパーで、CRUD(create / get / update / delete / sync)が揃った「リソース管理型」のモデル群です。
AWS認証情報はシェルの環境変数経由で渡します。新しいディレクトリで swamp repo init してから、エージェントを起動するところからスタートしました。
mkdir swamp-tryout-create && cd swamp-tryout-create
swamp repo init
以降は Claude Code を起動して、「作成 → 更新 → 削除」を一通り進めます。プロンプトごとに、エージェントが何をしたかを並べていきます。
ここから先、ユーザーが入力するのはプロンプトだけです。swamp のコマンド実行、YAML の編集、aws CLI の呼び出しまで、すべてエージェントが行います。記事中の引用ブロック(> ...)はユーザーがエージェントに渡したプロンプト、bash や yaml のコードブロックはエージェントが実行・生成したものです。
作成
@swamp/aws/ec2 を使って、検証用にVPC・Subnet・SecurityGroup・小さめのEC2インスタンスを1セット作ってください。AWS認証情報はシェルの環境変数経由で取得してください。
エージェントはまず swamp extension search aws で公式拡張があることを確認し、@swamp/aws/ec2 を pull します。
swamp extension pull @swamp/aws/ec2
EC2関連の型がローカルに展開されたあと、エージェントは swamp model type describe @swamp/aws/ec2/vpc で型のスキーマを確認します。
エージェントは AWS の SSM Public Parameter から AMI ID を取得しました。
aws ssm get-parameter \
--name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
--query "Parameter.Value" --output text
# → ami-xxxxxxxxxxxxxxxxx
ここでいきなりリソース作成に進まず、エージェントは作成予定の構成を表で出してきて確認を求めました。
┌───────────────┬──────────────────────────────────────────────────────────┐
│ リソース │ 設定 │
├───────────────┼──────────────────────────────────────────────────────────┤
│ リージョン │ ap-northeast-1(AWS_REGIONを明示的に設定) │
│ VPC │ CIDR 10.0.0.0/16, DNS解決/DNSホスト名 有効 │
│ Subnet │ CIDR 10.0.1.0/24, AZ ap-northeast-1a │
│ SecurityGroup │ VPC内のSG。Ingressなし、デフォルトのEgress(全許可) │
│ EC2 Instance │ t3.nano(小さめ), Amazon Linux 2023, キーペアなし │
│ Tag │ 全リソースに Name=swamp-tryout-*, Project=swamp-tryout │
└───────────────┴──────────────────────────────────────────────────────────┘
確認ポイント
- アカウント: 検証用のIAMロールに作成
- コスト: t3.nano は東京リージョンで概ね $0.0068/時(≈月$5)
- ネット接続: IGW/RouteTable は作らないので外部疎通不可
- キーペア無し: 起動だけ確認する想定
「進めてください」と返すと、エージェントは swamp model create で4つのスキャフォールドを作って YAML を埋めていきます。1モデル=1リソースなので create を4回打つことになります。
VPCはシンプルです。
type: '@swamp/aws/ec2/vpc'
typeVersion: 2026.04.23.2
id: ac6a5ebb-5c0f-401c-9816-afae6bfbf69c
name: test-vpc
version: 1
tags: {}
globalArguments:
name: swamp-tryout-vpc
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: swamp-tryout-vpc
- Key: Project
Value: swamp-tryout
methods: {}
globalArguments のキー名は CidrBlock / EnableDnsSupport のような CloudFormation の PascalCase です。@swamp/aws/ec2 が Cloud Control API のラッパーで、リソースのスキーマもそのまま借りているためです。リージョンは globalArguments ではなく環境変数 AWS_REGION で渡す仕様で、拡張のREADMEにもそう書かれています。
globalArguments.name は型のスキーマで「factory pattern の一意識別子」と説明されているフィールドで、生成データの参照キーに効いてきます。後述するCEL式の第2引数がこの値になります。
サブネット以降はVPCを CEL式で参照します。
globalArguments:
name: swamp-tryout-subnet
VpcId: ${{ data.latest("test-vpc", "swamp-tryout-vpc").attributes.VpcId }}
AvailabilityZone: ap-northeast-1a
CidrBlock: 10.0.1.0/24
Tags:
- Key: Name
Value: swamp-tryout-subnet
セキュリティグループはVPCを参照しつつ、Ingressは付けずに作ります(後で別モデルとして足します)。
globalArguments:
name: swamp-tryout-sg
GroupDescription: swamp tryout test SG
GroupName: swamp-tryout-sg
VpcId: ${{ data.latest("test-vpc", "swamp-tryout-vpc").attributes.VpcId }}
EC2は AMI / サブネット / セキュリティグループ をすべてCELで参照します。
globalArguments:
name: swamp-tryout-instance
ImageId: ami-xxxxxxxxxxxxxxxxx
SubnetId: ${{ data.latest("test-subnet", "swamp-tryout-subnet").attributes.SubnetId }}
InstanceType: t3.nano
SecurityGroupIds:
- ${{ data.latest("test-sg", "swamp-tryout-sg").attributes.GroupId }}
エージェントは各モデルを swamp model validate で通したあと、依存の順に swamp model method run を打っていきます。
AWS_REGION=ap-northeast-1 swamp model method run test-vpc create
AWS_REGION=ap-northeast-1 swamp model method run test-subnet create
AWS_REGION=ap-northeast-1 swamp model method run test-sg create
AWS_REGION=ap-northeast-1 swamp model method run test-instance create
create 後の各リソースは .swamp/data/<type>/<model-id>/<dataName>/v1/raw に状態が保存され、次のモデルから data.latest() で参照できます。
最終的にできた構成。
| リソース | ID | 補足 |
|---|---|---|
| VPC | vpc-xxxxxxxxxxxxxxxxx |
10.0.0.0/16 |
| Subnet | subnet-xxxxxxxxxxxxxxxxx |
10.0.1.0/24, ap-northeast-1a |
| SecurityGroup | sg-xxxxxxxxxxxxxxxxx |
swamp-tryout-sg |
| EC2 Instance | i-xxxxxxxxxxxxxxxxx |
t3.nano, running, PrivateIP 10.0.1.x |
更新
ここでは更新の動きを見せるため、HTTPS 443 のingressルール(CidrIp: 0.0.0.0/0)を @swamp/aws/ec2/security-group-ingress の別モデルとしてエージェントに事前に作ってもらった状態からスタートします。セキュリティグループ本体のYAMLにルールを抱え込ませず、ルール単位で create / delete できるようにする構造です。次のリクエストはこのルールのCIDRを絞ること。
追加したingressルールのソースIPアドレスを x.x.x.x/32 にしてください
エージェントはYAMLの CidrIp を x.x.x.x/32 に書き換えて update を実行します。swamp 側のレポートは succeeded を返しました。が、AWSコンソール側で describe-security-group-rules を叩くとこうなっていました。
+-----------------------+-----------+-------------------------------+
| SecurityGroupRuleId | CidrIpv4 | Description |
+-----------------------+-----------+-------------------------------+
| sgr-xxxxxxxxxxxxxxxx1 | 0.0.0.0/0 | HTTPS from specific IP (test) |
+-----------------------+-----------+-------------------------------+
Description は変わっていますが、CidrIp は元のままです。
エージェントが原因をまとめてくれました。
AWSのAPI上、ingressルールのCIDR変更は revoke + authorize(=delete + create)が必要なため、deleteしてから再作成します
Cloud Control API は内部的に UpdateSecurityGroupRuleDescriptions* を使っていて、これは Description しか書き換えません。CIDR や ポートを変えたい場合は revoke(=delete)してから authorize(=create)し直す必要があります。
エージェントは delete → create を流して対応しました。
# 古いルールを delete(identifierは古いSecurityGroupRuleId)
AWS_REGION=ap-northeast-1 swamp model method run test-sg-ingress-https delete \
--input identifier=sgr-xxxxxxxxxxxxxxxx1
# 同じモデルから create で再作成
AWS_REGION=ap-northeast-1 swamp model method run test-sg-ingress-https create
新しい Rule ID sgr-xxxxxxxxxxxxxxxx2 で x.x.x.x/32 のルールが入りました。delete メソッドは Cloud Control API の Identifier が必須なので、--input identifier=... で渡しています。
| 項目 | 変更前 | 変更後 |
|---|---|---|
| Rule ID | sgr-xxxxxxxxxxxxxxxx1 |
sgr-xxxxxxxxxxxxxxxx2 |
| CidrIp | 0.0.0.0/0 |
x.x.x.x/32 |
| Description | HTTPS from anywhere (test) | HTTPS from specific IP (test) |
削除
今回作成したリソースを削除して
エージェントは依存の逆順で swamp model method run delete を打っていきます。EC2 → ingress → セキュリティグループ → サブネット → VPC の順です。delete は identifier(プライマリ識別子)が必須なので、--input identifier=<id> を毎回渡します。
AWS_REGION=ap-northeast-1 swamp model method run test-instance delete \
--input identifier=i-xxxxxxxxxxxxxxxxx
AWS_REGION=ap-northeast-1 swamp model method run test-sg-ingress-https delete \
--input identifier=sgr-xxxxxxxxxxxxxxxx2
# EC2のterminateを待つ
aws ec2 wait instance-terminated --instance-ids i-xxxxxxxxxxxxxxxxx
AWS_REGION=ap-northeast-1 swamp model method run test-sg delete \
--input identifier=sg-xxxxxxxxxxxxxxxxx
AWS_REGION=ap-northeast-1 swamp model method run test-subnet delete \
--input identifier=subnet-xxxxxxxxxxxxxxxxx
AWS_REGION=ap-northeast-1 swamp model method run test-vpc delete \
--input identifier=vpc-xxxxxxxxxxxxxxxxx
EC2のterminate待ちが大半で、ingress と セキュリティグループ・サブネット・VPC は数秒で消えました。AWS側でも describe-vpcs が InvalidVpcID.NotFound、describe-instances の State が terminated になっているところまで確認しました。
触ってみて
エージェントの動きで印象的だったのは、最初のプロンプトで即作成に走らずに、構成案・コスト感・接続性の確認ポイントを表にまとめて確認を求めてきたことでした。@swamp/aws/ec2 は実際にAWSをリソースを作成するタイプの拡張なので、勝手に進められると料金や疎通の前提が違って後で困ります。swamp-model スキルの「Verify before destructive operations」ルールがこれを促しているのだと思います。
CEL式の data.latest(<modelName>, <dataName>) は、型のスペック名ではなく globalArguments.name の値(ファクトリパターンのインスタンス名)を第2引数に渡すのがポイントでした。前回ブログでも @webframp/aws/inventory のデータ参照で同じ data.latest() パターンを使っていますが、@swamp/aws/* は1モデルで複数のリソースインスタンスを生やせる factory 設計なので、name をきちんと意識して書く必要があります。
ingress ルールの管理はセキュリティグループ本体の update ではなく、別モデル @swamp/aws/ec2/security-group-ingress を立てる方を選んだのも面白い判断でした。Cloud Control API の update がDescription程度しか書き換えないため、CIDRやポートを変えるたびに revoke + authorize が必要になります。これをモデル単位で delete → create に閉じ込められるなら、セキュリティグループのモデル定義そのものを汚さずに運用できる、という発想です。
create も update も delete も、書き味の差はメソッド名だけで揃います。プロビジョニングと棚卸しが同じモデルの仕組みで扱えるのが Swamp の面白いところでした。
次回はこれを swamp workflow でまとめて、create から cleanup までを1コマンドで流せるようにしてみます。








