diff --git a/.github/workflows/ci-cd-default.yml b/.github/workflows/ci-cd-default.yml index f3d1020b..1ac8f349 100644 --- a/.github/workflows/ci-cd-default.yml +++ b/.github/workflows/ci-cd-default.yml @@ -83,12 +83,19 @@ on: 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 @@ -97,22 +104,6 @@ jobs: 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 @@ -135,6 +126,25 @@ jobs: 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 @@ -245,38 +255,8 @@ jobs: 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: | @@ -287,29 +267,6 @@ jobs: 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 @@ -487,6 +444,34 @@ jobs: 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: | @@ -557,6 +542,116 @@ jobs: 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) }} + 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: | diff --git a/ci-cd/README.md b/ci-cd/README.md index de0e09ee..3b71b578 100644 --- a/ci-cd/README.md +++ b/ci-cd/README.md @@ -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: @@ -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"] @@ -301,7 +304,7 @@ 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 @@ -309,6 +312,16 @@ flowchart TD 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["Waits for both jobs"] andPruneMaven["Waits for both jobs"] @@ -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 diff --git a/ci-cd/build-maven-project/action.yml b/ci-cd/build-maven-project/action.yml index 838ae05c..02fc4136 100644 --- a/ci-cd/build-maven-project/action.yml +++ b/ci-cd/build-maven-project/action.yml @@ -89,6 +89,15 @@ inputs: description: "JSON object with extra environment variables to set in same scope as maven is invoked." required: false default: "" + build-only: + description: | + Set to 'true' for builds that run next to the regular build, ie. the CodeQL job and the + Spring Boot image build. Skips tests, test reporting, artifact deploy, attestation and SBOM, + as the regular build already takes care of those. Without 'mvn-cmd' it runs + 'clean package -DskipTests' (keeping the app's arguments, a full 'maven-build-project-command' + is still honoured). + required: false + default: "false" dsb-build-envs: description: | DSB build environment variables JSON. @@ -165,6 +174,7 @@ runs: INPUT_DSB_BUILD_ENVS: ${{ inputs.dsb-build-envs }} INPUT_MVN_VERSION_CMD: ${{ inputs.mvn-version-cmd }} INPUT_MVN_CMD: ${{ inputs.mvn-cmd }} + INPUT_BUILD_ONLY: ${{ inputs.build-only }} GITHUB_WORKSPACE: ${{ github.workspace }} run: deno run --allow-env --allow-read --allow-write ${{ github.action_path }}/action/1_mvn-cmd.ts @@ -213,7 +223,7 @@ runs: - name: Publish Test Results uses: EnricoMi/publish-unit-test-result-action@567cc7f8dcea3eba5da355f6ebc95663310d8a07 - if: ${{ !cancelled() && github.action == 'maven-build' }} + if: ${{ !cancelled() && github.action == 'maven-build' && inputs.build-only != 'true' }} with: check_name: "Test Results - ${{ fromJSON(inputs.dsb-build-envs).application-source-path }}" files: | @@ -222,7 +232,7 @@ runs: - name: Log Test Results uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 # v3.0.0 - if: ${{ !cancelled() && github.action == 'maven-build'}} + if: ${{ !cancelled() && github.action == 'maven-build' && inputs.build-only != 'true' }} with: name: "Maven Test Results - ${{ fromJSON(inputs.dsb-build-envs).application-source-path }}" path: "${{ fromJSON(inputs.dsb-build-envs).application-source-path }}/**/target/**/TEST-*.xml" @@ -230,6 +240,7 @@ runs: fail-on-empty: "false" - id: deploy-artifacts + if: inputs.build-only != 'true' shell: bash env: INPUT_BUILD_ENVS: "${{ inputs.dsb-build-envs }}" diff --git a/ci-cd/build-maven-project/action/1_mvn-cmd.ts b/ci-cd/build-maven-project/action/1_mvn-cmd.ts index 3019e455..74725490 100644 --- a/ci-cd/build-maven-project/action/1_mvn-cmd.ts +++ b/ci-cd/build-maven-project/action/1_mvn-cmd.ts @@ -8,6 +8,7 @@ const MVN_VERSION_ARGUMENTS_DEFAULT: string = '-B' const MVN_VERSION_GOALS_DEFAULT: string = 'versions:set' const MVN_ARGUMENTS_DEFAULT: string = '-B' const MVN_GOALS_DEFAULT: string = 'clean install' +const MVN_GOALS_BUILD_ONLY: string = 'clean package' /** * Main function for the GitHub Action. @@ -58,18 +59,25 @@ export async function run(): Promise { core.setOutput('mvn-version-cmd', mvnVersionCmd) core.endGroup() - const skipBuildTests: boolean = typeof appVars['maven-build-skip-tests'] === 'boolean' ? appVars['maven-build-skip-tests'] : appVars['maven-build-skip-tests'] === 'true' + // Build-only mode (used by the CodeQL job): compile and package without tests, and without + // 'install' so nothing lands in the local repo. The app's goals are ignored on purpose, they may + // carry e.g. sonar, a full 'maven-build-project-command' is still honoured. + const buildOnly: boolean = getActionInput('build-only') === 'true' + const skipBuildTests: boolean = buildOnly || (typeof appVars['maven-build-skip-tests'] === 'boolean' ? appVars['maven-build-skip-tests'] : appVars['maven-build-skip-tests'] === 'true') // --- Determine Maven Build Command --- core.startGroup('Determining Maven Build Command') + if (buildOnly) { + core.info(`Build-only mode: using goals '${MVN_GOALS_BUILD_ONLY}' and skipping tests.`) + } const mvnCmd = determineMavenCommand( mvnCmdInput, appVars, safePomFilePath, - MVN_GOALS_DEFAULT, + buildOnly ? MVN_GOALS_BUILD_ONLY : MVN_GOALS_DEFAULT, MVN_ARGUMENTS_DEFAULT, 'maven-build-project-command', - 'maven-build-project-goals', + buildOnly ? undefined : 'maven-build-project-goals', 'maven-build-project-arguments', skipBuildTests, ) diff --git a/ci-cd/build-maven-project/action/test_mvn-cmd.ts b/ci-cd/build-maven-project/action/test_mvn-cmd.ts new file mode 100644 index 00000000..8d4c89f4 --- /dev/null +++ b/ci-cd/build-maven-project/action/test_mvn-cmd.ts @@ -0,0 +1,55 @@ +import { assertEquals, assertStringIncludes } from 'common/test_deps.ts' +import { mockCore, resetMockCore } from 'common/utils/mock-core.ts' +import { join, setCore } from 'common/deps.ts' +import { AppVars } from 'common/interfaces/application-variables.ts' +import { run } from './1_mvn-cmd.ts' + +// Replace the real core with the mock +setCore(mockCore) + +/** Runs the step against a temp workspace with a pom.xml, returns the determined build command. */ +async function runWith(appVars: Partial, buildOnly?: string): Promise { + resetMockCore() + const workspace = await Deno.makeTempDir() + const previousWorkspace = Deno.env.get('GITHUB_WORKSPACE') + try { + await Deno.writeTextFile(join(workspace, 'pom.xml'), '') + Deno.env.set('GITHUB_WORKSPACE', workspace) + mockCore.outputs['DSB_BUILD_ENVS'] = JSON.stringify({ 'application-source-path': './', 'application-version': '1.2.3', ...appVars }) + if (buildOnly !== undefined) mockCore.outputs['BUILD_ONLY'] = buildOnly + await run() + return mockCore.outputs['mvn-cmd'] + } finally { + if (previousWorkspace === undefined) Deno.env.delete('GITHUB_WORKSPACE') + else Deno.env.set('GITHUB_WORKSPACE', previousWorkspace) + await Deno.remove(workspace, { recursive: true }) + } +} + +Deno.test('mvn-cmd - regular build uses the app goals and runs tests', async () => { + const cmd = await runWith({ 'maven-build-project-goals': 'clean install sonar:sonar', 'maven-build-project-arguments': '-Duser.timezone=Europe/Oslo' }) + assertStringIncludes(cmd, 'clean install sonar:sonar') + assertStringIncludes(cmd, '-Duser.timezone=Europe/Oslo') + assertEquals(cmd.includes('-DskipTests'), false) +}) + +Deno.test('mvn-cmd - build-only packages without tests and ignores the app goals', async () => { + const cmd = await runWith({ 'maven-build-project-goals': 'clean install sonar:sonar', 'maven-build-project-arguments': '-Duser.timezone=Europe/Oslo' }, 'true') + assertEquals(cmd.endsWith(' clean package'), true) + assertStringIncludes(cmd, '-Duser.timezone=Europe/Oslo') + assertStringIncludes(cmd, '-DskipTests') + assertEquals(cmd.includes('install'), false) + assertEquals(cmd.includes('sonar'), false) +}) + +Deno.test('mvn-cmd - build-only keeps a full maven-build-project-command but skips tests', async () => { + const cmd = await runWith({ 'maven-build-project-command': 'mvn -B -Pci verify' }, 'true') + assertStringIncludes(cmd, '-Pci verify') + assertStringIncludes(cmd, '-DskipTests') +}) + +Deno.test('mvn-cmd - build-only false behaves like a regular build', async () => { + const cmd = await runWith({}, 'false') + assertEquals(cmd.endsWith(' clean install'), true) + assertEquals(cmd.includes('-DskipTests'), false) +}) diff --git a/ci-cd/build-spring-boot-image/action.yml b/ci-cd/build-spring-boot-image/action.yml index 3ee10db4..f889cd25 100644 --- a/ci-cd/build-spring-boot-image/action.yml +++ b/ci-cd/build-spring-boot-image/action.yml @@ -188,6 +188,8 @@ runs: run: deno run --allow-env --allow-read --allow-write ${{ github.action_path }}/action/5_filter-build-envs.ts # build docker image + # build-only: the regular build already attested the maven artifacts and produced the SBOM used + # below, so don't do that a second time here. - uses: dsb-norge/github-actions/ci-cd/build-maven-project@v4 if: fromJSON(inputs.dsb-build-envs)['has-changes'] == true with: @@ -195,6 +197,7 @@ runs: mvn-cmd: ${{ steps.mvn-cmd.outputs.mvn-cmd }} set-extra-envs: ${{ steps.extra-envs.outputs.json }} dsb-build-envs: ${{ steps.filter-build-envs.outputs.dsb-build-envs }} + build-only: "true" - id: tag-and-push if: fromJSON(inputs.dsb-build-envs)['has-changes'] == true