Skip to content
Merged
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
353 changes: 353 additions & 0 deletions .github/workflows/pnp-processor-build-native-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,353 @@
name: test and build native docker image

on:
workflow_call:
secrets:
SECRET_AUTH:
required: true
GCLOUD_AUTH_STAGING:
required: true
inputs:
GCR_image_name:
description: 'A name of the image to be created in GCR'
required: true
type: string
path-to-pom:
description: 'Path to the pom file. When working directory is set, this path is relative to it.'
required: true
type: string
path-to-dockerfile:
description: 'Path to dockerfile which builds the service. When working directory is set, this path is relative to it.'
required: true
type: string
slack-channel:
description: 'A slack channel which needs to be notified in case of failure'
required: false
type: string
pact-application-id:
required: false
type: string
pact-webhook-id:
required: false
type: string
jdk-distribution:
description: 'JDK Distribution name'
type: string
required: false
default: 'temurin'
java-version:
description: 'Java version used in the project'
type: string
required: false
default: '25'
graalvm-version:
description: 'GraalVM version used in the project'
type: string
required: false
default: '25.0'
graalvm-distribution:
description: 'GraalVM distribution used for native image builds'
type: string
required: false
default: 'graalvm'
do-pact-consumer-tests:
description: 'If true runs tests defining data contracts'
type: boolean
required: false
default: false
do-pact-provider-tests:
description: 'If true runs tests verifying implementation against data-contracts'
type: boolean
required: false
default: false
is-pact-consumer-can-i-deploy-dry-run:
description: 'If true, consumer can-i-deploy step will be executed in dry-run mode'
type: boolean
required: false
default: false
is-pact-provider-can-i-deploy-dry-run:
description: 'If true, provider can-i-deploy step will be executed in dry-run mode'
type: boolean
required: false
default: false
enable-kafka-streams-lint:
description: 'If true runs Kafka Streams Lint'
type: boolean
required: false
default: true
working-directory:
description: 'working directory'
required: false
type: string
tag-prefix:
description: 'Release tag prefix. Must be provided in case of multiple projects in the same repository. Each project needs to have its own prefix. Use {service-prefix}-v format.'
type: string
required: false
default: 'v'
jobs:
test:
name: Test
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: extenda/actions/gcp-secret-manager@v0
with:
service-account-key: ${{ secrets.SECRET_AUTH }}
secrets: |
GITHUB-TOKEN: github-token
NEXUS_PASSWORD: nexus-password
NEXUS_USERNAME: nexus-username

- name: Setup gcloud
uses: extenda/actions/setup-gcloud@v0
with:
service-account-key: ${{ secrets.SECRET_AUTH }}

- name: Set up JDK
uses: actions/setup-java@v5
with:
distribution: ${{ inputs.jdk-distribution }}
java-version: ${{ inputs.java-version }}
cache: maven

- name: Determine version
uses: extenda/actions/conventional-version@v0
id: semver
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
build-number: ${{ github.run_number }}
tag-prefix: ${{ inputs.tag-prefix }}

- name: Verify with Maven
uses: extenda/actions/maven@v0
with:
args: verify -s settings.xml
version: ${{ steps.semver.outputs.version }}
service-account-key: ${{ secrets.SECRET_AUTH }}
working-directory: ${{ inputs.working-directory || '.' }}

