GitHubでOrganization Environmentsが欲しい

GitHub Organization下の複数リポジトリにてクレデンシャルを共有したいケースがよくある。 このような場合にはOrganization SecretsやOrganization Variableが便利。

docs.github.com

これはこれで良いのだけれど、GitHub Environmentsを利用するようになると GitHub EnvironmentsもOrganizationで共有したくなる。 そんな機能は提供されていないようだが、やはりリクエストには挙がっていた。

github.com

なので現時点では複数リポジトリでEnvironmentを共有する方法はなさそう。 代替手段として、スクリプトで同一Environmentを複数リポジトリに作成する方法もありそう。ただし、APIでは提供されているが GitHub CLIでは提供されていない。

docs.github.com

github.com

なのでスクリプト化するのであればAPIで作り込む必要がある。 それ以外の手段だと、GitHub CLIのextensionを利用したり、Terraformで作成したりする方法はありそう。とはいえ、やはり公式にOrganization Environments機能を提供してくれるのが一番うれしいのだが。

AWS lambdaのNode.jsを18にアップデートする

AWS LambdaのNode.js 16ランタイムは 2024年6月に廃止がアナウンスされている。 (Node.js 16 LTS自体は公式には2023年11月にEOLを迎えている)。 できるだけ早めに Node.js 18 or 20にアプデートしたい。

docs.aws.amazon.com

AWS Lambda固有の事情として、Node.js 18以降では AWS SDK for v2 から AWS SDK for v3にアップデートされる。AWS SDK for v3はmodular architectureに変更となったのでパッケージの利用方法に非互換な修正が入る。これについても合わせて対応する必要がある。

幸いにも、公式にアップデート手順が提供されているのでこれに従えばよい。

aws.amazon.com

// AWS SDK for Javascript v2
const AWS = require("aws-sdk");

const s3Client = new AWS.S3({});
await s3Client.createBucket(params).promise();
// AWS SDK for Javascript v3
const { S3 } = require("@aws-sdk/client-s3");

const s3Client = new S3({});
await s3Client.createBucket(params);

エクスポートを利用するためにJavaScriptモジュールとして宣言する

JavaScriptには他のファイルで定義されたリソースを利用するための仕組みとして import/exportがある。 exportを利用するためには、ソースファイルがモジュールである必要がある。

ソースファイルで export 宣言を使用するためには、そのファイルはランタイムによってモジュールとして解釈される必要があります。

developer.mozilla.org

モジュールとして宣言していない場合は SyntaxError: Cannot use import statement outside a module になる。

