Portで始めるInternal Developer Portal (2) : Blueprint、Entity
先端技術開発グループ(WAND)の小島です。以前、Port.io(Port)という、Internal Developer Portal(IDP)のツールの技術検証を行いました。数回に分けてPortの使い方を解説していきたいと思います。今回はその2回目の記事で、BlueprintとEntityを中心に説明します。
以前の記事はこちら
(1)では、Portの概要とSelf-service Actionを解説し、「Hello, World」と表示されるActionを実装しました。
ステートフルなAction
Self-service ActionはGitHub Actionsと連携し、Terraform等であらゆるリソースをデプロイ可能です。
しかし、これは「投げっぱなし(ステートレス)」の方法であり、デプロイしたリソースをPort側で管理することができません。これをステートフルにし、Port側で管理するには、今回紹介するBlueprintとEntityを利用します。
BlueprintとEntity
(1)の記事からの用語解説です。
- Blueprint(ブループリント) : カタログ内の資産タイプ(例:サービス、環境、チームなど)の構造・プロパティ・関連性を定義するデータモデルです。すべてのエンティティは、必ずいずれかのブループリントを元に作成されます。
- Entity(エンティティ) : ブループリントを元に作成された「実体」です。カタログ内で一意のIDとプロパティを持つ、具体的なマイクロサービス、環境、ユーザーなどを指します。他のエンティティと関連付けることも可能です。
一言でいえば、Blueprintはオブジェクト指向におけるクラス、Entityはインスタンスに相当します。

Port画面の例です。「EC2 Instances」というBlueprintに対し、「my-web-server」「my-db-server」の2つのEntityを追加しています。
Blueprintはプロパティ(EC2インスタンスの名前・タイプ・状態)の定義のみを行い、具体的な値はEntity側で保持・管理します。
IdentifierはPort上の一意なIDで、Blueprintの定義に関わらず必要です。なお、今回はEntityのIdentifierは自動生成の値を使用しています。
TerraformでBlueprintを生成する
Blueprintはブラウザからも定義可能ですが、Terraformで書いたほうがわかりやすいです。BlueprintだけをTerraformで定義すると、以下のコードになります。
tutorial_02_ec2_blueprint.tf
resource "port_blueprint" "ec2_instance" {
title = "EC2 Instance"
icon = "EC2"
description = "Blueprint for managing EC2 instance information"
identifier = "blueprint_ec2_instance"
properties = {
string_props = {
"instance_name" = {
title = "Instance Name"
description = "Name of the EC2 instance"
required = true
}
"instance_type" = {
title = "Instance Type"
description = "Type of the EC2 instance (e.g., t2.micro, t3.medium)"
required = true
}
"instance_state" = {
title = "Instance State"
description = "Current state of the EC2 instance"
required = true
enum = [
"running",
"stopped",
"pending",
"stopping",
"terminated"
]
enum_colors = {
"running" = "green"
"stopped" = "red"
"pending" = "yellow"
"stopping" = "orange"
"terminated" = "darkGray"
}
}
}
}
}
-
identifier:BlueprintのIdentifierで、Entityとは別に設定が必要です。Blueprint間で連携するときに使用します。 -
properties.string_props: プロパティを定義します。自由記述以外に、instance_stateのような選択肢(Enum型)も可能です。
terraform applyすると、以下の画面になります。

Entityの追加:ブラウザから
作成したBlueprintへ、ブラウザからEntity「my-web-server」を追加してみましょう。 これはPort上への登録のみで、実際のAWS環境にEC2インスタンスが作成されるわけではありません。
Blueprintの右の「+EC2 Instance」をクリック。

各項目を入力します。
- Instance Name :
my-web-server - Instance State :
stopped - Instance Type :
t3.nano

「Register」をクリックすると、Entityが登録されました。

Entityの追加:Terraformから
TerraformでのEntityの追加も可能です。my-db-serverというEntityを追加してみます。
tutorial_02_ec2_blueprint.tf
resource "port_entity" "my_db_server" {
# identifierを指定しない場合は自動生成
title = "my-db-server"
blueprint = port_blueprint.ec2_instance.identifier
properties = {
string_props = {
"instance_name" = "my-db-server"
"instance_type" = "r7i.large"
"instance_state" = "running"
}
}
}
ブラウザから作成したmy-web-server、Terraformから作成したmy-db-serverの2つのEntityが表示されました。

