継続は力なり

タイトル通り定期的な更新を心掛けるブログです。

Bytebase API を実行するワークフロー を Step Functions で構築する

タダです.

前回の記事で Bytebase の API を実行するコマンドを紹介しました.この記事ではローカルから curl で実行していたのですが,自動化して実行できるように Step Functions のサードパーティ API 実行を試してみます.

sadayoshi-tada.hatenablog.com

Step Functions から Bytebase API を実行する準備

API を実行する時に必要な準備を行います.以下,3つを行います.

  • サービスアカウント及びキーの発行
  • サービスアカウントキーの Secrets Manager への格納
  • EventBridge Connection の追加

サービスアカウントキーの発行

事前準備としてサービスアカウントのユーザーを作ります.Workspace Admin か DBA のロールを付与する必要があるため,DBAを設定して作成しました.作成後にサービスアカウントキーをコピーしておきます.

サービスアカウントキーの Secrets Manager への格納

発行したサービスアカウントキーを Secrets Manager に入れていきます.加えて,Step Functions の IAM ロールに読み取りアクセス許可を付与します.

EventBridge Connection の追加

Step Functions から API を叩く時に Authorization ヘッダーを付ける必要があるのですが,Step Functions の API 実行する時に Authorization は設定できない仕様になっているため EventBridge Connection を用意します.

curl で実行したときの例

# List projects
curl --request GET ${bytebase_url}/v1/projects \
  --header 'Authorization: Bearer '${bytebase_token}

www.bytebase.com

以下のような感じで設定します.値は Bearer dummy を初期設定として入れています.

Step Functions でワークフローを構築する

準備が完了したら,ワークフローを構築していきます.まずは結論ですが,以下の ASL を作りました.

{
  "Comment": "Bytebase API exec test",
  "StartAt": "GetBytebaseServiceAPIUserCredential",
  "States": {
    "GetBytebaseServiceAPIUserCredential": {
      "Type": "Task",
      "Parameters": {
        "SecretId": "[サービスアカウントキーを格納した Secrets Manager 名]"
      },
      "Resource": "arn:aws:states:::aws-sdk:secretsmanager:getSecretValue",
      "Next": "Call Bytebase Authrization API"
    },
    "Call Bytebase Authrization API": {
      "Type": "Task",
      "Resource": "arn:aws:states:::http:invoke",
      "Parameters": {
        "ApiEndpoint": "https://[Bytebaseの外部アクセス URL]/v1/auth/login",
        "Authentication": {
          "ConnectionArn": "[EventBridge Connection の ARN]"
        },
        "Method": "POST",
        "RequestBody": {
          "email": "[サービスアカウントのアドレス]",
          "password.$": "$.SecretString",
          "web": true
        }
      },
      "Retry": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "BackoffRate": 2,
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "JitterStrategy": "FULL"
        }
      ],
      "ResultSelector": {
        "token.$": "States.StringSplit($.ResponseBody.token, ':')"
      },
      "OutputPath": "$.token[0]",
      "Next": "UpdateConnection"
    },
    "UpdateConnection": {
      "Type": "Task",
      "Parameters": {
        "Name": "[EventBridge Connection 名]",
        "AuthorizationType": "API_KEY",
        "AuthParameters": {
          "ApiKeyAuthParameters": {
            "ApiKeyName": "Authorization",
            "ApiKeyValue.$": "States.Format('Bearer {}',$)"
          }
        }
      },
      "Resource": "arn:aws:states:::aws-sdk:eventbridge:updateConnection",
      "Next": "Call Bytebase Get Instances API"
    },
    "Call Bytebase Get Projects API": {
      "Type": "Task",
      "Resource": "arn:aws:states:::http:invoke",
      "Parameters": {
        "ApiEndpoint": "https://[Bytebaseの外部アクセス URL]/v1/projects",
        "Method": "GET",
        "Authentication": {
          "ConnectionArn": "[EventBridge Connection の ARN]"
        }
      },
      "Retry": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "BackoffRate": 2,
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "JitterStrategy": "FULL"
        }
      ],
      "End": true
    }
  }
}

詳細の説明をしていきます.

認証のエンドポイントを叩く処理

下記の部分が該当します.まずはサービスアカウントキーを Secrets Manager から取得し,認証エンドポイントを叩きます.レスポンスで認証トークンが返ってくるので,それを EventBridge Connection の値に入れます.認証トークンは当然ずっと固定ではないため,更新できるようにしています.UpdateConnection API を実行すれば更新ができたので,この処理をステートに入れています.

