Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 168 additions & 73 deletions .github/workflows/ci-cd-default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,19 @@
default: 0

jobs:
check-docker-disk-space:
name: Check Docker Disk Space & Prune if Needed
# No separate up-front Docker disk check job: the GitHub-hosted runners are fresh per job, so
# checking one runner's disk says nothing about the next. 'build-deploy' checks its own runner.
create-matrix:
name: Create build matrix
runs-on: ${{ inputs.runs-on }}
defaults:
run:
shell: bash
outputs:
app-vars-matrix: ${{ steps.create-matrix.outputs.app-vars }}
applications-version: ${{ steps.create-matrix.outputs.applications-version }}
codeql-app-vars-matrix: ${{ steps.codeql-matrix.outputs.app-vars }}
codeql-has-apps: ${{ steps.codeql-matrix.outputs.has-apps }}
steps:
- name: Notify Internal Status (ci-cd-started)
uses: dsb-norge/github-actions/ci-cd/notify-internal-status@v4
Expand All @@ -97,22 +104,6 @@
step-name: ci-cd-started
basic-auth-password: ${{ secrets.CLOUD_MONITOR_BASIC_AUTH_PASSWORD }}

- name: "🩺 Check Docker disk usage and prune if necessary"
uses: dsb-norge/github-actions/ci-cd/check-docker-disk-space@v4
with:
threshold-gb: "7"

create-matrix:
name: Create build matrix
needs: check-docker-disk-space
runs-on: ${{ inputs.runs-on }}
defaults:
run:
shell: bash
outputs:
app-vars-matrix: ${{ steps.create-matrix.outputs.app-vars }}
applications-version: ${{ steps.create-matrix.outputs.applications-version }}
steps:
- name: "🧹 Clean: 🏢 workspace"
id: clean
uses: dsb-norge/directory-recreate@v1
Expand All @@ -135,6 +126,25 @@
with:
apps: ${{ inputs.apps }}