ActionからEntityを作成する
Actionの定義上、EC2作成などの実行後にはEntityも作成されるのが自然な流れです。Action完了と同時にEntityを自動作成するには、以下の2つの方法があります。
- GitHub Actionsの中でEntityを作成する
- メリット:同期的に処理が見える。細かい制御が書きやすい。
- デメリット:Portとの結合度が高まり、API Keyをリポジトリで管理する必要がある。
- Automationを利用する
- メリット:Portの中でロジックが完結できる。「Actionが成功したら」というイベント駆動で疎結合にできる。
- デメリット:PortのTerraformの設定が増える。
ActionによるEntityの作成:(1) GitHub Actions内で作成する
最も簡単なActionとEntityの連携は、GitHub Actions内でEntityを作成してしまうことです。
GitHub ActionsからEntityを作成するには、Port公式が提供しているport-github-actionを利用すると便利です。PortのActionの定義を変更する必要はありません。
※事前にリポジトリのシークレットに、PORT_CLIENT_IDとPORT_CLIENT_SECRETの登録が必要です。
- name: Create EC2 Instance Entity in Port
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: UPSERT
identifier: ${{ env.RUN_ID }}
title: ${{ env.INSTANCE_NAME }}
blueprint: blueprint_ec2_instance
properties: |-
{
"instance_name": "${{ env.INSTANCE_NAME }}",
"instance_type": "${{ env.INSTANCE_TYPE }}",
"instance_state": "${{ env.INSTANCE_STATE }}"
}
my-ml-serverという名前でActionを実行完了後に、Entityが作成されます。

GitHub Actionの全体コードは以下の通り。
tutorial_02_create_ec2_instance_action.yml
name: Tutorial 02 - Create EC2 Instance Action
on:
workflow_dispatch:
inputs:
port_context:
description: 'Input values from Port'
required: false
type: string
jobs:
create-ec2-instance-entity:
runs-on: ubuntu-latest
steps:
- name: Install jq
run: sudo apt-get update && sudo apt-get install -y jq
- name: Parse port_context JSON into env var
env:
PORT_CONTEXT: ${{ github.event.inputs.port_context }}
run: |
# 文字列化されたJSONを一度deserializeしてから整形
PARSED=$(echo "$PORT_CONTEXT" | jq -R 'fromjson')
echo "── parsed JSON ──"
echo "$PARSED"
# JSONを環境変数に登録(後の処理で使用)
echo 'PORT_CONTEXT<<EOF' >> $GITHUB_ENV
echo "$PORT_CONTEXT" >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Extract EC2 instance properties from Port context
run: |
echo "Extracting EC2 instance properties from context:"
# PORT_CONTEXTは文字列化されたJSONなので、-R 'fromjson'でまずJSONとして解釈
INSTANCE_NAME=$(echo "$PORT_CONTEXT" | jq -R 'fromjson | .instance_name' -r)
INSTANCE_TYPE=$(echo "$PORT_CONTEXT" | jq -R 'fromjson | .instance_type' -r)
INSTANCE_STATE=$(echo "$PORT_CONTEXT" | jq -R 'fromjson | .instance_state' -r)
RUN_ID=$(echo "$PORT_CONTEXT" | jq -R 'fromjson | .run_id' -r)
echo "Instance Name: $INSTANCE_NAME"
echo "Instance Type: $INSTANCE_TYPE"
echo "Instance State: $INSTANCE_STATE"
echo "Run ID: $RUN_ID"
# 環境変数として保存
echo "INSTANCE_NAME=$INSTANCE_NAME" >> $GITHUB_ENV
echo "INSTANCE_TYPE=$INSTANCE_TYPE" >> $GITHUB_ENV
echo "INSTANCE_STATE=$INSTANCE_STATE" >> $GITHUB_ENV
echo "RUN_ID=$RUN_ID" >> $GITHUB_ENV
- name: Create EC2 Instance Entity in Port
uses: port-labs/port-github-action@v1
with:
clientId: ${{ secrets.PORT_CLIENT_ID }}
clientSecret: ${{ secrets.PORT_CLIENT_SECRET }}
baseUrl: https://api.getport.io
operation: UPSERT
identifier: ${{ env.RUN_ID }}
title: ${{ env.INSTANCE_NAME }}
blueprint: blueprint_ec2_instance
properties: |-
{
"instance_name": "${{ env.INSTANCE_NAME }}",
"instance_type": "${{ env.INSTANCE_TYPE }}",
"instance_state": "${{ env.INSTANCE_STATE }}"
}
ActionによるEntityの作成:(2) Automationの利用
PortのAutomationは、カタログ上のイベント(Entity作成・更新等)をトリガーに、処理を自動実行する仕組みです。手動の「Action」とは異なり、定義したルールに基づきバックエンドが自動的に呼び出されます。 なお、Terraformの実装においては、ActionもAutomationも同じport_actionを利用します。
Entityを作成するには、Actionが成功したら実行されるAutomationを定義します。

