Portで始めるInternal Developer Portal (1) : Action

に公開

先端技術開発グループ(WAND)の小島です。以前、Port.io(Port)という、Internal Developer PortalIDP)のツールがどのようなことができるのか、どのように使えばいいのかなどの、導入に向けた技術検証を行いました。この際、Portの公式以外のドキュメントがほとんどなく、理解するのに時間を要したため、数回に分けてPortの使い方を解説していきたいと思います。

Internal Developer Portal(IDP)とは

Internal Developer Portal(IDP)は、企業内の開発者が必要とするツール、ドキュメント、API、サービスカタログなどを一元的に提供する社内向けのWebサイトです。開発者がインフラを意識せず、セルフサービスで迅速に開発環境を整えたり、ソフトウェアの情報を検索したりできるようにすることで、開発体験と生産性を向上させることを目的としています。

IDPと呼ばれるものには、Internal Developer Platformと呼ばれるものもあります。「Internal Developer Platform」がツール群やインフラ全体(基盤)を指すのに対し、「Internal Developer Portal」は開発者が実際に触るWebサイト(インターフェース)を指します。今回は「Internal Developer Portal」の文脈で書きます。

代表的な例が、Spotifyが開発し、現在はオープンソース(OSS)となっている「Backstage」です。SpotifyはBackstageを社内ポータルとして利用し、マイクロサービスの管理、ドキュメントの集約、新しいサービスの立ち上げ(スキャフォールディング)などを簡素化しています。

参考文献

なぜBackstageではなくPortなのか

IDPというとOSSのBackstageを採用する例が多くありますが、今回はあえてBackstageを採用しませんでした。理由は、Backstageでは機能の追加・変更のたびにTypeScriptによる「フロントエンド」の開発が必要です。IDP専任の開発者を複数人配置する必要があり、人件費を考慮するとコストパフォーマンスが見合わないだろうという試算があったためです。英語記事ですが、BackstageのTCO(総保有コスト)についてはこちらに議論があり、数年で数百万ドルに達する可能性があることが指摘されています。もっと大規模な会社で利用される場合、専任の人を配置しても釣り合いが取れる可能性はあります。

その代わりに、Backstageをベースとしてサービス化したPort.io(Port)について導入の検証を行いました。Portでは、フロントエンドは既にサービス側で用意されているため、開発の負担はありません。代わりに、GitHub ActionsやJira、AWSといった社内ツールやインフラとの連携部分のみ、開発を行うだけで済みます。また、その連携もある程度、Port側で用意されています。

Portのデモ画面

こちらがPortのデモ画面です。登録なし、無料でアクセスできます。以下のShowcaseにアクセスすることで、どのようなものか理解することができます。

Portの用語

Portにはたくさんの用語が出てきますが、とりあえず以下の5つをなんとなく理解しておけばOKです。

  • Self-serviceセルフサービス) : 開発者がUIなどを介して、サービスのスキャフォールディングやクラウドリソースのプロビジョニングなどを手動で実行できる仕組みです。事前に設定されたテンプレートやワークフローを利用し、ガードレール(承認やポリシー)の範囲内で、直感的かつ安全に作業を進められます。
  • Actionアクション) : Portで実行できる操作の単位です。サービス作成やデプロイ、インフラ構築など、ユーザーが手動で実行する「セルフサービスアクション」と、イベントをトリガーに自動実行される「オートメーション」の2種類があります。Actionには入力項目・実行ロジック・承認フローなどを柔軟に定義でき、実行結果はカタログに反映されます。
  • Catalogカタログ) : 組織内のソフトウェア資産(マイクロサービス、API、クラウドリソース、CI/CDパイプライン等)を一元管理するリアルタイムなビューです。エンティティ間の関係性の可視化や、強力な検索・フィルタリング機能を備えています。組織の「ソース・オブ・トゥルース(信頼できる唯一の情報源)」として機能します。
  • Blueprintブループリント) : カタログ内の資産タイプ(例:サービス、環境、チームなど)の構造・プロパティ・関連性を定義するデータモデルです。すべてのエンティティは、必ずいずれかのブループリントを元に作成されます。
  • Entityエンティティ) : ブループリントを元に作成された「実体」です。カタログ内で一意のIDとプロパティを持つ、具体的なマイクロサービス、環境、ユーザーなどを指します。他のエンティティと関連付けることも可能です。

