はじめに
前回の記事では、FormaeからAWSリソースを参照できるところまで確認しました。
今回はその続きとして、既存のAWSリソースをFormaeで抽出し、IaCとして管理するところまで試してみます。
具体的には、以下の内容を確認します。
- 既存AWSリソースをFormaeのコードとして抽出し、Formaeの管理下に置く
- コードを変更してAWSリソースへ反映する
- AWSコンソール上で変更した内容をFormae側で検知できるか確認する
結論から言うと、既存リソースの抽出とコードからの変更適用は比較的スムーズにできました。
一方で、AWSコンソール側で変更した内容の反映タイミングや、再抽出後の差分確認には少し気になる挙動もありました。
formaeとは
Formaeは、Terraformのようにインフラをコードで管理するIaCツールです。
既存のクラウドリソースを読み取り、コード化し、実際のインフラ状態とコードを同期しながら管理することを得意としています。
Terraformと大きく異なる点として、FormaeにはTerraformのようなstateファイルがありません。
このあたりの設計思想の違いも気になったため、実際に触りながら挙動を確認していきます。
既存のリソースをIaCに変換する
以下公式ガイドを参考に、既存のAWSリソースをFormaeの管理対象に取り込んでみます。
前提
前回実施したクラウドとの接続確認ができていること
手順
formae agentの起動
前回からOS再起動したのでエージェントが停止していました。
formae agent startを実行します。※前回と同じなのでコマンド実行結果は省略
AWSリソースの抽出
リソースの抽出(extract)コマンドを実行します。
discovered.pklファイルに抽出結果を書き出します。
user@host:~/formae/1.AwsImport$ formae extract --query="managed:false" discovered.pkl
Successfully extracted 26 resource(s) as Pkl to 'discovered.pkl'
Tip: You have 26 unmanaged resources. You can extract them using formae extract --query='managed:false', adjust and apply the changes.
user@host:~/formae/1.AwsImport$
スタックラベルの追加
discovered.pklから、以下コメントを解除し、labelの値を入れます。
forma {
local myStack = new formae.Stack {
// Please provide a stack to bring the resources in this Forma under management
// label = ""
description = "Unmanaged resources"
}
myStack
以下のように変更しました。
forma {
local myStack = new formae.Stack {
label = "imported-resources"
description = "Unmanaged resources"
}
myStack
discovered.pklをapplyします。
user@host:~/formae/1.AwsImport$ formae apply --mode reconcile --watch discovered.pkl
Command will
├── create stack imported-resources (description: Unmanaged resources)
├── update resource dopt-XXXXXXXXXXXXXXXXX
│ ├── of type AWS::EC2::DHCPOptions
│ ├── from unmanaged to imported-resources
│ └── by doing the following:
│ └── put resource under management
<中略>
└── update resource AWSServiceRoleForTrustedAdvisor
├── of type AWS::IAM::Role
├── from unmanaged to imported-resources
└── by doing the following:
└── put resource under management
This operation will create 1 stack(s) and update 26 resource(s).
Do you want to continue? (Y): Y
The asynchronous command has started on the formae agent.
Watching commands status (refreshing every 2s)...
┌─────────────────────────────┬─────────┬────────────┬────────┬──────┬──────────┬─────────┬───────┬──────┬──────────┬────────────────────┬──────┐
│ ID │ Command │ Status │ Change │ Wait │ Progress │ Success │ Retry │ Fail │ Canceled │ Started At │ Time │
├─────────────────────────────┼─────────┼────────────┼────────┼──────┼──────────┼─────────┼───────┼──────┼──────────┼────────────────────┼──────┤
│ XXXXXXXXXXXXXXXXX │ apply │ InProgress │ 27 │ 23 │ 0 │ 4 │ 0 │ 0 │ 0 │ 06/06/2026 11:32AM │ 2s │
└─────────────────────────────┴─────────┴────────────┴────────┴──────┴──────────┴─────────┴───────┴──────┴──────────┴────────────────────┴──────┘
<中略>
Watching commands status (refreshing every 2s)...
┌─────────────────────────────┬─────────┬─────────┬────────┬──────┬──────────┬─────────┬───────┬──────┬──────────┬────────────────────┬──────┐
│ ID │ Command │ Status │ Change │ Wait │ Progress │ Success │ Retry │ Fail │ Canceled │ Started At │ Time │
├─────────────────────────────┼─────────┼─────────┼────────┼──────┼──────────┼─────────┼───────┼──────┼──────────┼────────────────────┼──────┤
│ XXXXXXXXXXXXXXXXX │ apply │ Success │ 27 │ 0 │ 0 │ 27 │ 0 │ 0 │ 0 │ 06/06/2026 11:32AM │ 17s │
└─────────────────────────────┴─────────┴─────────┴────────┴──────┴──────────┴─────────┴───────┴──────┴──────────┴────────────────────┴──────┘
user@host:~/formae/1.AwsImport$
リソースのStackがunmanagedからimported-resourcesにかわりました。
user@host:~/formae/1.AwsImport$ formae inventory resources --query="stack:imported-resources"
┌──────────────────────────┬────────────────────┬────────────────────────────────┬──────────────────────────┐
│ NativeID │ Stack │ Type │ Label │
├──────────────────────────┼────────────────────┼────────────────────────────────┼──────────────────────────┤
│ dopt-XXXXXXXXXXXXXXXXX │ imported-resources │ AWS::EC2::DHCPOptions │ dopt-XXXXXXXXXXXXXXXXX │
├──────────────────────────┼────────────────────┼────────────────────────────────┼──────────────────────────┤
│ igw-XXXXXXXXXXXXXXXXX │ imported-resources │ AWS::EC2::InternetGateway │ igw-XXXXXXXXXXXXXXXXX │
├──────────────────────────┼────────────────────┼────────────────────────────────┼──────────────────────────┤
│ acl-XXXXXXXXXXXXXXXXX │ imported-resources │ AWS::EC2::NetworkAcl │ acl-XXXXXXXXXXXXXXXXX │
├──────────────────────────┼────────────────────┼────────────────────────────────┼──────────────────────────┤
│ rtb-XXXXXXXXXXXXXXXXX │ imported-resources │ AWS::EC2::RouteTable │ rtb-XXXXXXXXXXXXXXXXX │
├──────────────────────────┼────────────────────┼────────────────────────────────┼──────────────────────────┤
│ sg-XXXXXXXXXXXXXXXXX │ imported-resources │ AWS::EC2::SecurityGroup │ sg-XXXXXXXXXXXXXXXXX │
├──────────────────────────┼────────────────────┼────────────────────────────────┼──────────────────────────┤
│ sgr-XXXXXXXXXXXXXXXXX │ imported-resources │ AWS::EC2::SecurityGroupEgress │ sgr-XXXXXXXXXXXXXXXXX │
├──────────────────────────┼────────────────────┼────────────────────────────────┼──────────────────────────┤
│ sgr-XXXXXXXXXXXXXXXXX │ imported-resources │ AWS::EC2::SecurityGroupIngress │ sgr-XXXXXXXXXXXXXXXXX │
├──────────────────────────┼────────────────────┼────────────────────────────────┼──────────────────────────┤
│ subnet-XXXXXXXXXXXXXXXXX │ imported-resources │ AWS::EC2::Subnet │ subnet-XXXXXXXXXXXXXXXXX │
├──────────────────────────┼────────────────────┼────────────────────────────────┼──────────────────────────┤
│ subnet-XXXXXXXXXXXXXXXXX │ imported-resources │ AWS::EC2::Subnet │ subnet-XXXXXXXXXXXXXXXXX │
├──────────────────────────┼────────────────────┼────────────────────────────────┼──────────────────────────┤
│ subnet-XXXXXXXXXXXXXXXXX │ imported-resources │ AWS::EC2::Subnet │ subnet-XXXXXXXXXXXXXXXXX │
└──────────────────────────┴────────────────────┴────────────────────────────────┴──────────────────────────┘
Summary: Showing 10 of 26 total resources (use --max-results 26 to see all)
user@host:~/formae/1.AwsImport$
これでリソースがformaeの管理下に置かれました。
どのようなコードが書かれているか確認します。
discovered.pklに全リソースのコードがずらっと書かれていました。
amends "@formae/forma.pkl"
import "@formae/formae.pkl"
<中略>
forma {
local myStack = new formae.Stack {
label = "imported-resources"
description = "Unmanaged resources"
}
myStack
local myAccount = new formae.Target {
label = "my-account"
namespace = "AWS"
discoverable = true
config {
Type = "AWS"
Region = "ap-northeast-1"
}
}
myAccount
new dhcpoptions.DHCPOptions {
label = "dopt-XXXXXXXXXXXXXXXXX"
domainName = "ap-northeast-1.compute.internal"
domainNameServers = new Listing { "AmazonProvidedDNS" }
tags = new Listing {}
target = myAccount.res
stack = myStack.res
}
new internetgateway.InternetGateway {
label = "igw-XXXXXXXXXXXXXXXXX"
tags = new Listing {}
target = myAccount.res
stack = myStack.res
}
<中略>
description = "Access for the AWS Trusted Advisor Service to help reduce cost, increase performance, and improve security of your AWS environment."
managedPolicyArns = new Listing { "arn:aws:iam::aws:policy/aws-service-role/AWSTrustedAdvisorServiceRolePolicy" }
maxSessionDuration = 3600
path = "/aws-service-role/trustedadvisor.amazonaws.com/"
roleName = "AWSServiceRoleForTrustedAdvisor"
target = myAccount.res
stack = myStack.res
}
}
formaeのコードを変更し、AWSリソースに変更を適用する
コードを変更し、AWSリソースに変更を適用したいと思います。
ハウツーガイドにこのケースのやり方の記載がなかったため、手探りでやっていきます。
今回はインターネットゲートウェイのタグにテストタグを追加してみようと思います。
new internetgateway.InternetGateway {
label = "igw-XXXXXXXXXXXXXXXXX"
tags = new Listing {}
target = myAccount.res
stack = myStack.res
}
以下のように変更しました。
new internetgateway.InternetGateway {
label = "igw-XXXXXXXXXXXXXXXXX"
tags = new Listing { new types.Tag {
key = "formae-test"
value = "true"
} }
target = myAccount.res
stack = myStack.res
}
formae eval discovered.pklコマンドでPklファイルを評価します。
コード記述が間違っていた時にここで検知もしてくれました。
user@host:~/formae/1.AwsImport$ formae eval discovered.pkl
<中略>
"Targets": [
{
"Config": {
"Region": "ap-northeast-1",
"Type": "AWS"
},
"Discoverable": true,
"Label": "my-account",
"Namespace": "AWS"
}
]
}
json形式の表示が出てきました。
変数なども反映したformaeで最終的にどのように設定を認識したかを表示するコマンドのようです。
次に、formae apply --mode reconcile --simulate discovered.pklを実行します。
--simulateを付けると、実際には変更せず、適用予定の内容だけを確認できました。
Terraformのplanに近い感覚で使えそうです。
user@host:~/formae/1.AwsImport$ formae apply --mode reconcile --simulate discovered.pkl
Command will
└── update resource igw-XXXXXXXXXXXXXXXXX
├── of type AWS::EC2::InternetGateway
├── in stack imported-resources
└── by doing the following:
└── add new Tag "formae-test" with the value "true"
Command will not continue - simulation only
user@host:~/formae/1.AwsImport$
変更内容の確認ができました。問題なさそうです。
applyしていきます。
user@host:~/formae/1.AwsImport$ formae apply --mode reconcile discovered.pkl
Command will
└── update resource igw-XXXXXXXXXXXXXXXXX
├── of type AWS::EC2::InternetGateway
├── in stack imported-resources
└── by doing the following:
└── add new Tag "formae-test" with the value "true"
This operation will update 1 resource(s).
Do you want to continue? (Y): Y
The asynchronous command has started on the formae agent.
Run the following command to check the status of this command:
formae status command --query='id:XXXXXXXXXXXXXXXXXXXXXXXX'
user@host:~/formae/1.AwsImport$
applyに成功しました。
statusコマンドでapplyの実行結果を確認してみます。
user@host:~/formae/1.AwsImport$ formae status command --query='id:XXXXXXXXXXXXXXXXXXXXXXXX'
┌─────────────────────────────┬─────────┬─────────┬────────┬──────┬──────────┬─────────┬───────┬──────┬──────────┬────────────────────┬──────┐
│ ID │ Command │ Status │ Change │ Wait │ Progress │ Success │ Retry │ Fail │ Canceled │ Started At │ Time │
├─────────────────────────────┼─────────┼─────────┼────────┼──────┼──────────┼─────────┼───────┼──────┼──────────┼────────────────────┼──────┤
│ XXXXXXXXXXXXXXXXXXXXXXXX │ apply │ Success │ 1 │ 0 │ 0 │ 1 │ 0 │ 0 │ 0 │ 06/06/2026 12:34PM │ 25s │
└─────────────────────────────┴─────────┴─────────┴────────┴──────┴──────────┴─────────┴───────┴──────┴──────────┴────────────────────┴──────┘
user@host:~/formae/1.AwsImport$
成功したようです。
WEBコンソールを確認します。
タグが付与されていることを確認できました。
ドリフト検知の確認
次に、WEBコンソール上での手動変更が、ドリフトとしてformaeに認識されるか確認します。
もう一度、apply --simulateを実行するとどうなるか確認してみました。
user@host:~/formae/1.AwsImport$
user@host:~/formae/1.AwsImport$ formae apply --mode reconcile --simulate discovered.pkl
Error: forma rejected because the stacks it references have been modified since the last reconcile command.
There are two options to resolve this issue:
1) use the '--force' flag to apply the forma anyway (this will overwrite any changes made since the last reconcile), or
2) manually adjust your own code:
- extract the changes made since the last reconcile and incorporate them in your forma before applying it again.
Here is the list of extract commands to use (use different target file names):
formae extract --query='stack:imported-resources type:AWS::EC2::InternetGateway label:igw-XXXXXXXXXXXXXXXXX' <target forma file>
user@host:~/formae/1.AwsImport$
変更が加えられているため、コマンドが拒否されました。
二つの選択肢があるようです。
-
-forceで強制実行 - 一部を抽出(
extract)し、手動で変更を取り込む
いったん、差分の全量を確認してみます。
discovered2.pklに現在のAWSリソース全量の抽出を行い、discovered.pklとdiffして差分を調べようと思います。
差分はタグの変更だけになるはずです。
user@host:~/formae/1.AwsImport$
user@host:~/formae/1.AwsImport$ formae extract --query="managed:true" discovered2.pkl
Successfully extracted 26 resource(s) as Pkl to 'discovered2.pkl'
user@host:~/formae/1.AwsImport$ diff discovered.pkl discovered2.pkl
26c26
< local myStack = new formae.Stack {
---
> local importedResources = new formae.Stack {
30c30
< myStack
---
> importedResources
50c50
< stack = myStack.res
---
> stack = importedResources.res
55,58c55
< tags = new Listing { new types.Tag {
< key = "formae-test"
< value = "true"
< } }
---
> tags = new Listing {}
<中略>
530c543
< }
---
> }
\ No newline at end of file
user@host:~/formae/1.AwsImport$
大量の差分が出てしまいました。
stackをunmanagedからimported-resourcesに変更したことの差分のようです。
リトライ:formaeのコードを変更し、AWSリソースに変更を適用する
一度、最新の状態を取り込んでから再度コードを変更し、AWSリソースへの変更適用をリトライします。
再度タグ作成するようにコードを書き換えます。
new internetgateway.InternetGateway {
label = "igw-XXXXXXXXXXXXXXXXX"
tags = new Listing { new types.Tag {
key = "formae-test"
value = "true"
} }
target = myAccount.res
stack = importedResources.res
}
formae eval discovered.pklは問題なく通りました。
前回とは異なり、formae apply --mode reconcile --simulate discovered.pklはエラーになりました。
user@host:~/formae/1.AwsImport$ formae apply --mode reconcile --simulate discovered.pkl
Error: forma rejected because the stacks it references have been modified since the last reconcile command.
There are two options to resolve this issue:
1) use the '--force' flag to apply the forma anyway (this will overwrite any changes made since the last reconcile), or
2) manually adjust your own code:
- extract the changes made since the last reconcile and incorporate them in your forma before applying it again.
Here is the list of extract commands to use (use different target file names):
formae extract --query='stack:imported-resources type:AWS::EC2::InternetGateway label:igw-XXXXXXXXXXXXXXXXX' <target forma file>
user@host:~/formae/1.AwsImport$
初回はエラーが出なかったのに、今回はエラーが出た理由については検証が必要です。
前回の抽出後から差分が出ているといわれました。
強制的に適用するか、変更を確認して手動で変更をコードに取り込むか、どちらかをするように言われています。
formae extract --query='stack:imported-resources type:AWS::EC2::InternetGateway label:igw-XXXXXXXXXXXXXXXXX' current-igw.pklコマンドを実行し、現行の情報を再度確認してみます。
new internetgateway.InternetGateway {
label = "igw-XXXXXXXXXXXXXXXXX"
tags = new Listing {}
target = myAccount.res
stack = importedResources.res
}
やはりタグがついていないこと以外は現在のコードと一致していました。
--forceで強制的に適用することにします。
user@host:~/formae/1.AwsImport$ formae apply --mode reconcile --force discovered.pkl
Command will
└── update resource igw-XXXXXXXXXXXXXXXXX
├── of type AWS::EC2::InternetGateway
├── in stack imported-resources
└── by doing the following:
└── add new Tag "formae-test" with the value "true"
This operation will update 1 resource(s).
Do you want to continue? (Y): Y
The asynchronous command has started on the formae agent.
Run the following command to check the status of this command:
formae status command --query='id:XXXXXXXXXXXXXXXXXXXXXXXX'
user@host:~/formae/1.AwsImport$
リソースが作成されました。
コードの再抽出
再度、AWSリソースをコードとして抽出しようと思います。
extractでdiscovered2.pklにAWSリソース状況を抽出します。
user@host:~/formae/1.AwsImport$ formae extract --query="managed:true" discovered2.pkl
File 'discovered2.pkl' already exists. Overwrite? (Y): Y
Successfully extracted 26 resource(s) as Pkl to 'discovered2.pkl'
user@host:~/formae/1.AwsImport$
discovered.pklとdiscovered2.pklをdiffしてみます。
差分はないはずです。
user@host:~/formae/1.AwsImport$ diff discovered.pkl discovered2.pkl
56,58c56,58
< key = "formae-test"
< value = "true"
< } }
---
> key = "formae-test"
> value = "true"
> } }
user@host:~/formae/1.AwsImport$
インデントの違いが差分に出てしまいました。
今後毎回差分として検知されるのが嫌なので、formae extract --query="managed:true" discovered.pklコマンドで差分を取り込みました。
今後applyした後はextractで再抽出をしたほうがよさそうです。
リトライ:ドリフト検知の確認
もう一度、WEBコンソールからタグ設定を削除し、extractで再抽出およびdiffを確認したいと思います。
タグを削除しました。
discovered2.pklに抽出します。
user@host:~/formae/1.AwsImport$ formae extract --query="managed:true" discovered2.pkl
File 'discovered2.pkl' already exists. Overwrite? (Y): Y
Successfully extracted 26 resource(s) as Pkl to 'discovered2.pkl'
user@host:~/formae/1.AwsImport$
タグを追加するためにapplyしたコードdiscovered.pklと
タグ削除後にAWS上から情報を抽出したコードdiscovered2.pklをdiffします。
user@host:~/formae/1.AwsImport$ diff discovered.pkl discovered2.pkl
user@host:~/formae/1.AwsImport$
diffによる差分が無しになってしまいました。
discovered2.pklファイルの中身を見たところ、WEBコンソール上で削除したformae-testタグがコードが書かれたままになっていました。
その後、参照系のコマンドを色々打ちながら調査し、再び差分確認をしてみました。
user@host:~/formae/1.AwsImport$ formae extract --query="managed:true" discovered2.pkl
File 'discovered2.pkl' already exists. Overwrite? (Y): Y
Successfully extracted 26 resource(s) as Pkl to 'discovered2.pkl'
user@host:~/formae/1.AwsImport$ diff discovered.pkl discovered2.pkl
55,58c55
< tags = new Listing { new types.Tag {
< key = "formae-test"
< value = "true"
< } }
---
> tags = new Listing {}
user@host:~/formae/1.AwsImport$
すると、今度は差分が表示されました。
この挙動の原因は今回の検証だけでは判断できませんでした。
AWS/Formae側の反映タイミング、参照系コマンドによる状態更新などが関係している可能性があります。
さいごに
- 既存AWSリソースをFormaeのコードとして抽出し、Formaeの管理下に置く
- コードを変更してAWSリソースへ反映する
- AWSコンソール上で変更した内容をFormae側で検知できるか確認する
これらをやってみました。
既存リソースの抽出と、コードからAWSリソースへの変更適用は比較的スムーズにできました。
一方で、外部変更後の再抽出や差分確認では、挙動をもう少し確認したい点も残りました。
やはり、厳格さはterraformに軍配が上がりますが、柔軟性はformaeに軍配が上がると思います。
MCPを使うことでformaeの柔軟性がさらに活きる気がしますので、次回MCPの利用を検証したいと思います。


