CLI でフローをコピーする
CLI はフローをポータブルなバンドルにシリアライズし、別の場所で再作成できます。dci export-cloudflow-flow は、そのフローに加えて subflow ノード経由で到達可能なすべてのフローを 1 つの JSON ドキュメントとして書き出し、dci import-cloudflow-flow は、認証済みのテナント内にそれらのフローを作成します。
バンドルはテナントに中立で、クレデンシャルを含みません。Connections、Datastore テーブル、global variables は、インポート時に実際のリソースへ再バインドする名前付きの_requirements_ として移動します。元のテナントに関するそれ以外の情報(クレデンシャル、テナント識別子、スケジュール、実行履歴)は一切含まれません。
このチュートリアルでは After-Hours Instance Sweep というフローを使用します。これは、本番以外の EC2 インスタンスを停止するスケジュール済みフローです。ただし、ここでの各コマンドはどのフローに対しても同じように動作します。
Export と import には最新の CLI が必要です。まず dci update を実行し、コマンドが dci --help に表示されない場合は Keep it up to date を参照してください。
目的と到達目標
-
目的 (Goal): コンソール上でノードごとに作り直すことなく、稼働中のフローをあるテナントから別のテナントへ移動すること。
-
到達目標 (Objectives): このチュートリアルでは次の内容を学びます。
-
フローをバンドルファイルにエクスポートし、その中身を確認する方法
-
ドライランを使って、何も書き込まずにインポートを検証する方法
-
ターゲットテナント内の Connections やテーブルに、バンドルの requirements をバインドする方法
-
フローをインポートし、コンソールで仕上げる方法
-
始める前に
-
CLI をインストールし、サインインしてください。
dci validateを実行して両方を確認してください。 -
エクスポートおよびインポートを行う各テナントで、DoiT アカウントに CloudFlow Editor または CloudFlow Manager 権限が付与されていることを確認してください。詳細は CloudFlow permissions を参照してください。
-
省略可能ですが、以下でバンドルの一部を読み取ったりインポートオプションを追加したりするために使用する jq をインストールしてください。
フロー ID を探す
Export はフロー名ではなくフロー ID を受け取ります。フロー一覧は、ID を表示するよう指定した場合に ID を表示します。
dci list-cloudflows --fields id,name
╔══════════════════════╤══════════════════════════════════════════════════════╗
║ id │ name ║
╟━━━━━━━━━━━━━━━━━━━━━━┼━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╢
║ q8RR0Ea9KqbMtgAxKWiW │ Datastore SQL Testing ║
║ G2zdE9inbvwxBc38FCVc │ Draft Delete unused Oracle Cloud reserved public IPs ║
║ EBnC9oHaZpcH3hgWGoSp │ After-Hours Instance Sweep ║
╚══════════════════════╧══════════════════════════════════════════════════════╝
ID は、コンソールでフローを開いたときの URL の最後のセグメントでもあります。
フローをエクスポートする
Export はバンドルを標準出力に書き出すため、リダイレクトしてファイルに保存します。
dci export-cloudflow-flow EBnC9oHaZpcH3hgWGoSp > after-hours-sweep.json
結果は、フロー全体を表す 1 つの JSON ドキュメントです。そのトップレベルを見ると、どのような内容かが分かります。
jq '{kind, schemaVersion, rootFlow, requirements, flows: [.flows[] | {key, name, nodes: (.nodes | length)}]}' after-hours-sweep.json
{
"kind": "cloudflow.doit.com/FlowBundle",
"schemaVersion": 1,
"rootFlow": "EBnC9oHaZpcH3hgWGoSp",
"requirements": {},
"flows": [
{
"key": "EBnC9oHaZpcH3hgWGoSp",
"name": "After-Hours Instance Sweep",
"nodes": 7
}
]
}
-
rootFlow: エクスポートしたフローです。そこから呼び出される subflow は、flows内に並んで格納されます。 -
requirements: フローが必要とする、テナントにスコープされたリソースです。このフローでは宣言されていません。requirements を持つフローについては、Bind requirements で説明します 。 -
flows: フローそのもの(ノード、トランジション、パラメータ、変数定義)です。
デフォルトでは変数の値も一緒に移動します。変数名・型・必須かどうかだけを残し、値をバンドルに含めたくない場合は、--include-variable-values=false を付けてエクスポートしてください。
ポリシーおよび Slack チャンネルの参照は、テナント間では移動できません。Export はそれらをサポートされていない参照として記録し、Import はそれらを使用しているノードを未完了としてフラグします。その後、コンソールで参照先を付け替えてください。
バンドルを YAML として読む
バンドルはマシン向けですが、フローの構成を俯瞰したい場合は YAML の方が読みやすいことが多いです。less でページングしたり、先頭行だけを抜き出して全体像をつかんだりしてください。
dci export-cloudflow-flow EBnC9oHaZpcH3hgWGoSp --output yaml | head -13
exportedAt: "2026-08-25T17:58:31.696566271Z"
flows:
- description: Stops non-prod instances on weekday evenings. Skips production. Flags
untagged instances to their owners.
firstNode: 4e10b33c-5ff9-4667-b8d9-594317c55e7d
key: EBnC9oHaZpcH3hgWGoSp
name: After-Hours Instance Sweep
nodes:
- key: 0421485f-0a3a-4f64-a587-fe1b0fb361e0
name: 'Production: skip'
parameters:
outputs: []
type: flowOutput
フロー内のすべてのノードは nodes の下に、firstNode と各トランジションが参照する内部 ID をキーとして並びます。同じビューはどの出力フォーマットでも利用でき、--fields でさらに絞り込めます。
YAML は閲覧専用として使用してください。Import が受け付けるのは JSON なので、インポートに使うファイルはデフォルトのフォーマットのままにしてください。
インポートをドライランする
インポート前には必ず検証してください。ドライランは何も書き込まず、インポートプランだけを返します。
dci import-cloudflow-flow --dry-run < after-hours-sweep.json
{
"action": {
"command": "import-cloudflow-flow",
"status": "simulated",
"dry_run": true
},
"result": {
"errors": [],
"flowsToCreate": [
{
"incompleteNodeCount": 0,
"key": "EBnC9oHaZpcH3hgWGoSp",
"name": "After-Hours Instance Sweep",
"nodeCount": 7
}
],
"requirements": [],
"valid": true
}
}
valid: true かつ errors 配列が空、incompleteNodeCount: 0 の場合、そのインポートは完全に成功する見込みです。検証は問題を一度にすべて報告するため、拒否されたバンドルでも、試行ごとに 1 件ずつではなく、一度に全ての修正点を把握できます。
バンドルはエクスポート時と同じ形で標準入力から渡します。CLI が、API が期待するリクエスト形状にラップします。
requirements をバインドする
Connection、Datastore テーブル、global variable を使用するフローは、それらを requirement として宣言します。これらのリソースは 1 つのテナント内にだけ存在するためです。次は、未使用の Oracle Cloud の reserved public IP を削除するフローの requirements の例です。
dci export-cloudflow-flow G2zdE9inbvwxBc38FCVc > oci-flow.json
jq '.requirements' oci-flow.json
{
"connections": [
{
"key": "omni-oci",
"name": "omni-oci",
"provider": "Oracle",
"usedByNodes": [
"G2zdE9inbvwxBc38FCVc/delete-resources",
"G2zdE9inbvwxBc38FCVc/list-resources"
]
}
]
}
ドライランは、各 requirement をターゲットテナントに対して解決し、候補を提案します。
dci import-cloudflow-flow --dry-run < oci-flow.json | jq '.result'
{
"errors": [],
"flowsToCreate": [
{
"incompleteNodeCount": 2,
"key": "G2zdE9inbvwxBc38FCVc",
"name": "Draft Delete unused Oracle Cloud reserved public IPs",
"nodeCount": 4
}
],
"requirements": [
{
"candidates": [
{
"id": "CWGMz3ndg9WihPZnUXAi",
"name": "omni-oci",
"provider": "Oracle"
}
],
"consequenceIfUnbound": "referencing nodes are imported with the connection cleared and flagged incomplete",
"key": "omni-oci",
"resolution": "suggested",
"section": "connections"
}
],
"valid": true
}
各 requirement には resolution が付きます。
-
bound: あなたがバインディングを指定しており、検証も通過しています。 -
suggested: ターゲットテナントに妥当と思われる候補があり、その一覧がcandidatesに示されています。suggestion は自動では適用されないため、明示的にバインドしてください。 -
willCreate: Import がリソースを新規作成します。global variables はこの方法で解決され、options.createMissingTablesを指定した場合の Datastore テーブルも同様です。 -
unbound: 一致するものがありません。インポート自体は成功しますが、consequenceIfUnboundが、その後に修正が必要な内容を示します。この例では、2 つのノードが接続先をクリアした状態で到着することを意味します。
requirement をバインドするには、生のバンドルではなく、完全なリクエスト形状を送信します。キーはプランから取得し、ID はターゲットテナントから取得します(dci list-cloudflow-connections)。
jq '{bundle: ., bindings: {connections: {"omni-oci": "CWGMz3ndg9WihPZnUXAi"}}}' oci-flow.json \
| dci import-cloudflow-flow --dry-run | jq '.result.requirements[0]'
{
"boundTo": "CWGMz3ndg9WihPZnUXAi",
"consequenceIfUnbound": "referencing nodes are imported with the connection cleared and flagged incomplete",
"key": "omni-oci",
"resolution": "bound",
"section": "connections"
}
同じリクエスト形状にインポートオプションも含めます。options.createMissingTables: true は、バンドル内のスキーマから不足している Datastore テーブルを作成します(構 造のみで、行データは含まれません)。options.namePrefix は、インポートによって作成されるすべてのフロー名の先頭に付与されます。
jq '{bundle: ., options: {namePrefix: "Copy of "}}' after-hours-sweep.json \
| dci import-cloudflow-flow --dry-run | jq '.result.flowsToCreate[0]'
{
"incompleteNodeCount": 0,
"key": "EBnC9oHaZpcH3hgWGoSp",
"name": "Copy of After-Hours Instance Sweep",
"nodeCount": 7
}
フローをインポートする
プランに問題がなければ、--dry-run を外し、冪等性キーを追加します。イン ポートごとに新しいキーを生成してください。
dci import-cloudflow-flow --idempotency-key "$(uuidgen)" < after-hours-sweep.json
{
"flows": [
{
"id": "YT57P7OhP2nLwZMo9QSw",
"key": "EBnC9oHaZpcH3hgWGoSp",
"name": "After-Hours Instance Sweep",
"status": "draft"
}
]
}
key はバンドル内でのフロー ID、id はターゲットテナント内での新しいフロー ID です。Import は作成専用であり、呼び出しのたびに新しいドラフトフローを新しい ID で作成し、既存のフローを更新することはありません。同じ冪等性キーを再送すると 2 つ目のコピーは作られず、代わりに同じレスポンスが返されます。そのため、中断されたインポートを安全に再試行できます。
インポートされたフローはドラフトとして到着します。誰かがコンソールで公開するまでは、何も実行されず、スケジュールも有効になりません。
別のテナントへインポートする
テナント間でのコピーも、各コマンドにcustomer context を付与する以外は同じ 2 つのコマンドです。ソースからエクスポートし、ターゲットにインポートします。
dci export-cloudflow-flow EBnC9oHaZpcH3hgWGoSp -D <source-customer-id> > after-hours-sweep.json
dci import-cloudflow-flow --idempotency-key "$(uuidgen)" -D <target-customer-id> < after-hours-sweep.json
テナントをまたぐコピーでは、requirements に特に注意が必要です。元のテナントでは suggested として解決していた接続が、ターゲットテナントでは unbound になる場合があります。本番インポートの前にターゲットに対してドライランを行い、プランが列挙した items をバインドしてください。
CloudFlow のコマンドで -D に渡すのは customer の ID であり、ドメインや URL 表示名ではありません。他のコマンドは 3 種類すべてを受け付けます。
次のステップ
-
コンソールでインポートしたフローを開き、すべてのノードが完了していることを確認してください。requirements が unbound のまま残ったノードにはフラグが付いています。
-
バンドルでは移動できないものを再接続してください。未バインドのままにした connections、Slack チャンネル、ポリシー参照などです。
-
ターゲットテナントで必要なスケジュールまたはトリガーを設定し、その後フローを公開してください。詳細は Manage a flow を参照してください。