4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ここ半年くらい、GitHubActionを触る機会が多く、そこで学んだ点や躓いた点を列挙していきます。

目次

1.入力への参照はinputsを使うべき
2.environmentとreusableWorkflow併用時の注意点
3.environmentとreusableWorkflow併用時の注意点2
4.awsへのアクセスでoidc利用したときの注意点
5.reusable-workflowのネストは4層まで
6.apiからworkflow実行時に実行したrunの特定ができない

入力への参照はinputsを使うべき

例えば手動実行できるこんなスクリプトがあるとします。
ここでinputへの参照をgithub.event.inputsで書いてしまうと
あとで、ワークフローから再利用できるようにこうすると、inputにアクセスできず、あれーってなります。

name: foo

on:
  workflow_dispatch:
    inputs:
      foo:
        required: true
        type: string
     bar:
        required: true
        type: string
+  // ワークフローからの呼び出しを追加。
+  workflow_call:
+    inputs:
+      foo:
+        required: true
+        type: string
env:
  FOO_INPUT: ${{ github.event.inputs.foo }}
  BAR_INPUT: ${{ github.event.inputs.bar || 'BAR'}}
jobs:
  do-something:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

workflow_dispatchとworkflow_callどちらのinputにもアクセスできるinputsコンテキストがあるので、それで参照しましょう。

name: foo

on:
  workflow_dispatch:
    inputs:
      foo:
        required: true
        type: string
     bar:
        required: true
        type: string
  workflow_call:
    inputs:
      foo:
        required: true
        type: string
env:
-  FOO_INPUT: ${{ github.event.inputs.foo }}
-  BAR_INPUT: ${{ github.event.inputs.bar || 'BAR'}}
+  FOO_INPUT: ${{ inputs.foo }}
+  BAR_INPUT: ${{ inputs.bar || 'BAR'}}
jobs:
  do-something:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

environmentとreusable-workflow併用時の注意

既存の親スクリプトをEnvironmentに対応させたいというとき、こんな風にワークフロー呼び出しジョブへ直接指定することはできません。(エラーになります)

name: do all

on:
  workflow_dispatch:
    inputs:
      foo:
        type: string
        default: 'fooo'
+     env:
+       type: environment
+       default: 'dev'
jobs:
  call-build:
    uses: ./.github/workflows/build.yml
    with:
      foo: ${{ inputs.foo }}
  call-deploy:
+   environment: ${{ inputs.env }}
    uses: ./.github/workflows/deploy.yml
    with:
      foo: ${{ inputs.foo }}

呼び出しジョブではなく呼び出し先のジョブをenvironment対応させて引数を渡す形で対応しましょう。

name: do all

on:
  workflow_dispatch:
    inputs:
      foo:
        type: string
        default: 'fooo'
+     env:
+       type: environment
+       default: 'dev'
jobs:
  call-build:
    uses: ./.github/workflows/build.yml
    with:
      foo: ${{ inputs.foo }}
  call-deploy:
    uses: ./.github/workflows/deploy.yml
    with:
      foo: ${{ inputs.foo }}
+     environment: ${{ inputs.foo }}

ジョブ側はこんな感じ

name: deploy

on:
  workflow_call:
    inputs:
      foo:
        type: string
        required: true
+    environment:
+      type: environment
+      required: true
jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
+   environment: ${{ inputs.environment }}

書いてみると、そりゃそうかってなりますね。

environmentとreusable-workflow併用時の注意2

呼び出し先のジョブにenvironmentを指定する場合に、複数のジョブがあると、その度に承認が必要になります。

name: deploy

on:
  workflow_dispatch:
    inputs:
      foo:
        type: string
        default: 'fooo'
+     env:
+       type: environment
+       default: 'dev'
jobs:
  call-pre-deploy:
    uses: ./.github/workflows/pre-deploy.yml
    with:
      foo: ${{ inputs.foo }}
+     environment: ${{ inputs.foo }} // ここで一度承認
  call-deploy:
    uses: ./.github/workflows/deploy.yml
    with:
      foo: ${{ inputs.foo }}
+     environment: ${{ inputs.foo }} // さらにもう一度承認