$ node debug.js
(node:9940) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/home/thaim/work/debug.js:1
import { sample } from "./test.js";
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:77:18)
    at wrapSafe (node:internal/modules/cjs/loader:1288:20)
    at Module._compile (node:internal/modules/cjs/loader:1340:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
    at node:internal/main/run_main_module:28:49

Node.js v20.11.0

ドキュメントやエラーメッセージの通り、 package.jsonでtype: "module"を指定するか .mjs拡張子を利用する必要がある。 これはCommonJSではなくESモジュールを利用することを明示していることになる。

developer.mozilla.org

typescriptbook.jp

node.jsの場合はpackage.jsonでtype: "module"を指定する方法がよさそう。

GitHub Environmentsで設定した変数を参照する。

GitHub Environmentsにはシークレットと変数が設定できて、それぞれ secretsおよびvarsコンテキストを用いて参照する。 例えばシークレットとしてTOKENを設定していれば ${{ secrets.TOKEN }} で、変数として RAILS_ENVを設定していれば ${{ vars.RAILS_ENV }} で参照する。

docs.github.com

変数を自動で環境変数に展開するようなオプションはなさそう。 なので環境変数として利用したければ個別に設定する必要がある。

# 上手くいかない例:
jobs:
  deployment:
    runs-on: ubuntu-latest
    environment: development
    steps:
      - run: echo RAILS_ENV=${RAILS_ENV}

# 上手くいく例
jobs:
  deployment:
    runs-on: ubuntu-latest
    environment: development
    env:
      RAILS_ENV: ${{ vars.RAILS_ENV }}
    steps:
      - run: echo RAILS_ENV=${RAILS_ENV}

GitHub Actionsで条件に応じて値を設定する

GitHub Actionsで条件に応じて変数に値を設定したい。 例えばmainブランチを対象とする場合はenvironmentにproductionを、それ以外のブランチを対象とする場合はdevelopmentを設定したい。

このようなケースでは三項演算子が利用できる。

docs.github.com

これを利用すれば条件に応じて変数に値を設定することができる。

env:
  MY_ENV_VAR: ${{ github.ref == 'refs/heads/main' && 'production' || 'development' }}

条件が増えた場合も同じように対応可能だが可読性が悪くなる。 このようなケースでは actionsとして kanga333/variable-mapper がよさそう。

github.com

on: [push]
name: Export variables corresponding to regular expression-matched keys
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: kanga333/variable-mapper@master
      with:
        key: "${{github.base_ref}}"
        map: |
          {
            "master": {
              "environment": "production",
              "AWS_ACCESS_KEY_ID": "${{ secrets.PROD_AWS_ACCESS_KEY_ID }}",
              "AWS_SECRET_ACCESS_KEY": "${{ secrets.PROD_AWS_ACCESS_KEY_ID }}"
            },
            "staging": {
              "environment": "staging",
              "AWS_ACCESS_KEY_ID": "${{ secrets.STG_AWS_ACCESS_KEY_ID }}",
              "AWS_SECRET_ACCESS_KEY": "${{ secrets.STG_AWS_ACCESS_KEY_ID }}"
            },
            ".*": {
              "environment": "development",
              "AWS_ACCESS_KEY_ID": "${{ secrets.DEV_AWS_ACCESS_KEY_ID }}",
              "AWS_SECRET_ACCESS_KEY": "${{ secrets.DEV_AWS_ACCESS_KEY_ID }}"
            }
          }
    - name: Echo environment
      run: echo ${{ env.environment }}

これを利用すれば、複数の環境変数を条件に応じて設定できるし可読性が高くなる。 ただし、上記のような使い分けになるとGitHub Environmentsの方が管理しやすくなりそう。

docs.github.com

これらを踏まえて、environmentsを動的に選択する処理には三項演算子を、environmentに応じた変数の使い分けはGitHub Environmentsを利用する。 environmentsで切り替えるようなユースケースでない場合は kanga333/variable-mapper を利用する。

AWS LambdaのCloudTrailイベント名はAPI名と一致しない場合がある

AWS のCloudTrailログにおいてイベント名は基本的にAPI名と一致する。

例えば、S3に関するCloudTrailログであればイベント名は ListBucketsやGetBucketAclなど。 これはAWSドキュメントのAPI Referenceなどに記載の名前と一致する。

docs.aws.amazon.com

一方で、LambdaのCloudTrailログはAPI名とイベント名が一致しない場合がある。 API名はGetFunctionやListVersionsByFunctionに対し、イベント名は末尾に 2015033120150331v2 などの文字列が付与されている場合がある。

docs.aws.amazon.com

特にドキュメントを参照しても、イベント名と一致しないことは記載されていない。そもそもCloudTrailログにおけるイベント名とは実行したAPI名と一致するのだと思っていた。実際には上記の例のように一致しない場合がある。

これについて補足等無いか確認したが、ナレッジセンターの記事に(重要の表記はあるが)さらっと記載されているだけだった。

重要:****イベント名には、「getFunction20150331」 などの日付やバージョン情報が含まれている場合がありますが、これらはすべて同じパブリック API を参照しています。

repost.aws

実行したAPIと完全一致すると思い込んで検索したら出てこなくてちょっと手間取った。一致しない場合があることを覚えておくとよさそう。

AWS LambdaのGoランタイムを provided.al2023にアップデートする

AWS Lambdaの go1.xランタイムのサポートが切れるのでアップデートする。

aws.amazon.com

provided.al2023の選択

ランタイムアップデートについてはAWS LambdaのGo言語向けドキュメントにまとまっている。 以下以外のドキュメントでは Amazon Linux 2023 (provided.al2023)に関する記述は無く、Amazon Linux 2 (provided.al2)の記載しかない。これは英語ドキュメントでも同様。しかし、特に違いを考慮することなく provided.al2023を選択して問題なさそう。

docs.aws.amazon.com

実行ファイル名を bootstrapにする

go1.xランタイムでは実行ファイル名をHandlerとして指定することができた。 provider.al2以降ではbootstrapで固定なのでHandlerの設定と合わせて修正する。ランタイムを Amazon Linux 2023 にすると、Handlerはbootstrap

docs.aws.amazon.com

バイナリビルド

これはランタイムアップデートとは直接関係ないけれど、macOS上でgo buildすると当然 GOOS=darwin、GOARCHはarm64 または amd64となる。

AWS Lambda向けには GOOS=linux GOARCH=amd64を指定してビルドする必要がある。特に問題なければGOARCH=arm64でビルドしてアーキテクチャでarm64を選択しても問題なさそう。

エラーログ

Handler名が間違っていたり、ビルド設定が間違っていたりするとどちらも実行時に以下のような Runtime.InvalidEntrypoint のエラーになる。 エラーメッセージを見てHandlerの修正ミスかな?と思っていたらビルドアーキテクチャ設定の間違いだった。

Phase: init  Status: error   Error Type: Runtime.InvalidEntrypoint

ちなみに、Handlerの設定ミスだと後続のエラーログに Error: Couldn't find valid bootstrap(s): [/var/task/bootstrap /opt/bootstrap] が出力され、ビルドアーキテクチャの設定ミスだと Error: fork/exec /var/task/bootstrap: exec format error が出力されるみたい。