diff --git a/.github/workflows/build-jvm.yml b/.github/workflows/build-jvm.yml index 086bfb2..725d7cc 100644 --- a/.github/workflows/build-jvm.yml +++ b/.github/workflows/build-jvm.yml @@ -19,11 +19,46 @@ on: description: "Additional Gradle arguments" type: string default: "" + test_postgres_image: + description: "PostgreSQL image for a service container on localhost:5432 (e.g. postgres:17). Empty = no database." + type: string + default: "" + test_postgres_db: + description: "Database created in the test PostgreSQL container" + type: string + default: "test" + test_postgres_user: + description: "User for the test PostgreSQL container" + type: string + default: "test" + test_postgres_password: + description: "Password for the test PostgreSQL container (throwaway CI database)" + type: string + default: "test" jobs: build: name: Build JVM runs-on: ubuntu-latest + # Optional database for tests that need a real PostgreSQL. GitHub skips a + # service whose image is an empty string, so this costs nothing when + # test_postgres_image is unset. No input is interpolated into `options` + # (docker flags); pg_isready needs no user, and 127.0.0.1 skips the + # socket-only server the image runs while it initialises. + services: + postgres: + image: ${{ inputs.test_postgres_image }} + env: + POSTGRES_DB: ${{ inputs.test_postgres_db }} + POSTGRES_USER: ${{ inputs.test_postgres_user }} + POSTGRES_PASSWORD: ${{ inputs.test_postgres_password }} + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -h 127.0.0.1" + --health-interval 5s + --health-timeout 5s + --health-retries 10 steps: - uses: actions/checkout@v6 @@ -46,6 +81,35 @@ jobs: java-version: ${{ steps.java.outputs.version }} cache: ${{ inputs.build_tool }} + # libpq-style variables, for tests that read their connection from the + # environment. Apps with hardcoded test settings can ignore them. + - name: Export test PostgreSQL connection + if: inputs.test_postgres_image != '' + env: + PG_DB: ${{ inputs.test_postgres_db }} + PG_USER: ${{ inputs.test_postgres_user }} + PG_PASSWORD: ${{ inputs.test_postgres_password }} + run: | + # Plain identifiers only, and no newline in the password: these + # values are written to $GITHUB_ENV, one variable per line. + for v in "$PG_DB" "$PG_USER"; do + if ! printf '%s' "$v" | grep -Eq '^[A-Za-z_][A-Za-z0-9_]{0,62}$'; then + echo "::error::test_postgres_db and test_postgres_user must be plain identifiers (letters, digits, _): '$v'" + exit 1 + fi + done + if [ "$(printf '%s' "$PG_PASSWORD" | wc -l)" -ne 0 ]; then + echo "::error::test_postgres_password must not contain a newline" + exit 1 + fi + { + echo "PGHOST=localhost" + echo "PGPORT=5432" + echo "PGDATABASE=$PG_DB" + echo "PGUSER=$PG_USER" + echo "PGPASSWORD=$PG_PASSWORD" + } >> "$GITHUB_ENV" + - name: Build and test (Maven) if: inputs.build_tool == 'maven' run: mvn --batch-mode verify ${{ inputs.maven_args }} diff --git a/.github/workflows/javabin.yml b/.github/workflows/javabin.yml index 898798a..22e634d 100644 --- a/.github/workflows/javabin.yml +++ b/.github/workflows/javabin.yml @@ -5,7 +5,7 @@ name: Javabin CI # name: Deploy # on: # push: -# branches: [main] +# branches: [main] # your default branch (main or master) # pull_request: # workflow_dispatch: # permissions: @@ -20,6 +20,10 @@ name: Javabin CI # and platform scripts are read from this public repo with the caller's # GITHUB_TOKEN. Onboarding prerequisites are listed in # docs/reusable-workflows.md. +# +# Deploy jobs (docker-build, tf-apply, ecs-deploy, eb-deploy) run only for +# pushes and manual runs on the repo's default branch, whatever it is called +# (main, master, ...). Pull requests and other branches build, test and plan. on: workflow_call: @@ -36,6 +40,30 @@ on: description: "Java version override (default: read from the repo's version files)" type: string default: "" + maven_args: + description: "Extra arguments for `mvn verify` (e.g. -DskipITs)" + type: string + default: "" + gradle_args: + description: "Extra arguments for `gradle build`" + type: string + default: "" + test_postgres_image: + description: "Run a PostgreSQL service container on localhost:5432 during the JVM build (e.g. postgres:17). Empty = no database." + type: string + default: "" + test_postgres_db: + description: "Database created in the test PostgreSQL container" + type: string + default: "test" + test_postgres_user: + description: "User for the test PostgreSQL container" + type: string + default: "test" + test_postgres_password: + description: "Password for the test PostgreSQL container (throwaway CI database, not a secret)" + type: string + default: "test" permissions: id-token: write @@ -59,6 +87,12 @@ jobs: with: build_tool: ${{ needs.detect.outputs.has_maven == 'true' && 'maven' || 'gradle' }} java_version: ${{ inputs.java_version }} + maven_args: ${{ inputs.maven_args }} + gradle_args: ${{ inputs.gradle_args }} + test_postgres_image: ${{ inputs.test_postgres_image }} + test_postgres_db: ${{ inputs.test_postgres_db }} + test_postgres_user: ${{ inputs.test_postgres_user }} + test_postgres_password: ${{ inputs.test_postgres_password }} build-ts: needs: detect @@ -69,13 +103,14 @@ jobs: pnpm_version: ${{ inputs.pnpm_version }} # -------------------------------------------------------------------------- - # 3. Docker build + ECR push (main only, after builds pass) + # 3. Docker build + ECR push (default branch only, after builds pass) # -------------------------------------------------------------------------- docker-build: needs: [detect, build-jvm, build-ts, tf-plan, tf-apply] if: | always() && - github.ref == 'refs/heads/main' && + github.event_name != 'pull_request' && + github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && needs.detect.outputs.has_docker == 'true' && (needs.build-jvm.result == 'success' || needs.build-jvm.result == 'skipped') && (needs.build-ts.result == 'success' || needs.build-ts.result == 'skipped') && @@ -108,12 +143,13 @@ jobs: team: ${{ needs.detect.outputs.app_team }} # -------------------------------------------------------------------------- - # 6. Terraform apply (main only, after plan + review — blocked on HIGH risk) + # 6. Terraform apply (default branch only, after plan + review — blocked on HIGH risk) # -------------------------------------------------------------------------- tf-apply: needs: [detect, tf-plan] if: >- - github.ref == 'refs/heads/main' && + github.event_name != 'pull_request' && + github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && needs.tf-plan.outputs.has_changes == 'true' uses: javaBin/platform/.github/workflows/tf-apply.yml@main with: @@ -123,13 +159,14 @@ jobs: team: ${{ needs.detect.outputs.app_team }} # -------------------------------------------------------------------------- - # 7. ECS deploy (main only, after docker build + TF apply) + # 7. ECS deploy (default branch only, after docker build + TF apply) # -------------------------------------------------------------------------- ecs-deploy: needs: [detect, docker-build, tf-apply] if: | always() && - github.ref == 'refs/heads/main' && + github.event_name != 'pull_request' && + github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && needs.docker-build.result == 'success' && (needs.tf-apply.result == 'success' || needs.tf-apply.result == 'skipped') uses: javaBin/platform/.github/workflows/ecs-deploy.yml@main @@ -147,7 +184,8 @@ jobs: needs: [detect, build-jvm, build-ts] if: | always() && - github.ref == 'refs/heads/main' && + github.event_name != 'pull_request' && + github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && needs.detect.outputs.has_eb == 'true' && (needs.build-jvm.result == 'success' || needs.build-jvm.result == 'skipped') && (needs.build-ts.result == 'success' || needs.build-ts.result == 'skipped') diff --git a/.github/workflows/tf-plan.yml b/.github/workflows/tf-plan.yml index d5bb314..5828c10 100644 --- a/.github/workflows/tf-plan.yml +++ b/.github/workflows/tf-plan.yml @@ -144,7 +144,7 @@ jobs: id: review if: steps.plan.outputs.has_changes == 'true' env: - NOTIFY: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + NOTIFY: ${{ github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} run: sh .platform/scripts/invoke-plan-review.sh "${{ steps.upload.outputs.plan_key }}" - name: Post plan + review to PR diff --git a/docs/app-yaml-reference.md b/docs/app-yaml-reference.md index f1a16e2..c4a10af 100644 --- a/docs/app-yaml-reference.md +++ b/docs/app-yaml-reference.md @@ -329,7 +329,7 @@ These fields were documented earlier but are not implemented. Using them fails ` 1. Developer creates/updates `app.yaml` 2. The `expand` job runs `scripts/generate-modules.py`, which validates `app.yaml` and writes `terraform/backend.tf`, `providers.tf`, `main.tf`, `outputs.tf` (module blocks sourced from `javaBin/platform//terraform/modules/*`), then commits them to the branch -3. `tf-plan` plans the committed files → LLM review → `tf-apply` on main +3. `tf-plan` plans the committed files → LLM review → `tf-apply` on the default branch (main or master) 4. `docker-build` pushes `{team}-{name}` to ECR, `ecs-deploy` rolls the service Generated files carry a `# GENERATED FROM app.yaml` marker and are overwritten on every run. Developers can add their own `.tf` files alongside them. diff --git a/docs/reusable-workflows.md b/docs/reusable-workflows.md index e682ee6..f2fddd8 100644 --- a/docs/reusable-workflows.md +++ b/docs/reusable-workflows.md @@ -9,7 +9,7 @@ Create `.github/workflows/deploy.yml`: name: Deploy on: push: - branches: [main] + branches: [main] # your default branch: main or master pull_request: workflow_dispatch: @@ -44,7 +44,13 @@ detect ──┬─ expand ──── tf-plan ── tf-apply ──┐ ``` - **PRs:** detect → build → expand (commits generated TF to the PR branch) → tf-plan (plan + LLM review) → PR comment -- **Main:** detect → build → expand → tf-plan → tf-apply → docker-build → ecs-deploy +- **Default branch** (push or manual run): detect → build → expand → tf-plan → tf-apply → docker-build → ecs-deploy + +Deploy jobs (`tf-apply`, `docker-build`, `ecs-deploy`, `eb-deploy`) run only when +`github.ref` is `refs/heads/` (`github.event.repository.default_branch`) and the +event is not a pull request. The full ref is compared, so a tag that happens to be named like the +default branch never deploys. Repos whose default branch is `master` deploy from `master`; there is no +need to rename it. Pushes to other branches build and plan but never apply. ## Workflow Reference @@ -57,6 +63,28 @@ Unified entrypoint. Calls all other workflows via the nested reusable workflow p | `node_version` | read from `.nvmrc` / `.node-version` / `.tool-versions` / `mise.toml`, else 22 | | `pnpm_version` | `packageManager` in `package.json` | | `java_version` | read from `.java-version` / `.tool-versions` / `mise.toml`, else 21 | +| `maven_args` / `gradle_args` | (empty) — extra arguments for `mvn verify` / `gradle build` | +| `test_postgres_image` | (empty) — set to e.g. `postgres:17` to run a PostgreSQL service container during the JVM build | +| `test_postgres_db` / `test_postgres_user` / `test_postgres_password` | `test` / `test` / `test` | + +#### Tests that need PostgreSQL + +Set `test_postgres_image` and match database, user and password to what your tests expect. The +container listens on `localhost:5432` and is healthy (`pg_isready`) before the build starts. The +build step also gets `PGHOST`, `PGPORT`, `PGDATABASE`, `PGUSER` and `PGPASSWORD`. The password is +for a throwaway CI container, not a secret. Database and user must be plain identifiers (letters, +digits, `_`), and the build fails early otherwise. + +```yaml +jobs: + javabin: + uses: javaBin/platform/.github/workflows/javabin.yml@main + with: + test_postgres_image: postgres:17 + test_postgres_db: myappunit + test_postgres_user: localdevuser + test_postgres_password: localdevuser +``` ### detect.yml @@ -73,6 +101,8 @@ Maven (`mvn verify`) or Gradle (`./gradlew build`) build + test. Uploads test re | `build_tool` | `maven` (javabin.yml passes `gradle` when there is no `pom.xml`) | | `java_version` | resolved from version files, else 21 | | `maven_args` / `gradle_args` | (empty) | +| `test_postgres_image` | (empty) = no database; else a PostgreSQL service container on `localhost:5432` | +| `test_postgres_db` / `test_postgres_user` / `test_postgres_password` | `test` | ### build-ts.yml