さすがに毎回承認させるのもアレだったんで、environmentを使うジョブでOIDCのロール名を解決してから、それを引き渡す形で対応しました。
OIDCロールの利用可否は、Role側で制約を課している(後述)のでこれ自体が露出するのは問題なし。

name: deploy

on:
  workflow_dispatch:
    inputs:
      foo:
        type: string
        default: 'fooo'
+     env:
+       type: environment
+       default: 'dev'
jobs:
+ get-aws-role:
+   name: Get AWS Role
+   runs-on: ubuntu-latest
+   environment: ${{ inputs.env }}
+   outputs:
+     role-to-assume: ${{ steps.resolve-role-to-assume.outputs.role-to-assume }}
+   steps:
+     - id: resolve-role-to-assume
+       run: |
+         AWS_ROLE_TO_ASSUME=${{ vars.AWS_ROLE_TO_ASSUME }}
+        echo "role-to-assume=$AWS_ROLE_TO_ASSUME" >> $GITHUB_OUTPUT
  call-pre-deploy:
    uses: ./.github/workflows/pre-deploy.yml
    with:
      foo: ${{ inputs.foo }}
+     role-to-assume: ${{ needs.get-aws-role.outputs.role-to-assume }}

  call-deploy:
    uses: ./.github/workflows/deploy.yml
    with:
      foo: ${{ inputs.foo }}
+     role-to-assume: ${{ needs.get-aws-role.outputs.role-to-assume }}

こんな感じ

awsへのアクセスでoidc利用したときの注意点

それなに?ってひとはこちらをどうぞ。

