From c6a3cccb463fe10c7f92294cbb459b8b6ca60d2b Mon Sep 17 00:00:00 2001 From: Marvin Lindner Date: Fri, 22 May 2026 15:29:42 +0200 Subject: [PATCH 1/4] chore: bootstrap ci/cd workflows --- .github/CODEOWNERS | 7 + .github/actions/build/action.yml | 33 +++++ .github/actions/cf-bind/action.yml | 78 +++++++++++ .github/actions/deploy-release/action.yml | 70 ++++++++++ .github/actions/integration-tests/action.yml | 34 +++++ .../actions/scan-with-blackduck/action.yml | 42 ++++-- .github/actions/scan-with-codeql/action.yml | 23 ++-- .github/actions/scan-with-sonar/action.yml | 80 ++++++++++++ .github/dependabot.yml | 30 +++++ .github/workflows/issue.yml | 31 +++++ .github/workflows/main.yml | 32 ++--- .github/workflows/pipeline.yml | 123 ++++++++++++++++++ .github/workflows/pr.yml | 18 +++ .github/workflows/prevent-issue-labeling.yml | 25 ++++ .github/workflows/release.yml | 122 +++++++++++++++++ .github/workflows/stale.yml | 25 ++++ 16 files changed, 729 insertions(+), 44 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/actions/build/action.yml create mode 100644 .github/actions/cf-bind/action.yml create mode 100644 .github/actions/deploy-release/action.yml create mode 100644 .github/actions/integration-tests/action.yml create mode 100644 .github/actions/scan-with-sonar/action.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/issue.yml create mode 100644 .github/workflows/pipeline.yml create mode 100644 .github/workflows/pr.yml create mode 100644 .github/workflows/prevent-issue-labeling.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/stale.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..9505331 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# Each pull request will require review and approval from the code owners +# before it can be merged. +# +# Learn more: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +# Global ownership — cdsmunich team owns everything +* @cap-java/cdsmunich diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml new file mode 100644 index 0000000..6544c1c --- /dev/null +++ b/.github/actions/build/action.yml @@ -0,0 +1,33 @@ +name: Maven Build +description: Builds a Maven project. + +inputs: + java-version: + description: The Java version the build will run with. + required: true + maven-version: + description: The Maven version the build will run with. + required: true + +runs: + using: composite + steps: + - name: Set up Java ${{ inputs.java-version }} + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: ${{ inputs.java-version }} + distribution: sapmachine + cache: maven + + - name: Set up Maven ${{ inputs.maven-version }} + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 + with: + maven-version: ${{ inputs.maven-version }} + + - name: Install @sap/cds-dk + run: npm i -g @sap/cds-dk@9.9.1 + shell: bash + + - name: Maven Build + run: mvn clean install -DskipTests -B -ntp -Dcds.install-node.skip -P skip-integration-tests + shell: bash diff --git a/.github/actions/cf-bind/action.yml b/.github/actions/cf-bind/action.yml new file mode 100644 index 0000000..bd6ca5f --- /dev/null +++ b/.github/actions/cf-bind/action.yml @@ -0,0 +1,78 @@ +name: Bind Cloud Foundry Services +description: Login to CF and bind the AI Core service for hybrid testing via cds bind. + +inputs: + cf-api: + description: Cloud Foundry API endpoint + required: true + cf-username: + description: Cloud Foundry username + required: true + cf-password: + description: Cloud Foundry password + required: true + cf-org: + description: Cloud Foundry organization + required: true + cf-space: + description: Cloud Foundry space + required: true + +runs: + using: composite + steps: + - name: Install CF CLI + shell: bash + env: + CF_CLI_VERSION: '8.18.3' + run: | + wget -q "https://packages.cloudfoundry.org/stable?release=linux64-binary&version=${CF_CLI_VERSION}&source=github-rel" -O cf-cli.tar.gz + tar -xzf cf-cli.tar.gz + sudo mv cf8 /usr/local/bin/cf + cf --version + + - name: CF Login + shell: bash + env: + CF_USERNAME: ${{ inputs.cf-username }} + CF_PASSWORD: ${{ inputs.cf-password }} + CF_API: ${{ inputs.cf-api }} + CF_ORG: ${{ inputs.cf-org }} + CF_SPACE: ${{ inputs.cf-space }} + run: | + for i in {1..5}; do + cf api "$CF_API" && \ + cf auth && \ + cf target -o "$CF_ORG" -s "$CF_SPACE" && break + if [ "$i" -eq 5 ]; then + echo "cf login failed after 5 attempts." + exit 1 + fi + echo "cf login failed, retrying ($i/5)..." + sleep 10 + done + + - name: Install @sap/cds-dk + shell: bash + run: | + npm i -g @sap/cds-dk@9.9.1 + echo "$(npm config get prefix)/bin" >> "${GITHUB_PATH}" + + - name: Install CDS dependencies + shell: bash + run: npm ci || npm install + working-directory: integration-tests + + - name: Bind ai-core + shell: bash + working-directory: integration-tests + run: | + for i in {1..5}; do + cds bind ai-core && break + echo "cds bind ai-core failed, retrying ($i/5)..." + sleep 30 + if [ "$i" -eq 5 ]; then + echo "cds bind ai-core failed after 5 attempts." + exit 1 + fi + done diff --git a/.github/actions/deploy-release/action.yml b/.github/actions/deploy-release/action.yml new file mode 100644 index 0000000..c330409 --- /dev/null +++ b/.github/actions/deploy-release/action.yml @@ -0,0 +1,70 @@ +name: Deploy Release to Maven Central +description: Deploys released artifacts to Maven Central repository. + +inputs: + user: + description: The user used for the upload (technical user for maven central upload) + required: true + password: + description: The password used for the upload (technical user for maven central upload) + required: true + pgp-pub-key: + description: The public pgp key ID + required: true + pgp-private-key: + description: The private pgp key + required: true + pgp-passphrase: + description: The passphrase for pgp + required: true + revision: + description: The revision of cds-feature-ai + required: true + maven-version: + description: The Maven version the build will run with. + required: true + +runs: + using: composite + steps: + - name: Set up Java + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + distribution: sapmachine + java-version: '17' + cache: maven + server-id: central + server-username: MAVEN_CENTRAL_USER + server-password: MAVEN_CENTRAL_PASSWORD + + - name: Set up Maven ${{ inputs.maven-version }} + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 + with: + maven-version: ${{ inputs.maven-version }} + + - name: Install @sap/cds-dk + run: npm i -g @sap/cds-dk@9.9.1 + shell: bash + + - name: Import GPG Key + run: | + echo "${{ inputs.pgp-private-key }}" | gpg --batch --passphrase "$PASSPHRASE" --import + shell: bash + env: + PASSPHRASE: ${{ inputs.pgp-passphrase }} + + - name: Deploy to Maven Central + run: > + mvn -B -ntp --show-version + -Dmaven.install.skip=true + -Dmaven.test.skip=true + -Dcds.install-node.skip + -Dgpg.passphrase="$GPG_PASSPHRASE" + -Dgpg.keyname="$GPG_PUB_KEY" + clean deploy -P deploy-release,skip-integration-tests + shell: bash + env: + MAVEN_CENTRAL_USER: ${{ inputs.user }} + MAVEN_CENTRAL_PASSWORD: ${{ inputs.password }} + GPG_PASSPHRASE: ${{ inputs.pgp-passphrase }} + GPG_PUB_KEY: ${{ inputs.pgp-pub-key }} diff --git a/.github/actions/integration-tests/action.yml b/.github/actions/integration-tests/action.yml new file mode 100644 index 0000000..bae0ec6 --- /dev/null +++ b/.github/actions/integration-tests/action.yml @@ -0,0 +1,34 @@ +name: Integration Tests +description: Run integration tests using Maven with cds bind for service bindings. + +inputs: + java-version: + description: The Java version the build shall run with. + required: true + maven-version: + description: The Maven version the build shall run with. + required: true + +runs: + using: composite + steps: + - name: Set up Java ${{ inputs.java-version }} + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: ${{ inputs.java-version }} + distribution: sapmachine + cache: maven + + - name: Setup Maven ${{ inputs.maven-version }} + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 + with: + maven-version: ${{ inputs.maven-version }} + + - name: Build dependencies for integration tests + run: mvn clean install -ntp -B -pl cds-feature-ai-core,cds-feature-recommendations,cds-starter-ai -am -DskipTests -Dcds.install-node.skip + shell: bash + + - name: Integration Tests (spring + mtx-local) + run: cds bind --exec -- mvn clean verify -ntp -B -f pom.xml -P mtx-integration-tests -Dcds.install-node.skip + working-directory: integration-tests + shell: bash diff --git a/.github/actions/scan-with-blackduck/action.yml b/.github/actions/scan-with-blackduck/action.yml index 8b23ade..f2ef79e 100644 --- a/.github/actions/scan-with-blackduck/action.yml +++ b/.github/actions/scan-with-blackduck/action.yml @@ -15,9 +15,13 @@ inputs: maven-version: description: The Maven version the build shall run with. required: true + version: + description: The project version to report to Black Duck (e.g. release tag). If empty, falls back to the Maven `revision` reduced to major-minor. + required: false + default: '' scan_mode: - description: The scan mode to use (FULL or RAPID) - default: 'RAPID' + description: The scan mode to use (FULL uploads a report to the Black Duck server; RAPID is a fast policy gate without server upload). + default: 'FULL' required: false runs: @@ -35,10 +39,19 @@ runs: with: maven-version: ${{ inputs.maven-version }} - - name: Get Revision - id: get-revision + - name: Resolve Project Version + id: resolve-version + env: + VERSION_INPUT: ${{ inputs.version }} run: | - echo "REVISION=$(mvn help:evaluate -Dexpression=revision -q -DforceStdout)" >> $GITHUB_OUTPUT + if [ -n "$VERSION_INPUT" ]; then + VERSION="$VERSION_INPUT" + else + REVISION=$(mvn help:evaluate -Dexpression=revision -q -DforceStdout) + VERSION=$(echo "$REVISION" | cut -d. -f1,2) + fi + echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT" + echo "Resolved BlackDuck project version: $VERSION" shell: bash - name: BlackDuck Security Scan @@ -49,15 +62,18 @@ runs: blackducksca_scan_full: ${{ inputs.scan_mode == 'FULL' }} github_token: ${{ inputs.github_token }} detect_args: > - --detect.project.name=com.sap.cds.feature.ai - --detect.project.version.name=${{ steps.get-revision.outputs.REVISION }} + --detect.project.name=cds-ai + --detect.project.version.name=${{ steps.resolve-version.outputs.VERSION }} + --detect.project.user.groups=CDSJAVA-OPEN-SOURCE --detect.included.detector.types=MAVEN - --detect.excluded.directories=**/node_modules,**/*test*,**/localrepo,**/target/site,**/*-site.jar,**/samples/** + --detect.excluded.directories=**/*test*,**/samples/** + --detect.maven.included.modules=cds-feature-ai-core,cds-feature-recommendations,cds-starter-ai + --detect.maven.excluded.scopes=test,provided --detect.tools=DETECTOR,BINARY_SCAN + --detect.timeout=6000 --detect.risk.report.pdf=false + --detect.policy.check.fail.on.severities=NONE + --detect.force.success.on.skip=true + --blackduck.signature.scanner.memory=4096 + --blackduck.trust.cert=true --logging.level.detect=INFO - env: - BLACKDUCKSCA_TOKEN: ${{ inputs.blackduck_token }} - BLACKDUCKSCA_URL: https://sap.blackducksoftware.com/ - BLACKDUCK_API_TOKEN: ${{ inputs.blackduck_token }} - DETECT_MAVEN_BUILD_COMMAND: '-pl com.sap.cds:cds-feature-ai' diff --git a/.github/actions/scan-with-codeql/action.yml b/.github/actions/scan-with-codeql/action.yml index bdaca32..5cbd337 100644 --- a/.github/actions/scan-with-codeql/action.yml +++ b/.github/actions/scan-with-codeql/action.yml @@ -1,5 +1,5 @@ -name: Scan with CodeQL -description: Scans the project with CodeQL +name: CodeQL Analysis +description: Runs CodeQL security analysis on the project. inputs: java-version: @@ -8,11 +8,15 @@ inputs: maven-version: description: The Maven version to use for the build. required: true + language: + description: The CodeQL language to analyze (java-kotlin or actions). + required: true runs: using: composite steps: - name: Set up Java ${{ inputs.java-version }} + if: inputs.language == 'java-kotlin' uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: java-version: ${{ inputs.java-version }} @@ -20,6 +24,7 @@ runs: cache: maven - name: Set up Maven ${{ inputs.maven-version }} + if: inputs.language == 'java-kotlin' uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 with: maven-version: ${{ inputs.maven-version }} @@ -27,23 +32,21 @@ runs: - name: Initialize CodeQL uses: github/codeql-action/init@ed410739ba306e4ebe5e123421a6bd694e494a2b # v4 with: - languages: java-kotlin - build-mode: manual + languages: ${{ inputs.language }} + build-mode: ${{ inputs.language == 'java-kotlin' && 'manual' || 'none' }} queries: security-extended - name: Install @sap/cds-dk - run: npm i -g @sap/cds-dk - shell: bash - - - name: Install npm dependencies - run: npm install + if: inputs.language == 'java-kotlin' + run: npm i -g @sap/cds-dk@9.9.1 shell: bash - name: Build Java code + if: inputs.language == 'java-kotlin' run: mvn clean compile -B -ntp -Dcds.install-node.skip shell: bash - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@ed410739ba306e4ebe5e123421a6bd694e494a2b # v4 with: - category: "/language:java-kotlin" + category: "/language:${{ inputs.language }}" diff --git a/.github/actions/scan-with-sonar/action.yml b/.github/actions/scan-with-sonar/action.yml new file mode 100644 index 0000000..4d10f98 --- /dev/null +++ b/.github/actions/scan-with-sonar/action.yml @@ -0,0 +1,80 @@ +name: Scan with SonarQube +description: Scans the project with SonarQube + +inputs: + sonarq-token: + description: The token to use for SonarQube authentication + required: true + github-token: + description: The token to use for GitHub authentication + required: true + java-version: + description: The version of Java to use + required: true + maven-version: + description: The version of Maven to use + required: true + +runs: + using: composite + + steps: + - name: Set up Java ${{inputs.java-version}} + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: ${{inputs.java-version}} + distribution: sapmachine + cache: maven + + - name: Set up Maven ${{inputs.maven-version}} + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 + with: + maven-version: ${{inputs.maven-version}} + + - name: Get Revision + id: get-revision + run: | + echo "REVISION=$(mvn help:evaluate -Dexpression=revision -q -DforceStdout)" >> $GITHUB_OUTPUT + shell: bash + + - name: Build project for SonarQube scan + run: cds bind --exec -- mvn clean verify -ntp -B -P mtx-integration-tests -Dcds.install-node.skip + working-directory: integration-tests + shell: bash + + - name: Verify JaCoCo reports exist + run: | + echo "=== Checking JaCoCo reports ===" + find . -name "jacoco.xml" -type f + for module in cds-feature-ai-core cds-feature-recommendations cds-starter-ai; do + if [ -f "$module/target/site/jacoco/jacoco.xml" ]; then + echo "Found: $module/target/site/jacoco/jacoco.xml" + else + echo "Missing: $module/target/site/jacoco/jacoco.xml" + fi + done + if [ -f "coverage-report/target/site/jacoco-aggregate/jacoco.xml" ]; then + echo "Found: coverage-report/target/site/jacoco-aggregate/jacoco.xml" + else + echo "Missing: coverage-report/target/site/jacoco-aggregate/jacoco.xml" + exit 1 + fi + shell: bash + + - name: SonarQube Scan + run: > + mvn org.sonarsource.scanner.maven:sonar-maven-plugin:sonar + -Dsonar.host.url=https://sonar.tools.sap + -Dsonar.token="${SONAR_TOKEN}" + -Dsonar.projectKey=cds-ai + -Dsonar.projectVersion=${{ steps.get-revision.outputs.REVISION }} + -Dsonar.qualitygate.wait=true + -Dsonar.java.source=17 + -Dsonar.exclusions=**/samples/**,**/integration-tests/** + -Dsonar.coverage.jacoco.xmlReportPaths=${{ github.workspace }}/coverage-report/target/site/jacoco-aggregate/jacoco.xml + -Dsonar.coverage.exclusions=**/src/test/**,**/src/gen/** + -B -ntp + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.github-token }} + SONAR_TOKEN: ${{ inputs.sonarq-token }} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8be0b3d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,30 @@ +version: 2 +updates: + - package-ecosystem: maven + directories: + - "/" + schedule: + interval: weekly + cooldown: + default-days: 7 + groups: + minor-patch: + patterns: + - "*" + update-types: + - minor + - patch + + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + cooldown: + default-days: 7 + groups: + minor-patch: + patterns: + - "*" + update-types: + - minor + - patch diff --git a/.github/workflows/issue.yml b/.github/workflows/issue.yml new file mode 100644 index 0000000..e771f0a --- /dev/null +++ b/.github/workflows/issue.yml @@ -0,0 +1,31 @@ +name: Label issues + +permissions: {} + +on: + issues: + types: + - opened + +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - run: gh issue edit "$NUMBER" --add-label "$LABELS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} + LABELS: New + + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `👋 Hello @${context.payload.issue.user.login}, thank you for submitting this issue. Our team is reviewing your report and will follow up with you as soon as possible.` + }) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 63b8bbf..738ce1b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,11 +1,5 @@ name: CI - MAIN -permissions: - contents: read - actions: read - security-events: write - packages: read - env: MAVEN_VERSION: '3.9.15' @@ -19,6 +13,8 @@ jobs: name: Blackduck Scan runs-on: ubuntu-latest timeout-minutes: 30 + permissions: + contents: read steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -29,19 +25,13 @@ jobs: blackduck_token: ${{ secrets.BLACK_DUCK_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} maven-version: ${{ env.MAVEN_VERSION }} - scan_mode: RAPID - - codeql: - name: CodeQL Scan - runs-on: ubuntu-latest - timeout-minutes: 60 - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + scan_mode: FULL - - name: Scan With CodeQL - continue-on-error: true - uses: ./.github/actions/scan-with-codeql - with: - maven-version: ${{ env.MAVEN_VERSION }} - java-version: '17' + build-and-test: + uses: ./.github/workflows/pipeline.yml + permissions: + contents: read + security-events: write + actions: read + packages: read + secrets: inherit diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml new file mode 100644 index 0000000..fbc1873 --- /dev/null +++ b/.github/workflows/pipeline.yml @@ -0,0 +1,123 @@ +name: Reusable Workflow + +env: + MAVEN_VERSION: '3.9.15' + +on: + workflow_call: + +jobs: + tests: + name: Tests (Java ${{ matrix.java-version }}) + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + java-version: [17, 21] + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Set up Java ${{ matrix.java-version }} + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: ${{ matrix.java-version }} + distribution: sapmachine + cache: maven + + - name: Set up Maven + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 + with: + maven-version: ${{ env.MAVEN_VERSION }} + + - name: Install @sap/cds-dk + run: npm i -g @sap/cds-dk@9.9.1 + shell: bash + + - name: Run Tests + run: mvn test -ntp -B -P skip-integration-tests -Dcds.install-node.skip + + integration-tests: + name: Integration Tests (Java ${{ matrix.java-version }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + java-version: [17, 21] + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Bind CF Services + uses: ./.github/actions/cf-bind + with: + cf-api: ${{ secrets.CF_API_AWS }} + cf-username: ${{ secrets.CF_USERNAME }} + cf-password: ${{ secrets.CF_PASSWORD }} + cf-org: ${{ secrets.CF_ORG_AWS }} + cf-space: ${{ secrets.CF_SPACE_AWS }} + + - name: Integration Tests + uses: ./.github/actions/integration-tests + with: + java-version: ${{ matrix.java-version }} + maven-version: ${{ env.MAVEN_VERSION }} + + sonarqube-scan: + name: SonarQube Scan + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 0 + + - name: Bind CF Services + uses: ./.github/actions/cf-bind + with: + cf-api: ${{ secrets.CF_API_AWS }} + cf-username: ${{ secrets.CF_USERNAME }} + cf-password: ${{ secrets.CF_PASSWORD }} + cf-org: ${{ secrets.CF_ORG_AWS }} + cf-space: ${{ secrets.CF_SPACE_AWS }} + + - name: SonarQube Scan + uses: ./.github/actions/scan-with-sonar + with: + java-version: '17' + maven-version: ${{ env.MAVEN_VERSION }} + sonarq-token: ${{ secrets.SONARQ_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + codeql: + name: CodeQL Analysis (${{ matrix.language }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + security-events: write + packages: read + actions: read + contents: read + strategy: + fail-fast: false + matrix: + language: [java-kotlin, actions] + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: CodeQL Analysis + uses: ./.github/actions/scan-with-codeql + with: + java-version: '17' + maven-version: ${{ env.MAVEN_VERSION }} + language: ${{ matrix.language }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..198ed92 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,18 @@ +name: CI - PR + +permissions: + actions: read + contents: read + packages: read + security-events: write + +on: + workflow_dispatch: + pull_request: + branches: [main] + types: [reopened, synchronize, opened] + +jobs: + build-and-test: + uses: ./.github/workflows/pipeline.yml + secrets: inherit diff --git a/.github/workflows/prevent-issue-labeling.yml b/.github/workflows/prevent-issue-labeling.yml new file mode 100644 index 0000000..6c35030 --- /dev/null +++ b/.github/workflows/prevent-issue-labeling.yml @@ -0,0 +1,25 @@ +name: Prevent "New" Label on Issues + +permissions: {} + +on: + issues: + types: [labeled] + +jobs: + remove_new_label: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Remove "New" label if applied by non-bot user + if: > + contains(github.event.issue.labels.*.name, 'New') && + github.event.label.name == 'New' && + github.event.sender.login != 'github-actions[bot]' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + run: | + gh issue edit "$ISSUE_NUMBER" --remove-label "New" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6148ac0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,122 @@ +name: Deploy to Maven Central + +env: + JAVA_VERSION: '17' + MAVEN_VERSION: '3.9.15' + +on: + release: + types: ["released"] + +jobs: + requires-approval: + runs-on: ubuntu-latest + name: "Waiting for release approval" + environment: release-approval + permissions: + contents: read + steps: + - name: Approval Step + run: echo "Release has been approved!" + + verify-version: + needs: requires-approval + name: Verify Version Matches Tag + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ github.event.release.tag_name }} + + - name: Set up Java + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: sapmachine + cache: maven + + - name: Set up Maven + uses: stCarolas/setup-maven@d6af6abeda15e98926a57b5aa970a96bb37f97d1 # v5 + with: + maven-version: ${{ env.MAVEN_VERSION }} + + - name: Verify pom.xml revision matches release tag + run: | + TAG="${{ github.event.release.tag_name }}" + REVISION=$(mvn help:evaluate -Dexpression=revision -q -DforceStdout) + echo "Tag: $TAG" + echo "Revision: $REVISION" + if [ "$TAG" != "$REVISION" ]; then + echo "::error::Release tag '$TAG' does not match pom.xml '$REVISION'. Open a 'Prep release' PR to bump the version before tagging." + exit 1 + fi + shell: bash + + blackduck: + needs: verify-version + name: Blackduck Scan + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ github.event.release.tag_name }} + + - name: Scan With Black Duck + uses: ./.github/actions/scan-with-blackduck + with: + blackduck_token: ${{ secrets.BLACK_DUCK_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} + maven-version: ${{ env.MAVEN_VERSION }} + version: ${{ github.event.release.tag_name }} + + build: + name: Build + runs-on: ubuntu-latest + timeout-minutes: 30 + needs: verify-version + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ github.event.release.tag_name }} + + - name: Build + uses: ./.github/actions/build + with: + java-version: ${{ env.JAVA_VERSION }} + maven-version: ${{ env.MAVEN_VERSION }} + + deploy: + name: Deploy to Maven Central + runs-on: ubuntu-latest + timeout-minutes: 30 + needs: [blackduck, build] + environment: release + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ github.event.release.tag_name }} + + - name: Deploy + uses: ./.github/actions/deploy-release + with: + user: ${{ secrets.CENTRAL_REPOSITORY_USER }} + password: ${{ secrets.CENTRAL_REPOSITORY_PASS }} + pgp-pub-key: ${{ secrets.PGP_PUBKEY_ID }} + pgp-private-key: ${{ secrets.PGP_PRIVATE_KEY }} + pgp-passphrase: ${{ secrets.PGP_PASSPHRASE }} + revision: ${{ github.event.release.tag_name }} + maven-version: ${{ env.MAVEN_VERSION }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..4feaa66 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,25 @@ +name: "Close stale issues" + +permissions: {} + +on: + schedule: + - cron: "30 1 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + actions: write + issues: write + pull-requests: write + steps: + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10 + with: + close-issue-message: "This issue has been automatically closed due to 2 weeks of inactivity. If you believe this was a mistake, please reopen or comment to continue the discussion." + days-before-stale: -1 + days-before-issue-close: 14 + days-before-pr-close: -1 + stale-issue-label: "author action" + remove-issue-stale-when-updated: true + labels-to-remove-when-unstale: "author action" From 68c20454a2a2051e9ce9553faf938fed9060c5df Mon Sep 17 00:00:00 2001 From: Marvin Lindner Date: Fri, 22 May 2026 15:43:01 +0200 Subject: [PATCH 2/4] chore: address pr bot review findings --- .github/actions/cf-bind/action.yml | 4 ++-- .github/actions/deploy-release/action.yml | 3 ++- .github/actions/scan-with-sonar/action.yml | 4 ++-- .github/workflows/issue.yml | 2 +- .github/workflows/release.yml | 3 ++- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/actions/cf-bind/action.yml b/.github/actions/cf-bind/action.yml index bd6ca5f..5391a6f 100644 --- a/.github/actions/cf-bind/action.yml +++ b/.github/actions/cf-bind/action.yml @@ -69,10 +69,10 @@ runs: run: | for i in {1..5}; do cds bind ai-core && break - echo "cds bind ai-core failed, retrying ($i/5)..." - sleep 30 if [ "$i" -eq 5 ]; then echo "cds bind ai-core failed after 5 attempts." exit 1 fi + echo "cds bind ai-core failed, retrying ($i/5)..." + sleep 30 done diff --git a/.github/actions/deploy-release/action.yml b/.github/actions/deploy-release/action.yml index c330409..2c70176 100644 --- a/.github/actions/deploy-release/action.yml +++ b/.github/actions/deploy-release/action.yml @@ -48,9 +48,10 @@ runs: - name: Import GPG Key run: | - echo "${{ inputs.pgp-private-key }}" | gpg --batch --passphrase "$PASSPHRASE" --import + echo "$PGP_PRIVATE_KEY" | gpg --batch --passphrase "$PASSPHRASE" --import shell: bash env: + PGP_PRIVATE_KEY: ${{ inputs.pgp-private-key }} PASSPHRASE: ${{ inputs.pgp-passphrase }} - name: Deploy to Maven Central diff --git a/.github/actions/scan-with-sonar/action.yml b/.github/actions/scan-with-sonar/action.yml index 4d10f98..e658949 100644 --- a/.github/actions/scan-with-sonar/action.yml +++ b/.github/actions/scan-with-sonar/action.yml @@ -1,5 +1,5 @@ name: Scan with SonarQube -description: Scans the project with SonarQube +description: Scans the project with SonarQube. Caller is responsible for setting up the CF binding (e.g. via the cf-bind action) before invoking this action. inputs: sonarq-token: @@ -34,7 +34,7 @@ runs: - name: Get Revision id: get-revision run: | - echo "REVISION=$(mvn help:evaluate -Dexpression=revision -q -DforceStdout)" >> $GITHUB_OUTPUT + echo "REVISION=$(mvn help:evaluate -Dexpression=revision -q -DforceStdout)" >> "$GITHUB_OUTPUT" shell: bash - name: Build project for SonarQube scan diff --git a/.github/workflows/issue.yml b/.github/workflows/issue.yml index e771f0a..6aa729f 100644 --- a/.github/workflows/issue.yml +++ b/.github/workflows/issue.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 with: script: | - github.rest.issues.createComment({ + await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6148ac0..2c468e9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,8 +45,9 @@ jobs: maven-version: ${{ env.MAVEN_VERSION }} - name: Verify pom.xml revision matches release tag + env: + TAG: ${{ github.event.release.tag_name }} run: | - TAG="${{ github.event.release.tag_name }}" REVISION=$(mvn help:evaluate -Dexpression=revision -q -DforceStdout) echo "Tag: $TAG" echo "Revision: $REVISION" From ea16b96a63d39f3a291c970113323a55dee2253c Mon Sep 17 00:00:00 2001 From: Marvin Lindner Date: Fri, 22 May 2026 16:00:37 +0200 Subject: [PATCH 3/4] fix: specify ai-core service instance and key --- .github/actions/cf-bind/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/cf-bind/action.yml b/.github/actions/cf-bind/action.yml index 5391a6f..e88154e 100644 --- a/.github/actions/cf-bind/action.yml +++ b/.github/actions/cf-bind/action.yml @@ -68,7 +68,7 @@ runs: working-directory: integration-tests run: | for i in {1..5}; do - cds bind ai-core && break + cds bind ai-core -2 ai-core:ai-core-key && break if [ "$i" -eq 5 ]; then echo "cds bind ai-core failed after 5 attempts." exit 1 From 8e0f5a3da6ef0fb0d0afcaee3de1d0c843c42ba2 Mon Sep 17 00:00:00 2001 From: Marvin Lindner Date: Fri, 22 May 2026 16:09:04 +0200 Subject: [PATCH 4/4] rename blackduck identifier --- .github/actions/scan-with-blackduck/action.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/actions/scan-with-blackduck/action.yml b/.github/actions/scan-with-blackduck/action.yml index f2ef79e..f8e3292 100644 --- a/.github/actions/scan-with-blackduck/action.yml +++ b/.github/actions/scan-with-blackduck/action.yml @@ -10,7 +10,7 @@ inputs: required: true java-version: description: The version of Java to use - default: '17' + default: "17" required: false maven-version: description: The Maven version the build shall run with. @@ -18,10 +18,10 @@ inputs: version: description: The project version to report to Black Duck (e.g. release tag). If empty, falls back to the Maven `revision` reduced to major-minor. required: false - default: '' + default: "" scan_mode: description: The scan mode to use (FULL uploads a report to the Black Duck server; RAPID is a fast policy gate without server upload). - default: 'FULL' + default: "FULL" required: false runs: @@ -62,7 +62,7 @@ runs: blackducksca_scan_full: ${{ inputs.scan_mode == 'FULL' }} github_token: ${{ inputs.github_token }} detect_args: > - --detect.project.name=cds-ai + --detect.project.name=com.sap.cds.cds-ai --detect.project.version.name=${{ steps.resolve-version.outputs.VERSION }} --detect.project.user.groups=CDSJAVA-OPEN-SOURCE --detect.included.detector.types=MAVEN