- name: "🎰 Generate: 🔎 CodeQL build matrix"
id: codeql-matrix
if: ${{ ! ( github.event_name == 'pull_request' && github.event.action == 'closed' ) }}
env:
APP_VARS_MATRIX: ${{ steps.create-matrix.outputs.app-vars }}
run: |
# Spring Boot apps with changes that have not opted out of CodeQL. 'codeql-enabled' is not
# normalized to a boolean until create-build-envs, so both 'false' and false opt out here.
MATRIX=$(jq -c '
.include |= map(select(
.["app-vars"]["application-type"] == "spring-boot"
and (.["app-vars"]["has-changes"] | tostring) == "true"
and (.["app-vars"]["codeql-enabled"] | tostring) != "false"
))
| .["application-name"] = [.include[]["application-name"]]
' <<< "${APP_VARS_MATRIX}")
echo "app-vars=${MATRIX}" >> "$GITHUB_OUTPUT"
echo "has-apps=$(jq '.include | length > 0' <<< "${MATRIX}")" >> "$GITHUB_OUTPUT"

- name: Notify Internal Status (build-matrix-finished)
if: always()
uses: dsb-norge/github-actions/ci-cd/notify-internal-status@v4
Expand Down Expand Up @@ -245,38 +255,8 @@
with:
dsb-build-envs: ${{ steps.build-env.outputs.json }}

- name: "🔎 Initialize CodeQL for Java and Kotlin"
if: |
matrix.app-vars['has-changes'] == true
&& ! ( github.event_name == 'pull_request' && github.event.action == 'closed' )
&& matrix.app-vars.application-type == 'spring-boot'
&& fromJSON(steps.build-env.outputs.json).codeql-enabled
# Soft rollout: CodeQL steps must not fail callers that lack GHAS or
# security-events: write. Remove once all consumers are onboarded.
continue-on-error: true
uses: github/codeql-action/init@v4
with:
languages: java-kotlin
# build-mode: manual (was: none) — the tracer follows the real Maven
# build below, which runs on our runners with access to internal
# artifacts. none-mode was not an alternative: it never extracted
# Kotlin at all, so pure-Kotlin apps failed analysis with "CodeQL
# could not process any code written in Java/Kotlin".
# The flip side of manual mode is that the tracer injects itself into
# kotlinc and refuses Kotlin versions newer than the bundle supports,
# which fails the Maven build itself (not this step). build-maven-project
# guards against that by disabling Kotlin extraction when needed.
build-mode: manual
# Add the code-quality query suite (maintainability/reliability — the
# SonarQube-type findings) on top of the security queries. Findings
# land in code scanning alongside the security results: while GitHub
# Code Quality is in preview, only its own default setup may upload
# to the Code Quality product (`analysis-kinds: code-quality` is
# rejected in custom workflows with "use the queries input with
# code-quality instead"). TODO: switch to Code Quality uploads when
# the preview opens for advanced setup.
queries: +code-quality

# CodeQL for Java/Kotlin runs in the parallel 'codeql' job below, so its tracer does not slow
# down this build and its tests.
- name: "🍃 Spring Boot app: ⚒ Build and analyze with Maven"
id: maven-build
if: |
Expand All @@ -287,29 +267,6 @@
with:
dsb-build-envs: ${{ steps.build-env.outputs.json }}

- name: "🔎 Perform CodeQL Analysis for Java and Kotlin"
if: |
matrix.app-vars['has-changes'] == true
&& ! ( github.event_name == 'pull_request' && github.event.action == 'closed' )
&& matrix.app-vars.application-type == 'spring-boot'
&& fromJSON(steps.build-env.outputs.json).codeql-enabled
continue-on-error: true
uses: github/codeql-action/analyze@v4
with:
category: "/language:java-kotlin/app:${{ matrix.app-vars.application-name }}"

- name: "🧹 Clear CodeQL tracer env (LD_PRELOAD) after analysis"
if: |
matrix.app-vars['has-changes'] == true
&& ! ( github.event_name == 'pull_request' && github.event.action == 'closed' )
&& matrix.app-vars.application-type == 'spring-boot'
# CodeQL's manual-mode tracer sets LD_PRELOAD via GITHUB_ENV; it
# persists for the rest of the job and breaks subsequent Deno steps
# that use scoped --allow-run. Clear it so docker/az/helm/etc. actions
# keep their tight subprocess allowlists.
shell: bash
run: echo "LD_PRELOAD=" >> "$GITHUB_ENV"

- name: "📊 Upload code coverage"
if: |
matrix.app-vars['has-changes'] == true
Expand Down Expand Up @@ -487,6 +444,34 @@
with:
dsb-build-envs: ${{ steps.build-env.outputs.json }}

- name: "🧹 Prune: 📚 the project's own artifacts from the dependencies cache"
if: |
matrix.app-vars['has-changes'] == true
&& ! ( github.event_name == 'pull_request' && github.event.action == 'closed' )
&& matrix.app-vars.application-type == 'spring-boot'
&& fromJSON(steps.build-env.outputs.json).github-dependencies-cache-enabled
&& steps.cache-restore.outputs.cache-hit != 'true'
# 'install' and 'deploy' put the project's own artifacts in the local repo under a version that
# is unique per build, a WAR alone can be hundreds of MB. Kept in the cache they pile up build
# after build, and every job pays for them on restore and save. The artifact dirs holding this
# build's version are the project's own modules: drop them with all their versions, so older
# builds already in the cache go too. Anything needed later is simply fetched again.
env:
CACHE_PATH: ${{ steps.cache-restore.outputs.cache-normalized-path }}
APP_VERSION: ${{ fromJSON(steps.build-env.outputs.json).application-version }}
run: |
if [[ -z "${CACHE_PATH}" || ! -d "${CACHE_PATH}" || -z "${APP_VERSION}" ]]; then
echo "Nothing to prune (cache path: '${CACHE_PATH}', version: '${APP_VERSION}')."
exit 0
fi
echo "Cache size before pruning: $(du -sh "${CACHE_PATH}" | cut -f1)"
find "${CACHE_PATH}" -mindepth 2 -type d -name "${APP_VERSION}" -prune -printf '%h\n' | sort -u |
while read -r ARTIFACT_DIR; do
echo "Removing ${ARTIFACT_DIR#"${CACHE_PATH}"/} ($(du -sh "${ARTIFACT_DIR}" | cut -f1))"
rm -rf "${ARTIFACT_DIR}"
done
echo "Cache size after pruning: $(du -sh "${CACHE_PATH}" | cut -f1)"

- name: "💾 Save 📚 build dependencies cache"
id: cache-save
if: |
Expand Down Expand Up @@ -557,6 +542,116 @@
basic-auth-password: ${{ secrets.CLOUD_MONITOR_BASIC_AUTH_PASSWORD }}
appvars: ${{ toJSON(matrix.app-vars) }}

# CodeQL for Java/Kotlin, in parallel with 'build-deploy' instead of tracing its full build with
# tests. This job only compiles and packages (build-only), so the analysis is off the critical path.
# Not part of 'ci-cd-conclusion': like the CodeQL steps it replaces, it must never block a build.
codeql:
name: CodeQL (Java/Kotlin)
needs: create-matrix
if: needs.create-matrix.outputs.codeql-has-apps == 'true'
runs-on: ${{ matrix.app-vars.runs-on || inputs.runs-on }}
strategy:
matrix: ${{ fromJSON(needs.create-matrix.outputs.codeql-app-vars-matrix) }}
fail-fast: false
env:
# Apps may enable Kotlin incremental compilation (eg. '-Dkotlin.compiler.incremental=true' in
# .mvn/maven.config) to avoid recompiling in deploy/image steps. The CodeQL tracer is only
# known to work with the regular compiler, so force it here. MAVEN_ARGS wins over
# .mvn/maven.config, '-ntp' is what the runner image sets by default.
MAVEN_ARGS: -ntp -Dkotlin.compiler.incremental=false
defaults:
run:
shell: bash
steps:
- name: "🧹 Clean: 🏢 workspace"
uses: dsb-norge/directory-recreate@v1

- name: "⬇ Checkout working branch"
uses: actions/checkout@v6

- name: "🔑 Obtain GitHub app installation access token for the CI/CD app"
id: auth
uses: dsb-norge/github-actions/get-github-app-installation-token@v4
with:
github-app-id: ${{ vars.ORG_CICD_APP_ID }}
github-app-installation-id: ${{ vars.ORG_CICD_APP_INSTALLATION_ID }}
github-app-private-key: "${{ secrets.ORG_CICD_APP_PRIVATE_KEY }}"

- name: "🎰 Generate: 🔢 DSB build variables"
id: build-env
uses: dsb-norge/github-actions/ci-cd/create-build-envs@v4
with:
app-vars: ${{ toJSON(matrix.app-vars) }}
github-json: ${{ toJSON(github) }}
secrets-json: ${{ toJSON(secrets) }}
Comment thread
rolvraen marked this conversation as resolved.
Dismissed
vars-json: ${{ toJSON(vars) }}
sonarqube-token: ${{ secrets.ORG_SONAR_TOKEN }}
jasypt-password: ${{ secrets.ORG_JASYPT_LOCAL_ENCRYPTOR_PASSWORD }}
acr-tenant-id: ${{ vars.ORG_DSBNO_AZURE_TENANT_ID }}
acr-subscription-id: ${{ vars.ORG_ACR_SUBSCRIPTION_ID }}
acr-push-service-principal-id: ${{ vars.ORG_ACR_PUSH_SERVICE_PRINCIPAL_ID }}
github-repo-token: ${{ secrets.GITHUB_TOKEN }}
npmjs-token: ${{ secrets.ORG_NPM_TOKEN }}
app-config-repo-token: ${{ steps.auth.outputs.github-token }}
pr-deploy-aks-creds-tenant-id: ${{ vars.ORG_DSBNO_AZURE_TENANT_ID }}
pr-deploy-aks-creds-subscription-id: ${{ vars.ORG_AKS_DEV_SUBSCRIPTION_ID }}
pr-deploy-aks-creds-service-principal-id: ${{ vars.ORG_AKS_DEV_ADMIN_SERVICE_PRINCIPAL_ID }}
acr-delete-service-principal-id: ${{ vars.ORG_ACR_DELETE_SERVICE_PRINCIPAL_ID }}

# Restore only: 'build-deploy' owns saving the cache.
- name: "🔄 Restore 📚 build dependencies cache"
if: fromJSON(steps.build-env.outputs.json).github-dependencies-cache-enabled
uses: dsb-norge/github-actions/ci-cd/dependencies-cache-restore@v4
with:
dsb-build-envs: ${{ steps.build-env.outputs.json }}

- name: "🔎 Initialize CodeQL for Java and Kotlin"
id: codeql-init
# Second check of the opt-out, after create-build-envs has normalized it.
if: fromJSON(steps.build-env.outputs.json).codeql-enabled
# Soft rollout: CodeQL steps must not fail callers that lack GHAS or
# security-events: write. Remove once all consumers are onboarded.
continue-on-error: true
uses: github/codeql-action/init@v4
with:
languages: java-kotlin
# build-mode: manual (was: none) — the tracer follows the Maven
# build below, which runs on our runners with access to internal
# artifacts. none-mode was not an alternative: it never extracted
# Kotlin at all, so pure-Kotlin apps failed analysis with "CodeQL
# could not process any code written in Java/Kotlin".
# The flip side of manual mode is that the tracer injects itself into
# kotlinc and refuses Kotlin versions newer than the bundle supports,
# which fails the Maven build itself (not this step). build-maven-project
# guards against that by disabling Kotlin extraction when needed.
build-mode: manual
# Add the code-quality query suite (maintainability/reliability — the
# SonarQube-type findings) on top of the security queries. Findings
# land in code scanning alongside the security results: while GitHub
# Code Quality is in preview, only its own default setup may upload
# to the Code Quality product (`analysis-kinds: code-quality` is
# rejected in custom workflows with "use the queries input with
# code-quality instead"). TODO: switch to Code Quality uploads when
# the preview opens for advanced setup.
queries: +code-quality

- name: "🍃 Spring Boot app: ⚒ Build for CodeQL with Maven"
id: codeql-build
if: steps.codeql-init.outcome == 'success'
# A failing build here is reported by 'build-deploy' already, never fail the workflow on it.
continue-on-error: true
uses: dsb-norge/github-actions/ci-cd/build-maven-project@v4
with:
dsb-build-envs: ${{ steps.build-env.outputs.json }}
build-only: "true"

- name: "🔎 Perform CodeQL Analysis for Java and Kotlin"
if: steps.codeql-build.outcome == 'success'
continue-on-error: true
uses: github/codeql-action/analyze@v4
with:
category: "/language:java-kotlin/app:${{ matrix.app-vars.application-name }}"

deploy-to-static:
name: Deploy app(s) to static environments
if: |
Expand Down
18 changes: 16 additions & 2 deletions ci-cd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,11 @@ Artifact attestation requires outbound HTTPS (443) access to Sigstore endpoints

CodeQL runs as part of the build for `spring-boot` (java-kotlin), `vue` (javascript-typescript), `python` and `maven-library` apps, with the `code-quality` query suite on top of the security queries. Maven-based apps with Kotlin sources additionally get [detekt](https://detekt.dev/) findings uploaded to code scanning, see [detekt](#detekt).

In `ci-cd-default.yml` the Java/Kotlin analysis of `spring-boot` apps runs in its own `codeql` job, in parallel with `build-deploy`, rather than tracing the regular build. That job calls `build-maven-project` with `build-only: true`, which runs `clean package -DskipTests` (keeping `maven-build-project-arguments`) and skips test reporting, artifact deploy, attestation and SBOM. The regular build and its tests thus run without the tracer, and the analysis is off the critical path. The `codeql` job is not part of `ci-cd-conclusion`, so it never blocks a build.

#### Kotlin version guard

Java/Kotlin analysis uses `build-mode: manual`, which means CodeQL's tracer injects itself into `kotlinc` during the real Maven build. When the project's Kotlin version is newer than the installed CodeQL bundle supports, the extractor throws before compilation:
Java/Kotlin analysis uses `build-mode: manual`, which means CodeQL's tracer injects itself into `kotlinc` during the Maven build it follows. When the project's Kotlin version is newer than the installed CodeQL bundle supports, the extractor throws before compilation:

```
com.semmle.extractor.java.interceptors.KotlinInterceptor$KotlinVersionTooRecentError:
Expand Down Expand Up @@ -290,6 +292,7 @@ flowchart TD
mavenDockerBuild["build-spring-boot-image"]
npmBuild["build-nodejs-project"]
npmDockerBuild["build-docker-image"]
cachePrune["Prune Own Artifacts From Cache"]
cacheSave["Save Dependencies Cache"]
eventTypeCheck{Event Type?}
ephemeralDeploy["deploy-to-ephemeral"]
Expand All @@ -301,14 +304,24 @@ flowchart TD
getToken --> createEnvs --> buildEnvsOutput --> cacheRestore --> appTypeCheck
appTypeCheck -- "spring-boot" --> mavenBuild --> mavenDockerBuild
appTypeCheck -- "vue" --> npmBuild --> npmDockerBuild
mavenDockerBuild --> cacheSave
mavenDockerBuild --> cachePrune --> cacheSave
npmDockerBuild --> cacheSave
cacheSave --> eventTypeCheck
eventTypeCheck -- "PR" --> ephemeralDeploy
eventTypeCheck -- "PR closed" --> teardownPR --> deletePRImages --> deletePRCaches
eventTypeCheck -- "push/manual" --> pruneImages
end

%% CodeQL job (matrix, spring-boot apps only), in parallel with build-deploy
subgraph codeqlJob["CodeQL Job (Matrix) (codeql)"]
codeqlEnvs["create-build-envs"]
codeqlCache["dependencies-cache-restore"]
codeqlInit["codeql-action/init"]
codeqlBuild["build-maven-project (build-only)"]
codeqlAnalyze["codeql-action/analyze"]
codeqlEnvs --> codeqlCache --> codeqlInit --> codeqlBuild --> codeqlAnalyze
end

%% AND join node for jobs that require both createMatrixJob and buildDeployJob
andDeployStatic["<b>Waits for both jobs</b>"]
andPruneMaven["<b>Waits for both jobs</b>"]
Expand All @@ -334,6 +347,7 @@ flowchart TD
%% Job dependencies (needs)
start --> createMatrixJob
matrixOutput -.-> buildDeployJob
matrixOutput -.-> codeqlJob
createMatrixJob --|waits on|--> andDeployStatic
buildDeployJob --|waits on|--> andDeployStatic
andDeployStatic --> deployStaticJob
Expand Down
Loading
Loading