注意点としては、OIDCを導入したはいいものの、検証を repo:my-org/my-repo:* 等がばがばにしちゃうと、AWS_SECRET等を直接使う場合とさほど変わりません。

 const githubOidcRole = new cdk.aws_iam.Role(this, "MyGithubOidcRole", {
      roleName: "MyGithubOidcRole",
      assumedBy: new cdk.aws_iam.FederatedPrincipal(
        gitHubIdProvider.openIdConnectProviderArn,
        {
          StringLike: {
            "token.actions.githubusercontent.com:sub": [
              "repo:my-org/my-repo:*", // ガバガバです。なんでも通します。
            ],
          },
          StringEquals: {
            "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          },
        },
        "sts:AssumeRoleWithWebIdentity",
      ),
});

この状態だと、プロテクトされていないブランチで好きに使えるようになってしまいます。
そこで、特に商用アカウントへアクセスするロールについては、プロテクトされたブランチもしくは承認が必要なEnvironmentのときのみ利用可能にしましょう。

CDK(on typescript)の例。

 const githubOidcRole = new cdk.aws_iam.Role(this, "MyGithubOidcRole", {
      roleName: "MyGithubOidcRole",
      assumedBy: new cdk.aws_iam.FederatedPrincipal(
        gitHubIdProvider.openIdConnectProviderArn,
        {
          StringLike: {
            "token.actions.githubusercontent.com:sub": [
-             "repo:my-org/my-repo:*",
+             "repo:my-org/my-repo:ref:refs/heads/main", // mainブランチ上で実行されたものはOK
+             "repo:my-org/my-repo:environment:production", // environment=productionのジョブはOK
            ],
          },
          StringEquals: {
            "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          },
        },
        "sts:AssumeRoleWithWebIdentity",
      ),
});

ちなみに、同一job内でenvironmentが使っている場合には、repo:my-org/my-repo:environment:productionみたいなenvironmentの値が渡ってきます。
先述のように別ジョブで解決している場合にはenvironmentではなくbranchがくるので、そっちの許可もいれています。

reusable-workflowのネストは4層まで

ちょっとしたことですが、あんまり細かくジョブ分けて再利用するようにしちゃうと、後々詰みますのでお気をつけて。
https://docs.github.com/ja/actions/sharing-automations/reusing-workflows#limitations

apiからworkflow実行時に実行したrunの特定ができない

※修正済みかもしれないので、状況知りたい人はこのIssueをみてください!

octokit使って、workflowをRunして、ステータスを監視して、完了したら通知する、みたいなことがしたかったです。

簡単にできるだろうなって思ったらできませんでした。

なぜならworkflowの実行後、そのジョブを特定する方法(RunId)がないからです。
Issueもあがってました。ので、対応してくださいるのを待ちましょう。

が、待ってられない人もいるかもなので回避方法を下記に説明します。
あんまりきれいではないです。

簡単な説明:ユニークなキーをワークフローに渡して、それを使って特定する。

ステップ1: まずは既存ワークフローに特定用のキー(distinct_id)を受け付けます

name: deploy

on:
  workflow_dispatch:
    inputs:
      env:
        type: environment
+     distinct_id:
+       type: string
jobs:
+ distinct-id:
+   name: Workflow ID Provider
+   runs-on: ubuntu-latest
+   steps:
+     - name: ${{inputs.distinct_id}}
+       run: echo run identifier ${{ inputs.distinct_id }}
  call-pre-deploy:
    uses: ./.github/workflows/migrate.yml
    with:
      env: ${{ inputs.stage }}
  call-deploy:
    uses: ./.github/workflows/deploy.yml
    with:
      env: ${{ inputs.stage }}

ステップ2: ワークフローをディスパッチする側で特定キーを渡します

export const runWorkflow = async (
  workflowId: string,
  inputs: Record<string, string>,
  branch: string,
) => {
  const secret = await getGitHubPat();
  const octokit: Octokit = new Octokit({ auth: secret });
+ // ユニークな文字列を生成  
+ const distinctId =
+   Date.now().toString(36) + Math.random().toString(36).substring(8);
  await octokit.rest.actions.createWorkflowDispatch({
      owner,
      repo,
      workflow_id: workflowId,
      ref: branch,
-     inputs,
+     inputs: { ...inputs, ...{ distinct_id: distinctId } },
  });
  // この関数の中身は↓に書きます。
  const runId = await getRunIdFromDistinctId(
      workflowId,
      distinctId,
      branch,
      octokit,
  );
  // RunIdを使って監視を実装します。
};

ステップ3: 特定キーからRunIdを解決してできあがり

// すぐにはとれないのでとれるまでぐるぐる...
const getRunIdFromDistinctId = async (
  workflowId: string,
  distinctId: string,
  branch: string,
  octokit: Octokit,
): Promise<number | null> => {
  const interval = 1000;
  const timeout = 1000 * 60;
  let elapsed = 0;
  while (elapsed < timeout) {
    try {
      const { runId } = await getRunId(
        workflowId,
        distinctId,
        branch,
        octokit,
      );
      if (runId) {
        return runId;
      }
    }
    await new Promise((resolve) => setTimeout(resolve, interval));
    elapsed += interval;
  }
  return null;
};

const getRunId = async (
  workflowId: string,
  distinctId: string,
  branch: string,
  octokit: Octokit,
) => {
  // Runしているワークフローを取得
  const runsResponse = await octokit.rest.actions.listWorkflowRuns({
    owner,
    repo,
    workflow_id: workflowId,
    ref: branch,
  });

  const runs = runsResponse.data.workflow_runs;
  const runIds = runs.map((run) => run.id);
  for (const runId of runIds) {
    // RunIdをもとにジョブ詳細を取得して
    const ret = await octokit.request(
      "GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs",
      {
        owner,
        repo,
        run_id: runId,
        headers: {
          "X-GitHub-Api-Version": "2022-11-28",
        },
      },
    );
    const run = ret.data;
    for (const job of ret.data.jobs) {
      // 発行したdistinctIdを持っているジョブが対象ののジョブ
      if (job.steps?.map((s: { name: any }) => s.name).includes(distinctId)) {
        const runId = job.run_id;
        const status = job.status;
        const conclusion = job.conclusion;
        console.log(
          `実行ID: ${runId}, ステータス: ${status}, 結果: ${conclusion}`,
        );
        return {
          runId,
          status,
          conclusion,
        };
      }
    }
  }
  console.log("Runが見つかりませんでした");
  return {
    runId: null,
    status: "",
    conclusion: "",
  };
};
4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?