今回は「Self-Service」の「Action」の最も簡単な例を実装してみます。具体的には、「ボタンを押すとS3バケットやAPIキーが発行される」ようなイメージのものがActionです。

今回は、入力したメッセージをGitHub Actions(バックエンド)に表示させるPortの「Action」を定義する例を、この記事の末尾で紹介します。まずはPortのActionの用語を解説します。

Actionの種類(Create / Day-2 / Delete)

デモ画面を見ると、PortのActionには「Create」「Day-2」「Delete」の3種類があることがわかります。

公式には以下のように定義されています。

  • CreateCreate Actions) : 新しいエンティティを作成するアクションです。既存のエンティティに紐づかず、新規作成時に利用します。
  • Day-2Day-2 Operations) : 既存エンティティの更新や運用タスク(例:設定変更、スケール、再起動など)を実行するアクションです。
  • DeleteDelete Actions) : 既存エンティティを削除するアクションです。インフラやカタログからリソースを削除する際に利用します。

エンティティという概念が絡むのが理解しづらいですが、以下のように考えるのがわかりやすいです。

  • Create : リソースの新規作成
  • Day-2 : 既存リソースの変更
  • Delete : 既存リソースの削除

ここでのリソースとは、「GitHubのリポジトリ」や「EC2やS3バケット」といったクラウドリソースだけではなく、「JiraのIssue」や「インシデントのレポート」なども含みます。

エンティティとは、これらのリソースをPort上で表現し、ステートフルに管理するためのアイテムです。実質的には、「Create / Day-2 / Delete」は一般的なアプリケーションにおけるCRUD(Create, Read, Update, Delete)に相当します。

今回は、「Hello, World」に相当するActionを定義するため、エンティティとは無関係のものを定義します。したがって、新規作成であるCreateのActionを定義します。

サンプルActionのアーキテクチャ図

今回作るActionのアーキテクチャ図は以下の通りになります。

  1. ユーザーがActionを実行する際に、「Hello, World」というテキストを入力して開始したとします
  2. PortがActionに紐づいているリポジトリのGitHub Actionsを起動します。
    (※事前に、このリポジトリへのPortのGitHub Appのインストールが必要です)
  3. GitHub Actions上で、ユーザーが入力した「Hello, World」が展開されて表示されます。

Action実行時にPortから起動されるもの(今回はGitHub Actions)をバックエンドといいます。今回はGitHub Actionsがバックエンドとなります。

バックエンドはGitHub Actions以外も選択可能で、GitLabやAzureのパイプライン、単純なWebhookや、Slackへのメッセージの送信も可能です。GitHub Actionsは追加の設定やデプロイなしで汎用的な操作を実行できるため、今回はGitHub Actionsで解説します。

事前準備:リポジトリにPortのAppをインストールする

PortからGitHub Actionsを起動するには、ターゲットのリポジトリにPortのGitHub Appのインストールが必要です。この他にも今後のエンティティの管理に便利なので、Portと紐づけるリポジトリには基本入れておいたほうが良いです。

このインストールは以下のようにします。GitHubのOrganizationの管理者権限が必要な場合があります。もしテスト用途で使うのであれば、別途Organizationを立ち上げたほうがいいかもしれません。

公式ドキュメントのGitHubのセットアップページを参照してください。

  1. GitHub App ページに移動します。
  2. Configure(設定)ボタンをクリックします。
  3. アプリをインストールする組織を選択します。
  4. 選択した組織内で、アプリをインストールするリポジトリを選択します。
  5. Install(インストール)ボタンをクリックします。
  6. インストールが完了すると、Port にリダイレクトされます。

インストールが完了すると、「GetPort.io」がリポジトリの「GitHub Apps」に表示されているはずです(※テスト用に作ったリポジトリです)。

Actionの作成

Actionの作成は、ブラウザ上のPortのコンソールやTerraformなどからも作成できます。今回は両方を解説します。

ブラウザ上のコンソールから作成

「Self-service」から、「+ Action」をクリックします。

1. Basic Details

「Basic Details」ではActionの基本情報を入力します。以下を入力し「Next」をクリック。

  • Title : Tutorial 01: Hello World
  • Identifier : port_tutorial_action_01
  • Description : Echo action for tutorials
  • Icon : (Actionという名前の矢印のアイコンを選択)
  • Operation : Createを選択

