Pull Request Review 情報を API で取得する
Pull Request のレビューで Code Owner が設定されているかどうかを取得する方法を調べたのでメモ。
GraphQL API の ReviewRequest オブジェクトで Code Owner かどうか取得できます。クエリの例を書いておきます。
query { repository(owner: "OWNER", name: "REPO") { pullRequest(number: 1918) { reviewRequests(first: 10) { nodes { asCodeOwner requestedReviewer { ... on Team { team: name } ... on User { user: login } } } } } } }
{ "data": { "repository": { "pullRequest": { "reviewRequests": { "nodes": [ { "asCodeOwner": true, "requestedReviewer": { "team": "sre" } }, { "asCodeOwner": false, "requestedReviewer": { "user": "int128" } } ] } } } } }
GitHub Actions で Issue を Project に追加する
カンバンをうまく運用するには自動化の仕組みが不可欠です。Issue や Pull Request を作ったのにカンバンに入れていなくて忘れ去っていたということ、ありますよね。本稿では GitHub に Issue や Pull Request が作成された契機で Project に自動的に追加する仕組みを考えます。
ここでは Organization で Project を使っている場合を想定します。
GraphQL Mutation で Issue を Project に追加する
まずは API で Project を操作する方法を説明します。
Issue や Pull Request を Project に追加するには addProjectCard
mutation が用意されています。これを使うと Project ボードの指定したカラムに追加できます。
さっそく GraphQL Explorer で試してみましょう。
まずは適当な Issue と Project を選び、以下のクエリで ID を取得します。数字は Issue number と Project number に対応します。
query { repository(owner: "ORG", name: "REPO") { issue(number: 27193) { id } } organization(login: "ORG") { project(number: 26) { columns(first: 1) { nodes { name id } } } } }
以下のような ID が返ってくるはずです。
{ "data": { "repository": { "issue": { "id": "MDSOMEISSUEID" } }, "organization": { "project": { "columns": { "nodes": [ { "name": "To Do", "id": "MDSOMEPROJECTCOLUMNID" } ] } } } } }
先ほど取得した ID を引数にして以下の mutation を実行します。
mutation($projectColumnId: ID!, $issueID: ID!) { addProjectCard(input: {projectColumnId: $projectColumnId, contentId: $issueID}) { projectColumn { name } } }
{ "projectColumnId": "MDSOMEPROJECTCOLUMNID", "issueID": "MDSOMEISSUEID" }
成功した場合は Project のカラム名が返ってくるはずです。すでに Project に Issue が追加済みの場合は以下のエラーが返されます。
{ "data": { "addProjectCard": null }, "errors": [ { "type": "UNPROCESSABLE", "path": [ "addProjectCard" ], "locations": [ { "line": 2, "column": 3 } ], "message": "Project already has the associated issue" } ] }
GitHub CLI で GraphQL mutation を実行する
GitHub CLI には gh api
コマンドが用意されており、REST や GraphQL のリクエストを直接実行できます。先ほどの mutation は以下のコマンドで実行できます。
gh api graphql -F 'projectColumnId=MDSOMEPROJECTCOLUMNID' -F 'issueID=MDSOMEISSUEID' -f query=' mutation($projectColumnId: ID!, $issueID: ID!) { addProjectCard(input: {projectColumnId: $projectColumnId, contentId: $issueID}) { projectColumn { name } } }'
GraphQL Explorer で試した内容をそのままコマンドで実行できるのでめっちゃ便利です。
GitHub Actions で Issue を Project に追加する
GitHub Actions の issues
event type を利用すると、Issue が変更された契機でワークフローを実行できます。さらに、ワークフローの if
で発火契機を細かく指定できます。
例として、Issue に特定のラベルが付いている場合に自動的に Project に追加するワークフローを書いてみましょう。
on: issues: types: # Issue の新規作成や変更、ラベル追加を契機にする(ラベル削除は別に考える必要あり) - opened - edited - labeled env: GITHUB_TOKEN: ${{ secrets.YOUR_TOKEN }} MUTATION: | mutation($projectColumnId: ID!, $issueID: ID!) { addProjectCard(input: {projectColumnId: $projectColumnId, contentId: $issueID}) { projectColumn { name } } } jobs: my-kanban: if: contains(github.event.issue.labels.*.name, 'Kubernetes') # 特定のラベルが付いている場合のみ runs-on: ubuntu-latest env: PROJECT_COLUMN_ID: MDSOMEPROJECTCOLUMNID steps: - run: gh api graphql -F "projectColumnId=$PROJECT_COLUMN_ID" -F 'issueID=${{ github.event.issue.node_id }}' -f query="$MUTATION"
このワークフローを配置して、Issue にラベルを付けてみましょう。以下の動作になるはずです。
- Issue に Kubernetes ラベルが付いている場合: ジョブが実行されて Project に Issue が追加される
- Issue に Kubernetes ラベルが付いていない場合: ワークフローは実行されるがジョブは実行されない
GitHub Actions の課金体系を見る限り、ジョブが実行された場合のみ課金されるようですが、実際は未確認です。
まとめ
- GraphQL のクエリをそのまま
gh api
コマンドに貼り付けて実行できるのが便利 - GitHub Actions の event trigger と条件分岐で細かく制御できる
Envoy OAuth2 Filterを試す(未完)
EnvoyのOAuth2 Filterを試してみました。残念ながら期待通りに動きませんでした。メモだけ残しておきます。
構成
- EKS 1.18
- Envoy 1.17.0-dev-483dd3
以下のトラフィックパスを構築します。
Browser ↓ Internet-facing ALB ↓ Service/NodePort ↓ Pod (envoy) ↓ Service ↓ Pod (ingress-nginx) ↓ Ingress (echoserver) ↓ Service (echoserver) ↓ Pod (echoserver)
Route53やALBを設定してインターネットからドメイン名でアクセス可能にしておきます。
また、Google Identity PlatformであらかじめClient IDを作成しておきます。Redirect URIsには https://echoserver.example.com/callback
を指定しておきます(example.com
は自分のドメインに置き換えてください)。
Envoyの設定
Example configuration を参考にYAMLを書きます。まずは検証のために必要最小限に設定にします。分かりにくいのでコメントを入れました。
# /etc/envoy/envoy.yaml static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 10000 } filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager codec_type: "AUTO" stat_prefix: ingress_http route_config: virtual_hosts: # すべてのトラフィックを service cluster に転送する - name: service domains: ["*"] routes: - match: prefix: "/" route: cluster: service timeout: 5s http_filters: # OAuth2 Filterを適用する - name: envoy.filters.http.oauth2 typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2 config: # Google OAuth2 Endpointを定義する token_endpoint: cluster: auth uri: oauth2.googleapis.com/token timeout: 3s authorization_endpoint: https://accounts.google.com/o/oauth2/v2/auth redirect_uri: "https://%REQ(:authority)%/callback" redirect_path_matcher: path: exact: /callback signout_path: path: exact: /signout credentials: # Client ID/Secretを定義する client_id: REDACTED.apps.googleusercontent.com token_secret: name: token sds_config: path: "/etc/envoy/secret.yaml" hmac_secret: name: hmac sds_config: path: "/etc/envoy/secret.yaml" - name: envoy.router clusters: - name: service connect_timeout: 5s type: LOGICAL_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: service endpoints: - lb_endpoints: - endpoint: address: socket_address: # 後続のバックエンドに転送する address: ingress-nginx-controller.ingress-nginx.svc.cluster.local port_value: 80 - name: auth connect_timeout: 5s type: LOGICAL_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: auth endpoints: - lb_endpoints: - endpoint: address: socket_address: address: oauth2.googleapis.com port_value: 443
# /etc/envoy/secret.yaml resources: - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" name: token generic_secret: secret: inline_string: REDACTED - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" name: hmac generic_secret: secret: inline_string: REDACTED
Envoyのデプロイ
以下のYAMLをデプロイします。
apiVersion: apps/v1 kind: Deployment metadata: name: envoy spec: replicas: 1 selector: matchLabels: app: envoy template: metadata: labels: app: envoy spec: containers: - args: - --config-path - /etc/envoy/envoy.yaml - --bootstrap-version - "3" - --service-node - ingress-envoy - --service-cluster - ingress-envoy image: envoyproxy/envoy-dev:483dd3007f15e47deed0a29d945ff776abb37815 name: envoy ports: - name: http containerPort: 10000 protocol: TCP resources: limits: memory: 256Mi requests: cpu: 10m memory: 256Mi volumeMounts: - mountPath: /etc/envoy name: envoy volumes: - name: envoy configMap: name: envoy --- apiVersion: v1 kind: Service metadata: name: envoy spec: type: NodePort ports: - name: http port: 80 nodePort: 30280 protocol: TCP targetPort: http selector: app: envoy
Envoy v3 APIを利用するには引数に --bootstrap-version 3
が必要です。OAuth2 Filter APIはv3で定義されています。
また、 generic_secret
を定義するには引数に --service-node
--service-cluster
が必要です。これらを指定しないと以下のエラーが出ます。
GenericSecretSdsApi: node 'id' and 'cluster' are required. Set it either in 'node' config or via --service-node and --service-cluster options.
動作確認
ブラウザで https://echoserver.example.com
にアクセスします。OAuth2 authorization endpointへのリダイレクトまでは動作したのですが、残念ながら以下のエラー画面になってしまいました。
承認エラー エラー 400: invalid_scope Some requested scopes were invalid. {invalid=[user]}
ブラウザのログを確認すると、以下のようなリクエストが飛んでいました。
https://echoserver.example.com ↓302 https://accounts.google.com/o/oauth2/v2/auth?client_id=REDACTED&scope=user&response_type=code&redirect_uri=https%3A%2F%2Fechoserver.example.com%2Fcallback&state=https%3A%2F%2Fechoserver.example.com%2F ↓302 https://accounts.google.com/signin/oauth/error?authError=REDACTED
OAuth2 scopeをemailなどに変更すれば動作すると思いますが、Envoyのドキュメントには設定項目が見当たりませんでした。v1.16時点ではoauth.protoに定義がないようです。ここでお手上げになりました。
(12/25追記) OAuth2 scopeを指定できるようにするPull Requestがあるようです。
このPull Requestを実際に動かしているコードもあるようです。
今後の課題
実用になるまでは以下の課題がありそうです。
pass_through_matcher
でFilterの除外条件を定義できるが実用に耐えられるか。authority
,path
のregex matchが使えるので大丈夫そう。email
などでリソースにアクセスできる条件を絞り込めるか。現状は UserInfo から属性を取得する実装がなさそうなので無理そう。