- name: Kafka Streams lint - fetch detekt config file
if: ${{ inputs.enable-kafka-streams-lint == true }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
DETEKT_FILE="${{ inputs.working-directory || '.' }}/pnp-detekt-plugin.yml"

curl -sSL \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3.raw" \
"https://api.github.com/repos/extenda/shared-workflows/contents/.github/configs/pnp-detekt-plugin.yml" \
-o "$DETEKT_FILE"

- name: Kafka Streams lint - get Detekt plugin for kafka streams
if: ${{ inputs.enable-kafka-streams-lint == true }}
uses: extenda/actions/maven@v0
with:
args: dependency:get -Dartifact=com.retailsvc:pnp-processors-core-common:0.173.2:jar
service-account-key: ${{ secrets.SECRET_AUTH }}
working-directory: ${{ inputs.working-directory || '.' }}

- name: Kafka Streams lint - build classpath with maven
if: ${{ inputs.enable-kafka-streams-lint == true }}
uses: extenda/actions/maven@v0
with:
args: dependency:build-classpath -DincludeScope=runtime -Dmdep.outputFile=cp.txt -DexcludeGroupIds=org.jetbrains.kotlin -DexcludeArtifactIds=kotlin-compiler-embeddable -q
service-account-key: ${{ secrets.SECRET_AUTH }}
working-directory: ${{ inputs.working-directory || '.' }}

- name: Kafka Streams lint - build classpath lib dir with maven
if: ${{ inputs.enable-kafka-streams-lint == true }}
uses: extenda/actions/maven@v0
with:
args: dependency:copy-dependencies -DoutputDirectory=lib -DexcludeGroupIds=org.jetbrains.kotlin -DexcludeArtifactIds=kotlin-compiler-embeddable -q
service-account-key: ${{ secrets.SECRET_AUTH }}
working-directory: ${{ inputs.working-directory || '.' }}

- name: Kafka Streams lint - download Detekt CLI
if: ${{ inputs.enable-kafka-streams-lint == true }}
run: |
set -euo pipefail
BASE_DIR="${{ inputs.working-directory || '.' }}"
mkdir -p "$BASE_DIR/lib"

curl -fsSL \
-o "$BASE_DIR/lib/detekt-cli.jar" \
"https://github.com/detekt/detekt/releases/download/v1.23.8/detekt-cli-1.23.8-all.jar"

ls -lh "$BASE_DIR/lib/detekt-cli.jar"
jar tf "$BASE_DIR/lib/detekt-cli.jar" | grep -E "io/gitlab/arturbosch/detekt/cli/Main.class|dev/detekt/cli/Main.class"

- name: Kafka Streams lint - verify with Detekt
if: ${{ inputs.enable-kafka-streams-lint == true }}
shell: bash
run: |
set -euo pipefail
BASE_DIR="${{ inputs.working-directory || '.' }}"

# Detekt 1.23.8 supports up to JVM target 22; use 21 for this repo.
DETEKT_JVM_TARGET="21"

DETEKT_CLASSPATH="$BASE_DIR/target/classes:$(cat "$BASE_DIR/cp.txt")"
LIB_JARS_CLASSPATH="$(find "$BASE_DIR/lib" -maxdepth 1 -type f -name '*.jar' -printf ':%p' | tr -d '\n')"

DETEKT_PLUGIN_JAR="$HOME/.m2/repository/com/retailsvc/pnp-processors-core-common/0.173.2/pnp-processors-core-common-0.173.2.jar"
KAFKA_STREAMS_JAR="$(tr ':' '\n' < "$BASE_DIR/cp.txt" | grep -E '/kafka-streams-[^/]+\.jar$' | head -n1 || true)"

if [[ ! -f "$DETEKT_PLUGIN_JAR" ]]; then
echo "Detekt plugin jar not found: $DETEKT_PLUGIN_JAR"
exit 1
fi

if [[ -z "$KAFKA_STREAMS_JAR" || ! -f "$KAFKA_STREAMS_JAR" ]]; then
echo "Kafka Streams jar not found in cp.txt; required for custom detekt rule classloading"
exit 1
fi

# Detekt expects multiple plugin paths as a comma-separated list.
DETEKT_PLUGINS="$DETEKT_PLUGIN_JAR,$KAFKA_STREAMS_JAR"

java -jar "$BASE_DIR/lib/detekt-cli.jar" \
--input "$BASE_DIR/src/main/kotlin" \
--config "$BASE_DIR/pnp-detekt-plugin.yml" \
--classpath "${DETEKT_CLASSPATH}${LIB_JARS_CLASSPATH}" \
--jvm-target "$DETEKT_JVM_TARGET" \
--plugins "$DETEKT_PLUGINS"

- name: Analyze with Sonar
uses: extenda/actions/sonar-scanner@v0
with:
sonar-host: https://sonarcloud.io
service-account-key: ${{ secrets.SECRET_AUTH }}
working-directory: './${{ inputs.working-directory }}'

- name: Publish pacts
if: ${{ inputs.do-pact-consumer-tests == true }}
uses: extenda/actions/pact-publish@v0
with:
pacts-directory: target/pacts
service-account-key: ${{ secrets.SECRET_AUTH }}

- name: Can i deploy - consumer?
if: ${{ inputs.do-pact-consumer-tests == true }}
uses: extenda/actions/pact-can-i-deploy@v0
with:
service-account-key: ${{ secrets.SECRET_AUTH }}
application-name: ${{ inputs.pact-application-id }}
retry-while-unknown: 15
retry-interval: 10
dry-run: ${{ inputs.is-pact-consumer-can-i-deploy-dry-run }}
env: "staging"

- name: Can i deploy - provider?
if: ${{ inputs.do-pact-provider-tests == true }}
uses: extenda/actions/pact-can-i-deploy@v0
with:
service-account-key: ${{ secrets.SECRET_AUTH }}
application-name: ${{ inputs.pact-application-id }}
retry-while-unknown: 15
retry-interval: 10
dry-run: ${{ inputs.is-pact-provider-can-i-deploy-dry-run }}
env: "staging"

- name: Notify Slack if failed
if: ${{ failure() && github.ref == 'refs/heads/master' }}
uses: extenda/actions/slack-notify@v0
with:
text: |
*Build failed for ${{ github.repository }}: ${{ github.workflow }}* :heavy_exclamation_mark:
Build failed on ${{ github.event_name }} event. Workflow: ${{ github.workflow }}. Job: ${{github.job}}. Run id: <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|${{ github.run_id }}>
channel: ${{ inputs.slack-channel }}
service-account-key: ${{ secrets.SECRET_AUTH }}

build-image:
name: Build native image
if: github.ref == 'refs/heads/master'
runs-on:
labels: ubuntu-latest-4-cores
timeout-minutes: 80
needs: test
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: extenda/actions/gcp-secret-manager@v0
with:
service-account-key: ${{ secrets.SECRET_AUTH }}
secrets: |
GITHUB-TOKEN: github-token
NEXUS_PASSWORD: nexus-password
NEXUS_USERNAME: nexus-username

- name: Determine version
uses: extenda/actions/conventional-version@v0
id: semver
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag-prefix: ${{ inputs.tag-prefix }}
build-number: ${{ github.run_number }}

- uses: graalvm/setup-graalvm@v1
with:
version: ${{ inputs.graalvm-version }}
distribution: ${{ inputs.graalvm-distribution }}
github-token: ${{ secrets.GITHUB_TOKEN }}
cache: 'maven'
native-image-job-reports: 'true'

- name: Setup gcloud v1
uses: extenda/actions/setup-gcloud@v0
with:
service-account-key: ${{ secrets.SECRET_AUTH }}

- name: Package native image with Maven
uses: extenda/actions/maven@v0
with:
args: package -Dpackaging=native-image -DskipTests -s settings.xml
version: ${{ steps.semver.outputs.version }}
service-account-key: ${{ secrets.SECRET_AUTH }}
working-directory: ${{ inputs.working-directory || '.' }}

- name: Setup gcloud v2
uses: extenda/actions/setup-gcloud@v0
with:
service-account-key: ${{ secrets.SECRET_AUTH }}

- name: Authenticate docker
run: |
gcloud --quiet auth configure-docker

- name: Docker build image and push
run: |
IMAGE=eu.gcr.io/extenda/${{ inputs.GCR_image_name }}
WORKING_DIR=${{ inputs.working-directory || '.' }}
docker build ./${WORKING_DIR} -f ${{ inputs.path-to-dockerfile }} --build-arg jar_version=${{ steps.semver.outputs.version }} -t $IMAGE:${{ steps.semver.outputs.version }} -t $IMAGE:${{ github.sha }} -t $IMAGE:latest
docker push --all-tags $IMAGE

- name: Attest image
uses: extenda/actions/binary-auth-attestation@v0
with:
image-path: eu.gcr.io/extenda/${{ inputs.GCR_image_name }}
service-account-key: ${{ secrets.GCLOUD_AUTH_STAGING }}

- name: Notify Slack if failed
if: failure()
uses: extenda/actions/slack-notify@v0
with:
text: |
*Build failed for ${{ github.repository }}: ${{ github.workflow }}* :heavy_exclamation_mark:
Build failed on ${{ github.event_name }} event. Workflow: ${{ github.workflow }}. Job: ${{github.job}}. Run id: <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|${{ github.run_id }}>
channel: ${{ inputs.slack-channel }}
service-account-key: ${{ secrets.SECRET_AUTH }}