diff --git a/.gitea/workflows/dotnet-build-test-postgres.yaml b/.gitea/workflows/dotnet-build-test-postgres.yaml new file mode 100644 index 0000000..d08c2ab --- /dev/null +++ b/.gitea/workflows/dotnet-build-test-postgres.yaml @@ -0,0 +1,127 @@ +name: .NET Build & Test with PostgreSQL (Reusable) + +on: + workflow_call: + inputs: + solution: + description: "Solution/project file to build (relative to working-directory). Empty = let dotnet pick the single sln/proj in working-directory" + required: false + default: "" + type: string + dotnet-version: + description: ".NET SDK version" + required: false + default: "10.0.x" + type: string + configuration: + description: "Build configuration" + required: false + default: "Release" + type: string + warnaserror: + description: "Treat build warnings as errors" + required: false + default: true + type: boolean + working-directory: + description: "Path where dotnet commands are executed (relative to repo root)" + required: false + default: "." + type: string + test-filter: + description: "Optional dotnet test --filter expression" + required: false + default: "" + type: string + postgres-image: + description: "PostgreSQL service image" + required: false + default: "postgres:17-alpine" + type: string + postgres-user: + description: "PostgreSQL superuser (POSTGRES_USER of the service)" + required: false + default: "postgres" + type: string + postgres-password: + description: "PostgreSQL password (throwaway CI database, not a secret)" + required: false + default: "postgres" + type: string + postgres-db: + description: "Database created on service start (POSTGRES_DB). Empty = same as postgres-user" + required: false + default: "" + type: string + +jobs: + build-and-test: + runs-on: ubuntu-latest + env: + DOTNET_CLI_TELEMETRY_OPTOUT: "1" + DOTNET_NOLOGO: "1" + SOLUTION: ${{ inputs.solution }} + CONFIGURATION: ${{ inputs.configuration }} + WARNASERROR: ${{ inputs.warnaserror }} + TEST_FILTER: ${{ inputs.test-filter }} + POSTGRES_HOST: postgres + POSTGRES_PORT: "5432" + POSTGRES_USER: ${{ inputs.postgres-user }} + POSTGRES_PASSWORD: ${{ inputs.postgres-password }} + POSTGRES_DB: ${{ inputs.postgres-db || inputs.postgres-user }} + + # Reached by service name on the job network (container-mode runner). + # No host port mapping, so concurrent jobs or a host PostgreSQL can't collide on 5432. + services: + postgres: + image: ${{ inputs.postgres-image }} + env: + POSTGRES_USER: ${{ inputs.postgres-user }} + POSTGRES_PASSWORD: ${{ inputs.postgres-password }} + POSTGRES_DB: ${{ inputs.postgres-db || inputs.postgres-user }} + + defaults: + run: + shell: bash + working-directory: ${{ inputs.working-directory }} + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ inputs.dotnet-version }} + + - name: Restore + run: dotnet restore ${SOLUTION:+"$SOLUTION"} + + - name: Build + run: | + ARGS=(-c "$CONFIGURATION" --no-restore) + if [ "$WARNASERROR" = "true" ]; then + ARGS+=(-warnaserror) + fi + dotnet build ${SOLUTION:+"$SOLUTION"} "${ARGS[@]}" + + # The image's init phase listens on the unix socket only, so an open TCP port means the final server is up. + - name: Wait for PostgreSQL + run: | + for _ in $(seq 1 60); do + if (exec 3<>"/dev/tcp/$POSTGRES_HOST/$POSTGRES_PORT") 2>/dev/null; then + echo "PostgreSQL reachable at $POSTGRES_HOST:$POSTGRES_PORT" + exit 0 + fi + sleep 1 + done + echo "::error::PostgreSQL not reachable at $POSTGRES_HOST:$POSTGRES_PORT after 60s" + exit 1 + + - name: Test + run: | + ARGS=(-c "$CONFIGURATION" --no-build) + if [ -n "$TEST_FILTER" ]; then + ARGS+=(--filter "$TEST_FILTER") + fi + dotnet test ${SOLUTION:+"$SOLUTION"} "${ARGS[@]}" diff --git a/README.md b/README.md index af85768..96b79f2 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,35 @@ jobs: No secrets. +## Variant with PostgreSQL + +`dotnet-build-test-postgres.yaml` runs the same steps as above, plus a PostgreSQL service container for integration tests. It has its own file because Gitea can't make `services:` conditional. + +```yaml +jobs: + build-test: + uses: templates/dotnet-build-test/.gitea/workflows/dotnet-build-test-postgres.yaml@v1 + with: + solution: MyApp.slnx + postgres-user: myapp + postgres-password: myapp + # postgres-image: postgres:17-alpine + # postgres-db: "" # empty = same as postgres-user +``` + +It takes all inputs of `dotnet-build-test.yaml`, plus these: + +| Name | Default | Description | +|---|---|---| +| `postgres-image` | `postgres:17-alpine` | Service image | +| `postgres-user` | `postgres` | Superuser, so it can create and drop databases | +| `postgres-password` | `postgres` | Throwaway CI password, not a secret | +| `postgres-db` | `""` | Database created at start. Empty means the same as `postgres-user` | + +The test step gets `POSTGRES_HOST=postgres`, `POSTGRES_PORT=5432`, `POSTGRES_USER`, `POSTGRES_PASSWORD` and `POSTGRES_DB` as environment variables. The job reaches the service by its name on the job network, which requires a container-mode runner. No host port is mapped. A step before the tests waits up to 60 s for the port to open. + +No secrets. + ## Versioning Only major tags (`v1`, `v2`, …). Fixes and new optional inputs move the existing tag. Changing or removing inputs, or changing behavior, needs a new major tag.