「Title」はダッシュボードに表示される名前、「Identifier」はPortの中で使われる一意の識別子です。Identifierはデフォルトでは自動生成(Autogenerate)になっていますが、競合を避けるためにも何らかの入力規則をもって入れたほうがいいのではないかと思われます。「Description」はActionの説明です。「Icon」はダッシュボードに表示されるアイコンの種類です。「Operation」は先程の「Create / Day-2 / Delete」のいずれかを選択し、今回は「Create」を選択します。その他は入力不要です。

2. User Form

「User Form」では、ユーザーが入力する値の種類や規則を定義します。「Input」をクリックします。

今回はシンプルなメッセージを入力するだけのテキストボックスを作りたいので、以下のように入力します。

  • Title : Message
  • Type : Textを選択
  • Required : Trueを選択
  • Description : Message to display in GitHub Actions
  • Icon : (Docsというドキュメントのアイコンを選択)

「Title」は入力値の名前、「Type」はテキストや数値などの入力値の種類、「Required」は必須かどうか(Trueなら必須)、「Description」は入力値の説明、「Icon」は入力値のアイコンです。

「Message」が追加されたので、「Next」をクリックします。

3. Backend

「Backend」ではActionが実行されたときに、次に動かすロジックの実体を指定します。ここではGitHub Actionsとします。

  • Invocation Type : Run GitHub Workflowを選択
  • Organization : (GitHubの組織を入力)
  • Repository : (GitHubのリポジトリ名を入力)
  • Workflow file name : tutorial_01_hello_world_action.yml
  • Report workflow status : Yes
  • Configure the invocation payload : 以下を入力
{
  "port_context": {
    "run_id": "{{.run.id}}",
    "{{ spreadValue() }}": "{{ .inputs }}"
  }
}

「Invocation Type」は、バックエンドをどれにするかを指定します。「Run GitHub Workflow」がGitHub Actionsで、他にはJenkins、GitLab、AzureなどのWorkflowを選択できます。

「Organization」と「Repository」はGitHubの組織、リポジトリ名を入力します。「Workflow file name」は、実行するGitHub ActionsのYAMLファイルを指定し、リポジトリの.github/workflows/以下にあるファイル名を指定します。ここで、tutorial_01_hello_world_action.ymlと入力した場合、YAMLファイルはリポジトリ内では.github/workflows/tutorial_01_hello_world_action.ymlにある必要があります。YAMLファイルの具体的な記述は、後ほど解説します。

「Report workflow status」は「Yes」にすることで、実行結果がPortから確認できます。成功/失敗がわかるのでYesにしておくことを推奨します。

「Configure the invocation payload」は入力変数や、ActionのメタデータをどのようにGitHub Actionsに渡すかを定義します。ここではport_contextというキー以下に、run_idというActionの実行IDと、Actionの入力値(この場合はmessageのみ)を展開して渡します。"{{ spreadValue() }}": "{{ .inputs }}"と書くことで、Actionのフォーム上の複数の入力値を動的に渡すことができます。

4. Permissions

「Permissions」では、実行権限や承認を必要とするかを設定できます。今回はデフォルトのままで、誰でも実行可能・承認不要とします。その後「Create」をクリック。

以下のようにActionができました!

Terraformから作成

Portには公式のTerraformのプロバイダーも用意されており、ActionもTerraformから作成できます。

認証情報の取得

Terraformに渡すためのPortの認証情報を取得します。ブラウザ上のコンソールから、右上の「…」で「Credentials」をクリック。

「Client ID」と「Client Secret」を使用します。これはPortの無料版のUIで、有料版や今後のアップデートではUIが変わる可能性があります。また、組織の権限設定によっては、OrganizationのAPIキーではなく、ユーザーのAPIキーが必要になる場合もあります。

認証情報をTerraformの実行環境(ローカルPCなど)の環境変数に登録します。環境変数に登録することで、Portの認証情報がTerraformに自動的に渡されます。

export PORT_CLIENT_ID=<YOUR_PORT_CLIENT_ID>
export PORT_CLIENT_SECRET=<YOUR_PORT_CLIENT_SECRET>

Terraformの記述

プロバイダー側の設定は以下のように記述します。認証情報は環境変数から自動的に展開されるため、明示的な記述は不要です。

