diff --git a/.github/actions/set-env/action.yml b/.github/actions/set-env/action.yml new file mode 100644 index 0000000..2249023 --- /dev/null +++ b/.github/actions/set-env/action.yml @@ -0,0 +1,84 @@ +name: Set Environment +description: Set environment, shared image tag, and the deploy matrix based on trigger context +inputs: + ar_hostname: + description: Artifact Registry hostname + required: true + project_id: + description: GCP project ID + required: true +outputs: + environment: + description: Target environment (e.g. stg, prod) + value: ${{ steps.set-env.outputs.environment }} + service_suffix: + description: Suffix appended to each Cloud Run service/job name (empty for prod) + value: ${{ steps.set-env.outputs.service_suffix }} + image_tag: + description: Shared Docker image tag (one image for all cmd targets) + value: ${{ steps.set-env.outputs.image_tag }} + matrix: + description: | + JSON deploy matrix derived from cmd/*. Shape: {"include":[{"name","type"}]}. + type is "service" for *-api targets, otherwise "job". + value: ${{ steps.set-matrix.outputs.matrix }} +runs: + using: composite + steps: + - id: set-env + shell: bash + run: | + # Environment + IS_WORKFLOW_DISPATCH="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.environment_name != '' }}" + IS_PUSH_TO_MAIN="${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.base_ref == 'main') }}" + if [ $IS_WORKFLOW_DISPATCH = "true" ]; then + export TARGET_ENVIRONMENT="${{ github.event.inputs.environment_name }}" + elif [ $IS_PUSH_TO_MAIN = "true" ]; then + export TARGET_ENVIRONMENT="stg" + else + exit 1 + fi + + echo "Environment: $TARGET_ENVIRONMENT" + + # Service name suffix + if [ "$TARGET_ENVIRONMENT" = "prod" ]; then + export SERVICE_NAME_SUFFIX="" + else + export SERVICE_NAME_SUFFIX="-$TARGET_ENVIRONMENT" + fi + + export REPO_NAME=$(echo ${{ github.repository }} | awk -F '/' '{print $2}') + # 全 cmd ターゲットで 1 つのイメージを共有するため、タグはサービス名を含めない。 + export IMAGE_TAG=${{ inputs.ar_hostname }}/${{ inputs.project_id }}/github-actions/$REPO_NAME:${{ github.sha }} + + echo "Repository name: $REPO_NAME" + echo "Service suffix: $SERVICE_NAME_SUFFIX" + echo "Image tag: $IMAGE_TAG" + + echo "environment=$TARGET_ENVIRONMENT" >> "$GITHUB_OUTPUT" + echo "service_suffix=$SERVICE_NAME_SUFFIX" >> "$GITHUB_OUTPUT" + echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT" + - id: set-matrix + shell: bash + run: | + # cmd/* を走査してデプロイ対象を自動生成する。 + # 末尾が "-api" のターゲットを Cloud Run Service、それ以外を Cloud Run Job として分類する。 + # サービス/ジョブが増減しても cmd/ に追従するため、ワークフロー側の編集は不要。 + include="" + for dir in cmd/*/; do + name=$(basename "$dir") + [ "$name" = "*" ] && continue + case "$name" in + *-api) type="service" ;; + *) type="job" ;; + esac + include="$include{\"name\":\"$name\",\"type\":\"$type\"}," + done + if [ -z "$include" ]; then + echo "No deploy targets found under cmd/" >&2 + exit 1 + fi + matrix="{\"include\":[${include%,}]}" + echo "Matrix: $matrix" + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index e59be30..9fd95ea 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -23,99 +23,107 @@ permissions: contents: read id-token: write -env: - REGION: asia-northeast1 - AR_REPOSITORY: server - IMAGE_NAME: server - jobs: - deploy: - name: Deploy (${{ github.event_name == 'workflow_dispatch' && inputs.environment_name || 'stg' }}) + set-env: runs-on: ubuntu-latest - environment: ${{ github.event_name == 'workflow_dispatch' && inputs.environment_name || 'stg' }} + outputs: + environment: ${{ steps.set-env.outputs.environment }} + service_suffix: ${{ steps.set-env.outputs.service_suffix }} + image_tag: ${{ steps.set-env.outputs.image_tag }} + matrix: ${{ steps.set-env.outputs.matrix }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - id: set-env + uses: ./.github/actions/set-env + with: + ar_hostname: ${{ vars._AR_HOSTNAME }} + project_id: ${{ vars.PROJECT_ID }} - - name: Resolve environment - id: env + # 全 cmd ターゲットを 1 つのイメージに同梱しているため、ビルド/プッシュは 1 回だけ実行する。 + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + env: + IMAGE_TAG: ${{ needs.set-env.outputs.image_tag }} + needs: + - set-env + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-depth: 0 + submodules: true + - name: Build run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - ENVIRONMENT="${{ inputs.environment_name }}" - else - ENVIRONMENT="stg" - fi - echo "name=${ENVIRONMENT}" >> "$GITHUB_OUTPUT" - # 命名規約 (infra/locals.tf env_suffix): prod は無サフィックス、他は -。 - if [ "$ENVIRONMENT" = "prod" ]; then - echo "suffix=" >> "$GITHUB_OUTPUT" - else - echo "suffix=-${ENVIRONMENT}" >> "$GITHUB_OUTPUT" - fi - - - name: Compose image reference - id: image + docker build \ + -t ${{ env.IMAGE_TAG }} \ + . \ + -f Dockerfile \ + --no-cache + - uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 + with: + workload_identity_provider: ${{ vars.WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ vars.SERVICE_ACCOUNT }} + - uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1 + with: + project_id: ${{ vars.PROJECT_ID }} + - name: Configure Docker run: | - echo "ref=${REGION}-docker.pkg.dev/${PROJECT_ID}/${AR_REPOSITORY}/${IMAGE_NAME}:${GITHUB_SHA}" >> "$GITHUB_OUTPUT" - env: - PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} + gcloud auth \ + configure-docker \ + ${{ vars._AR_HOSTNAME }} + - name: Push + run: | + docker push \ + ${{ env.IMAGE_TAG }} - - name: Authenticate to Google Cloud - id: auth - uses: google-github-actions/auth@v2 + # cmd/* から生成した matrix で各ターゲットを並列デプロイする。サービス/ジョブが増減しても自動追従する。 + deploy: + runs-on: ubuntu-latest + permissions: + id-token: write + environment: ${{ needs.set-env.outputs.environment }} + needs: + - set-env + - build-and-push + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.set-env.outputs.matrix) }} + env: + SERVICE_NAME: ${{ matrix.name }}${{ needs.set-env.outputs.service_suffix }} + IMAGE_TAG: ${{ needs.set-env.outputs.image_tag }} + steps: + - uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 with: - workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - service_account: ${{ secrets.GCP_DEPLOY_SERVICE_ACCOUNT }} - token_format: access_token - - - name: Set up Cloud SDK - uses: google-github-actions/setup-gcloud@v2 + workload_identity_provider: ${{ vars.WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ vars.SERVICE_ACCOUNT }} - - name: Log in to Artifact Registry - uses: docker/login-action@v3 + # *-api: Cloud Run Service としてデプロイ。共有イメージの起動コマンドを /bin/ に切り替える。 + - if: matrix.type == 'service' + uses: google-github-actions/deploy-cloudrun@2028e2d7d30a78c6910e0632e48dd561b064884d # v3.0.1 with: - registry: ${{ env.REGION }}-docker.pkg.dev - username: oauth2accesstoken - password: ${{ steps.auth.outputs.access_token }} + service: ${{ env.SERVICE_NAME }} + image: ${{ env.IMAGE_TAG }} + region: ${{ vars._DEPLOY_REGION }} + flags: --command=/bin/${{ matrix.name }} + env_vars: | + DB_IAM_USER=${{ vars.DB_IAM_USER }} + DB_NAME=${{ vars.DB_NAME }} + INSTANCE_CONNECTION_NAME=${{ vars.INSTANCE_CONNECTION_NAME }} + env_vars_update_strategy: overwrite + secrets_update_strategy: overwrite - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build and push image - uses: docker/build-push-action@v6 + # それ以外: Cloud Run Job として作成/更新する(実行はしない)。 + - if: matrix.type == 'job' + uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1 with: - context: . - push: true - tags: ${{ steps.image.outputs.ref }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Deploy Cloud Run services - run: | - for svc in academic-api announcement-api user-api; do - echo "::group::update service ${svc}${SUFFIX}" - gcloud run services update "${svc}${SUFFIX}" \ - --region="$REGION" \ - --image="$IMAGE" \ - --quiet - echo "::endgroup::" - done - env: - SUFFIX: ${{ steps.env.outputs.suffix }} - IMAGE: ${{ steps.image.outputs.ref }} - - - name: Deploy Cloud Run jobs + project_id: ${{ vars.PROJECT_ID }} + - if: matrix.type == 'job' + name: Deploy Cloud Run Job run: | - for job in \ - build-class-change-notifications-job \ - dispatch-notifications-job \ - migrate-job; do - echo "::group::update job ${job}${SUFFIX}" - gcloud run jobs update "${job}${SUFFIX}" \ - --region="$REGION" \ - --image="$IMAGE" \ - --quiet - echo "::endgroup::" - done - env: - SUFFIX: ${{ steps.env.outputs.suffix }} - IMAGE: ${{ steps.image.outputs.ref }} + gcloud run jobs deploy "${{ env.SERVICE_NAME }}" \ + --image "${{ env.IMAGE_TAG }}" \ + --region "${{ vars._DEPLOY_REGION }}" \ + --command "/bin/${{ matrix.name }}" \ + --set-env-vars "DB_IAM_USER=${{ vars.DB_IAM_USER }},DB_NAME=${{ vars.DB_NAME }},INSTANCE_CONNECTION_NAME=${{ vars.INSTANCE_CONNECTION_NAME }}"