{
  "Comment": "Bytebase API exec test",
  "StartAt": "GetBytebaseServiceAPIUserCredential",
  "States": {
    "GetBytebaseServiceAPIUserCredential": {
      "Type": "Task",
      "Parameters": {
        "SecretId": "[サービスアカウントキーを格納した Secrets Manager 名]"
      },
      "Resource": "arn:aws:states:::aws-sdk:secretsmanager:getSecretValue",
      "Next": "Call Bytebase Authrization API"
    },
    "Call Bytebase Authrization API": {
      "Type": "Task",
      "Resource": "arn:aws:states:::http:invoke",
      "Parameters": {
        "ApiEndpoint": "https://[Bytebaseの外部アクセス URL]/v1/auth/login",
        "Authentication": {
          "ConnectionArn": "[EventBridge Connection の ARN]"
        },
        "Method": "POST",
        "RequestBody": {
          "email": "[サービスアカウントのアドレス]",
          "password.$": "$.SecretString",
          "web": true
        }
      },
      "Retry": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "BackoffRate": 2,
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "JitterStrategy": "FULL"
        }
      ],
      "ResultSelector": {
        "token.$": "States.StringSplit($.ResponseBody.token, ':')"
      },
      "OutputPath": "$.token[0]",
      "Next": "UpdateConnection"
    },
   "UpdateConnection": {
      "Type": "Task",
      "Parameters": {
        "Name": "[EventBridge Connection 名]",
        "AuthorizationType": "API_KEY",
        "AuthParameters": {
          "ApiKeyAuthParameters": {
            "ApiKeyName": "Authorization",
            "ApiKeyValue.$": "States.Format('Bearer {}',$)"
          }
        }
      },
      "Resource": "arn:aws:states:::aws-sdk:eventbridge:updateConnection",
      "Next": "Call Bytebase Get Instances API"
    }

認証トークンを取得後にプロジェクトの一覧を取得する API を叩く

そして,本命の API 実行部分ですが,以下が該当します.GET メソッドで /v1/projects で叩くと取得ができます.

    "Call Bytebase Get Projects API": {
      "Type": "Task",
      "Resource": "arn:aws:states:::http:invoke",
      "Parameters": {
        "ApiEndpoint": "https://[Bytebaseの外部アクセス URL]/v1/projects",
        "Method": "GET",
        "Authentication": {
          "ConnectionArn": "[EventBridge Connection の ARN]"
        }
      },
      "Retry": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "BackoffRate": 2,
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "JitterStrategy": "FULL"
        }
      ],
      "End": true
    }
  }
}

認証トークンが更新していてリクエストが成功すると以下のようなレスポンスが出力されます.

{
  "Headers": {
    "date": [
      "Wed, 19 Jun 2024 10:12:07 GMT"
    ],
    "content-length": [
      "641"
    ],
    "grpc-metadata-content-type": [
      "application/grpc"
    ],
    "content-type": [
      "application/json"
    ]
  },
  "ResponseBody": {
    "projects": [
      {
        "name": "projects/default",
        "uid": "1",
        "state": "ACTIVE",
        "title": "Default",
        "key": "DEFAULT",
        "workflow": "UI",
        "tenantMode": "TENANT_MODE_DISABLED",
        "webhooks": [],
        "dataClassificationConfigId": ""
      },
      {
        "name": "projects/project-sample",
        "uid": "101",
        "state": "ACTIVE",
        "title": "Sample Project",
        "key": "SAM",
        "workflow": "UI",
        "tenantMode": "TENANT_MODE_DISABLED",
        "webhooks": [],
        "dataClassificationConfigId": ""
      },
      {
        "name": "projects/hoge",
        "uid": "102",
        "state": "ACTIVE",
        "title": "hoge",
        "key": "HOGE",
        "workflow": "UI",
        "tenantMode": "TENANT_MODE_DISABLED",
        "webhooks": [],
        "dataClassificationConfigId": ""
      }
    ],
    "nextPageToken": ""
  },
  "StatusCode": 200,
  "StatusText": "OK"
}

Step Functions の IAM ポリシー

最後に API 実行のために必要だった IAM ポリシーをまとめます.まずは states:InvokeHTTPEndpoint 関連ですが,それぞれのエンドポイントメソッドを定義しました.

{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Effect": "Allow",
          "Sid": "InvokeHttpEndpoint1",
          "Action": [
              "states:InvokeHTTPEndpoint"
          ],
          "Resource": [
              "arn:aws:states:ap-northeast-1:1234567891011:stateMachine:*"
          ],
          "Condition": {
              "StringEquals": {
                  "states:HTTPEndpoint": [
                      "https://[Bytebaseの外部アクセス URL]/v1/auth/login",
                      "https://[Bytebaseの外部アクセス URL]/v1/instances"
                  ],
                  "states:HTTPMethod": [
                      "POST",
                      "GET"
                  ]
              }
          }
      }
  ]
}

あと EventBridge Connection 関連のポリシー定義で以下を設定しました.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Sid": "RetrieveConnectionCredentials1",
            "Action": [
                "events:RetrieveConnectionCredentials"
            ],
            "Resource": [
                "arn:aws:events:ap-northeast-1:1234567891011:connection/[EventBridge Connection 名]/*"
            ]
        },
        {
            "Effect": "Allow",
            "Sid": "GetAndDescribeSecretValue1",
            "Action": [
                "secretsmanager:GetSecretValue",
                "secretsmanager:DescribeSecret"
            ],
            "Resource": [
                "arn:aws:secretsmanager:ap-northeast-1:1234567891011:secret:events!connection/[EventBridge Connection 名]/*"
            ]
        },
        {
            "Resource": "arn:aws:events:ap-northeast-1:1234567891011:connection/[EventBridge Connection 名]",
            "Effect": "Allow",
            "Action": [
                "events:UpdateConnection"
            ]
        },
        {
            "Resource": "arn:aws:secretsmanager:ap-northeast-1:1234567891011:secret:events!connection/[EventBridge Connection 名]/*",
            "Effect": "Allow",
            "Action": [
                "secretsmanager:PutSecretValue"
            ]
        }
    ]
}

まとめ

Bytebase の API を Step Functions で叩くことをやったので,まとめました.