terraform {
  required_providers {
    port = {
      source  = "port-labs/port-labs"
      version = "~> 2.0.3"
    }
  }
}

provider "port" {
  base_url = "https://api.getport.io"
}

具体的なActionの定義は以下の通りです。これは先程ブラウザから指定したものと同様の設定をTerraformで記述したものです。

tutorial_01_hello_world.tf
resource "port_action" "hello_world" {
  title       = "Tutorial 01: Hello World"
  icon        = "Actions"
  description = "Echo action for tutorials"
  identifier  = "port_tutorial_action_01"
  self_service_trigger = {
    operation = "CREATE"
    user_properties = {
      string_props = {
        message = {
          title       = "Message"
          description = "Message to display in GitHub Actions"
          icon        = "Docs"
          required    = true
        }
      }
    }
  }

  github_method = {
    org      = "<your-github-organization>"
    repo     = "<your-github-repository>"
    workflow = "tutorial_01_hello_world_action.yml"
    workflow_inputs = jsonencode({
      port_context : {
        run_id : "{{.run.id}}"
        "{{ spreadValue() }}" : "{{ .inputs }}"
      }
    })
    report_workflow_status = true
  }
}

terraform initterraform applyを実行すると、同様にActionが作成されます。

GitHub Actionsの記述

次にバックエンドとして実行されるGitHub Actionsを記述します。これはActionの設定における「Workflow file name」(Terraformの場合は、github_method -> workflow)に対応します。

このワークフローファイルは、.github/workflowsディレクトリの下に、先程Action作成時に指定したファイル名(この例では、tutorial_01_hello_world_action.yml)で作成する必要があります。

具体的なコードは以下の通りになります。

tutorial_01_hello_world_action.yml
name: Tutorial 01 - Hello World Action

on:
  workflow_dispatch:
    inputs:
      port_context:
        description: 'Input values from Port'
        required: false
        type: string

jobs:
  run-echo:
    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: Display message from Port context
        run: |
          echo "Displaying message from context:"
          
          # PORT_CONTEXTは文字列化されたJSONなので、-R 'fromjson'でまずJSONとして解釈
          # その後、.messageでmessageフィールドを抽出します
          # -r オプションで引用符(")を外してRAWテキストとして出力します
          MESSAGE=$(echo "$PORT_CONTEXT" | jq -R 'fromjson | .message' -r)
          
          echo "$MESSAGE"

基本的な考え方としては、workflow_dispatchの入力引数に文字列(port_context)として受け取り、jqなどでJSONをパースしてコードを実行していきます。パースした後は、後続のステップで利用しやすいようにGitHub Actions内の環境変数に登録しておくと便利でしょう。

workflow_dispatchinputsに複雑なJSONオブジェクトを渡す際、GitHub Actionsはそれを文字列として扱います。そのため、ワークフロー側でjq -R 'fromjson'のように、一度文字列からJSONに戻す処理が必要になります。

ワークフローのロジックが複雑になり、シェルでのパースに限界がある場合は、一度環境変数に登録してからPythonコードで処理する、といった設計も有効です。

Actionを実行してみる

作成したActionを実行してみましょう。作成したActionの「⚡️Create」をクリックします。

「Message」のテキストボックスは先程Actionの定義で作成したものです。「Hello, World」と入力します。

実行すると対象のGitHubリポジトリでGitHub Actionsが起動します。ワークフローはPortのアプリから起動しています。

実行結果の詳細を見ると、Portからの入力値が展開され、「Displaying message from context」に「Hello, World」と表示されました! Port側の入力値を変えればここの値も変わります。

以上で、PortのActionの作成、GitHub Actionsの連携のミニマムな例ができました。

まとめ

この記事では、フロントエンド開発が不要なInternal Developer Portal(IDP)ツール「Port」の導入を解説しました。具体的な最初のステップとして、ユーザーが入力したメッセージをGitHub Actionsに渡して実行させる「Self-service Action」の作成方法を、ブラウザとTerraformの両方で紹介しました。

次回以降では、Actionの状態を記録し、ステートフルにするための「Blueprint」や「Entity」について説明できたらと思います。2025年10月現在、Portの日本語解説はないに等しいので、この記事が皆様の何らかの参考になれば幸いです。

エクサウィザーズ Tech Blog

Discussion