- Port上のBlueprintからActionを実行します。
- バックエンドのGitHub Actionsが起動します。
- GitHub Actionsの成功通知がPortに飛びます。
- それをトリガーとして、Automationが実行されます。
- AutomationがEntityを作成します。
Actionが成功したら実行される、AutomationのTerraformコードは以下のようになります。
resource "port_action" "create_ec2_entity_automation" {
title = "Tutorial 02: Auto-Create EC2 Entity"
icon = "EC2"
description = "Automation to create EC2 entity after action completes"
identifier = "port_tutorial_automation_02"
automation_trigger = {
any_run_change_event = {
action_identifier = port_action.create_ec2_instance.identifier
}
jq_condition = {
expressions = [
".diff.after.status == \"SUCCESS\""
]
combinator = "and"
}
}
upsert_entity_method = {
blueprint_identifier = port_blueprint.ec2_instance.identifier
mapping = {
identifier = "{{.event.diff.after.id}}"
title = "{{.event.diff.after.properties.instance_name}}"
properties = jsonencode({
instance_name = "{{.event.diff.after.properties.instance_name}}"
instance_type = "{{.event.diff.after.properties.instance_type}}"
instance_state = "{{.event.diff.after.properties.instance_state}}"
})
}
}
}
※ 解説の都合上、今回は実行IDをEntity IDとしています。実運用ではリソース固有のIDを使うことを推奨します。これにより、同一リソースへの更新(Upsert)が正しく管理できます。
any_run_change_eventで監視対象のActionの状態が変わったらトリガーします。ただ、これだと起動直後にEntityが作成されてしまうので、jq_conditionで成功時(SUCCESS)のみに限定しています。
Entityへのマッピングは、イベントデータ以下のプロパティ(.event.diff.after.properties)を利用しています。このコードでは、EntityのIdentifierは、Actionの実行IDになります。
名前:my-cache-server、状態:running、タイプ:t4g.mediumでActionを実行します。完了すると、以下のEntityが作成されます。

また、デプロイしたAutomationは、「Builder」→「Automations」で確認できます。

GitHub Actionsのコードは以下の通りで、値を表示しているだけなのでなくても成立します。後ほどEC2デプロイ処理をここに追加します。
tutorial_02_create_ec2_instance_action.yml
name: Tutorial 02 - Create EC2 Instance Action
on:
workflow_dispatch:
inputs:
port_context:
description: 'Input values from Port'
required: false
type: string
jobs:
create-ec2-instance-entity:
runs-on: ubuntu-latest
steps:
- name: Install jq
run: sudo apt-get update && sudo apt-get install -y jq
- name: Parse port_context JSON into env var
env:
PORT_CONTEXT: ${{ github.event.inputs.port_context }}
run: |
# 文字列化されたJSONを一度deserializeしてから整形
PARSED=$(echo "$PORT_CONTEXT" | jq -R 'fromjson')
echo "── parsed JSON ──"
echo "$PARSED"
# JSONを環境変数に登録(後の処理で使用)
echo 'PORT_CONTEXT<<EOF' >> $GITHUB_ENV
echo "$PORT_CONTEXT" >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Extract EC2 instance properties from Port context
run: |
echo "Extracting EC2 instance properties from context:"
# PORT_CONTEXTは文字列化されたJSONなので、-R 'fromjson'でまずJSONとして解釈
INSTANCE_NAME=$(echo "$PORT_CONTEXT" | jq -R 'fromjson | .instance_name' -r)
INSTANCE_TYPE=$(echo "$PORT_CONTEXT" | jq -R 'fromjson | .instance_type' -r)
INSTANCE_STATE=$(echo "$PORT_CONTEXT" | jq -R 'fromjson | .instance_state' -r)
RUN_ID=$(echo "$PORT_CONTEXT" | jq -R 'fromjson | .run_id' -r)
echo "Instance Name: $INSTANCE_NAME"
echo "Instance Type: $INSTANCE_TYPE"
echo "Instance State: $INSTANCE_STATE"
echo "Run ID: $RUN_ID"
# 環境変数として保存
echo "INSTANCE_NAME=$INSTANCE_NAME" >> $GITHUB_ENV
echo "INSTANCE_TYPE=$INSTANCE_TYPE" >> $GITHUB_ENV
echo "INSTANCE_STATE=$INSTANCE_STATE" >> $GITHUB_ENV
echo "RUN_ID=$RUN_ID" >> $GITHUB_ENV
DAY-2, Delete Action
今までは新規にリソースを作成するCreate Actionだけでしたが、変更(DAY-2)と削除(Delete)も別のActionや、任意のバックエンドで定義可能です。
変更点としては、self_service_triggerのoperationを変えるだけです。バックエンドの処理では、Entityが二重に作られないように、Identifierを調整する必要があるかもしれません。
resource "port_action" "update_ec2_instance" {
title = "Tutorial 02: Update EC2 Instance"
# 略
self_service_trigger = {
operation = "DAY-2"
# ... 略 ...
}
基本はこれまでのAction定義なので詳細は割愛します。
まとめ
この記事では、Portのリソース管理の核となる「Blueprint」と「Entity」の概念を説明し、Terraformを用いた実装方法を解説しました。さらに、GitHub ActionsやAutomation機能を活用し、リソース作成Actionの実行と連動してEntityを自動登録・管理する手順を紹介しました。
もし次回があれば、「Exporters」についても書く予定です。
Portを使うという機会は少ないかもしれませんが、皆様の参考になれば幸いです。
Discussion