Compare commits

..

1 commit

52 changed files with 57 additions and 2111 deletions

View file

@ -14,7 +14,9 @@
"customizations": { "customizations": {
"vscode": { "vscode": {
"extensions": [ "extensions": [
"amazonwebservices.aws-toolkit-vscode" "amazonwebservices.aws-toolkit-vscode",
"biomejs.biome",
"likec4.likec4-vscode"
] ]
} }
} }

View file

@ -5,8 +5,11 @@ inputs:
apikey: apikey:
description: Aikido CI API key description: Aikido CI API key
required: true required: true
organization:
description: Organization or owner name
required: true
repository-name: repository-name:
description: Full repository name (owner/repo) description: Repository name
required: true required: true
branch-name: branch-name:
description: Branch to scan against description: Branch to scan against
@ -20,8 +23,9 @@ runs:
- --apikey - --apikey
- ${{ inputs.apikey }} - ${{ inputs.apikey }}
- --repositoryname - --repositoryname
- ${{ inputs.repository-name }} - ${{ inputs.organization }}/${{ inputs.repository-name }}
- --branchname - --branchname
- ${{ inputs.branch-name }} - ${{ inputs.branch-name }}
- --force-create-repository-for-branch - --force-create-repository-for-branch
- --include-dev-deps - --include-dev-deps

View file

@ -3,7 +3,6 @@ name: Aikido Security Full Scan
on: on:
schedule: schedule:
- cron: '0 0 * * *' - cron: '0 0 * * *'
workflow_dispatch:
jobs: jobs:
aikido-full-scan: aikido-full-scan:
@ -16,4 +15,4 @@ jobs:
- name: Run Aikido full scan - name: Run Aikido full scan
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aikido-full-scan@aikido-full-scan-v1 uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aikido-full-scan@aikido-full-scan-v1
with: with:
apikey: ${{ secrets.AIKIDO_CLIENT_API_KEY }} apikey: ${{ secrets.AIKIDO_CLIENT_API_KEY }}

View file

@ -2,6 +2,8 @@ name: Aikido Security PR Check
on: on:
pull_request: pull_request:
branches:
- '*'
concurrency: concurrency:
group: ${{ forgejo.workflow }}-${{ forgejo.ref }} group: ${{ forgejo.workflow }}-${{ forgejo.ref }}
@ -18,4 +20,4 @@ jobs:
- name: Security scan - name: Security scan
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aikido-pr-scan@aikido-pr-scan-v1 uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aikido-pr-scan@aikido-pr-scan-v1
with: with:
apikey: ${{ secrets.AIKIDO_CLIENT_API_KEY }} apikey: ${{ secrets.AIKIDO_CLIENT_API_KEY }}

View file

@ -1,89 +0,0 @@
name: tag-release
# Manually create or move a major release tag for a shared action.
# Tag format: <action-name>-v<major> (e.g. checkout-v1, pnpm-build-v2)
#
# If the tag already exists it is force-moved to the selected ref.
on:
workflow_dispatch:
inputs:
action:
description: Action to release
required: true
type: choice
options:
- aikido-full-scan
- aikido-pr-scan
- aws-configure
- cache
- checkout
- download-artifact
- esb-deploy
- helm-deploy
- i18n-sync
- inject-content
- maven-build
- pnpm-build
- playwright-merge
- playwright-run
- publish-npm-package
- publish-rust-crate
- publish-static-contents
- rust-build
- terraform-apply
- terraform-validate
- upload-artifact
- vacuum-lint
major-version:
description: 'Major version number (e.g. 1)'
required: true
type: string
ref:
description: 'Branch, tag, or commit SHA to tag (defaults to the default branch)'
required: false
type: string
default: ''
permissions:
contents: write
jobs:
tag-release:
runs-on: stackit-ubuntu-22
steps:
- name: Checkout
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/checkout@checkout-v1
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- name: Validate major version
env:
MAJOR: ${{ inputs.major-version }}
run: |
if ! echo "$MAJOR" | grep -qE '^[0-9]+$'; then
echo "::error::major-version must be a positive integer, got: $MAJOR"
exit 1
fi
- name: Create or move major tag
env:
ACTION: ${{ inputs.action }}
MAJOR: ${{ inputs.major-version }}
run: |
set -euo pipefail
TAG="${ACTION}-v${MAJOR}"
COMMIT=$(git rev-parse HEAD)
echo "Tag : $TAG"
echo "Commit : $COMMIT"
git config user.name "forgejo-actions[bot]"
git config user.email "forgejo-actions[bot]@noreply.forgejo.org"
git tag -f "$TAG" "$COMMIT"
git push origin -f "refs/tags/$TAG"
echo "::notice::Tag $TAG now points to $COMMIT"

View file

@ -2,6 +2,7 @@ name: validate-shared-actions
on: on:
pull_request: pull_request:
types: [opened, reopened, synchronize]
permissions: permissions:
contents: read contents: read
@ -11,8 +12,7 @@ jobs:
runs-on: stackit-ubuntu-22 runs-on: stackit-ubuntu-22
steps: steps:
- name: Checkout - name: Checkout
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/checkout@checkout-v1 uses: actions/checkout@v4
- name: Validate shared action metadata - name: Validate shared action metadata
uses: docker://data.forgejo.org/forgejo/runner:12 uses: docker://data.forgejo.org/forgejo/runner:12
with: with:

View file

@ -9,24 +9,11 @@ Shared actions for Forgejo CI/CD pipelines.
| [aikido-full-scan](aikido-full-scan) | Aikido full scan | | [aikido-full-scan](aikido-full-scan) | Aikido full scan |
| [aikido-pr-scan](aikido-pr-scan) | Aikido PR scan | | [aikido-pr-scan](aikido-pr-scan) | Aikido PR scan |
| [aws-configure](aws-configure) | Authenticate with AWS via OIDC | | [aws-configure](aws-configure) | Authenticate with AWS via OIDC |
| [cache](cache) | Cache files between workflow runs |
| [checkout](checkout) | Action for checking out a repository | | [checkout](checkout) | Action for checking out a repository |
| [download-artifact](download-artifact) | Download Forgejo Actions artifacts by name or pattern |
| [helm-deploy](helm-deploy) | Deploy a service to Kubernetes via Helm over SSH |
| [i18n-sync](i18n-sync) | Fetch translations from i18n.schmalz.com and open a pull request |
| [inject-content](inject-content) | Inject content into a file by appending or overwriting |
| [maven-build](maven-build) | Action for building and validating Maven projects |
| [pnpm-build](pnpm-build) | Action for building and validating with PNPM | | [pnpm-build](pnpm-build) | Action for building and validating with PNPM |
| [playwright-merge](playwright-merge) | Merge Playwright shard blob reports and publish consolidated reports |
| [playwright-run](playwright-run) | Run Playwright tests for one shard and upload its blob report |
| [publish-npm-package](publish-npm-package) | Publish a PNPM package to JFrog Artifactory |
| [publish-rust-crate](publish-rust-crate) | Publish a Rust crate to JFrog Artifactory |
| [publish-static-contents](publish-static-contents) | Syncs frontend assets to S3 and invalidates a CloudFront distribution | | [publish-static-contents](publish-static-contents) | Syncs frontend assets to S3 and invalidates a CloudFront distribution |
| [rust-build](rust-build) | Set up Rust toolchain, run checks, and build via the project's build.sh |
| [terraform-apply](terraform-apply) | Apply Terraform configuration files using the official Terraform CLI |
| [terraform-validate](terraform-validate) | Validate Terraform configuration files using the official Terraform CLI | | [terraform-validate](terraform-validate) | Validate Terraform configuration files using the official Terraform CLI |
| [upload-artifact](upload-artifact) | Upload files as a Forgejo Actions artifact |
| [vacuum-lint](vacuum-lint) | Validate and lint OpenAPI specifications using Vacuum |
## Security ## Security
@ -42,4 +29,4 @@ Reference actions from your project's workflow:
# see each action's README for inputs # see each action's README for inputs
``` ```
Each action has its own README with inputs, usage examples, and notes. Each action has its own README with inputs, usage examples, and notes.

View file

@ -12,15 +12,9 @@ inputs:
runs: runs:
using: composite using: composite
steps: steps:
- name: Normalize repository name - uses: ./.forgejo/actions/internal-aikido-full-scan
id: repo
shell: bash
run: |
repo="${{ forgejo.repository }}"
echo "name=${repo#/}" >> $GITHUB_OUTPUT
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/internal-aikido-full-scan@internal-aikido-full-scan-v1
with: with:
apikey: ${{ inputs.apikey }} apikey: ${{ inputs.apikey }}
repository-name: ${{ steps.repo.outputs.name }} organization: ${{ forgejo.repository_owner }}
repository-name: ${{ forgejo.event.repository.name }}
branch-name: ${{ forgejo.ref_name }} branch-name: ${{ forgejo.ref_name }}

View file

@ -16,7 +16,7 @@ inputs:
runs: runs:
using: composite using: composite
steps: steps:
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/internal-aikido-pr-scan@internal-aikido-pr-scan-v1 - uses: ./.forgejo/actions/internal-aikido-pr-scan
with: with:
apikey: ${{ inputs.apikey }} apikey: ${{ inputs.apikey }}
organization: ${{ forgejo.repository_owner }} organization: ${{ forgejo.repository_owner }}

View file

@ -8,8 +8,6 @@ Authenticate with AWS via OIDC and export credentials to the environment.
|-------|----------|---------|-------------| |-------|----------|---------|-------------|
| `role-arn` | Yes | | Full IAM role ARN | | `role-arn` | Yes | | Full IAM role ARN |
| `region` | No | `eu-central-1` | AWS region | | `region` | No | `eu-central-1` | AWS region |
| `aws-access-key-id` | No | | AWS access key to use. Only required for some authentication types. |
| `aws-secret-access-key` | No | | AWS secret key to use. Only required for some authentication types. |
## Usage ## Usage

View file

@ -9,12 +9,6 @@ inputs:
description: AWS region description: AWS region
required: false required: false
default: eu-central-1 default: eu-central-1
aws-access-key-id:
description: AWS access key to use. Only required for some authentication types.
required: false
aws-secret-access-key:
description: AWS secret key to use. Only required for some authentication types.
required: false
runs: runs:
using: composite using: composite
@ -26,5 +20,3 @@ runs:
with: with:
role-to-assume: ${{ inputs.role-arn }} role-to-assume: ${{ inputs.role-arn }}
aws-region: ${{ inputs.region }} aws-region: ${{ inputs.region }}
aws-access-key-id: ${{ inputs.aws-access-key-id }}
aws-secret-access-key: ${{ inputs.aws-secret-access-key }}

51
cache/README.md vendored
View file

@ -1,51 +0,0 @@
# cache
Composite wrapper around actions/cache pinned to a specific commit SHA to prevent supply chain attacks via tag or branch hijacking.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `path` | Yes | — | List of files, directories, and wildcard patterns to cache and restore |
| `key` | Yes | — | An explicit key for saving and restoring the cache |
| `restore-keys` | No | `''` | Ordered multiline string of prefix-matched keys used for restoring stale cache |
| `upload-chunk-size` | No | `''` | Chunk size in bytes used to split large files during upload |
| `enableCrossOsArchive` | No | `false` | Allow caches saved on one OS to be restored on another |
| `fail-on-cache-miss` | No | `false` | Fail the workflow if no cache entry is found |
| `lookup-only` | No | `false` | Check if a cache entry exists without downloading it |
## Outputs
| Output | Description |
|--------|-------------|
| `cache-hit` | `true` if an exact match was found for the primary key |
## Usage
```yaml
- name: Cache pnpm store
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: ~/.local/share/pnpm/store
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
```
```yaml
- name: Cache node_modules
id: node-modules-cache
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
if: steps.node-modules-cache.outputs.cache-hit != 'true'
run: npm ci
```
## Notes
- Pinned to `actions/cache` commit SHA `0057852b` (v4.3.0) to prevent supply chain attacks via tag or branch hijacking.
- Upstream action: [code.forgejo.org/actions/cache](https://code.forgejo.org/actions/cache).

54
cache/action.yml vendored
View file

@ -1,54 +0,0 @@
name: Schmalz Cache
description: >
Composite wrapper around actions/cache pinned to a specific commit SHA
to prevent supply chain attacks via tag or branch hijacking.
inputs:
path:
description: A list of files, directories, and wildcard patterns to cache and restore.
required: true
key:
description: An explicit key for saving and restoring the cache.
required: true
restore-keys:
description: An ordered multiline string listing prefix-matched keys used for restoring stale cache if no cache hit occurred for key.
required: false
default: ''
upload-chunk-size:
description: The chunk size used to split up large files during upload, in bytes.
required: false
default: ''
enableCrossOsArchive:
description: When enabled, allows Windows runners to save or restore caches that can be used on other platforms.
required: false
default: 'false'
fail-on-cache-miss:
description: Fail the workflow if cache entry is not found.
required: false
default: 'false'
lookup-only:
description: Check if a cache entry exists for the given input(s) without downloading the cache.
required: false
default: 'false'
outputs:
cache-hit:
description: A boolean value to indicate an exact match was found for the primary key.
value: ${{ steps.cache.outputs.cache-hit }}
runs:
using: composite
steps:
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# actions/cache v4.3.0 — https://code.forgejo.org/actions/cache/commits/tag/v4.3.0
- name: Cache
id: cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: ${{ inputs.path }}
key: ${{ inputs.key }}
restore-keys: ${{ inputs.restore-keys }}
upload-chunk-size: ${{ inputs.upload-chunk-size }}
enableCrossOsArchive: ${{ inputs.enableCrossOsArchive }}
fail-on-cache-miss: ${{ inputs.fail-on-cache-miss }}
lookup-only: ${{ inputs.lookup-only }}

View file

@ -33,9 +33,9 @@ runs:
using: composite using: composite
steps: steps:
# Pinned to commit SHA instead of a tag to prevent supply chain attacks. # Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# actions/checkout v4.3.1 — https://code.forgejo.org/actions/checkout/commits/tag/v4.3.1 # actions/checkout v6.0.2 — https://code.forgejo.org/actions/checkout/commits/tag/v6.0.2
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with: with:
ref: ${{ inputs.ref }} ref: ${{ inputs.ref }}
repository: ${{ inputs.repository }} repository: ${{ inputs.repository }}

View file

@ -1,46 +0,0 @@
# download-artifact
Download Forgejo Actions artifacts by name or pattern. Thin wrapper around `forgejo/download-artifact` pinned to a specific commit SHA to prevent supply chain attacks.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `name` | No | `""` | Exact artifact name or glob pattern (e.g. `blob-report-*`). If omitted, all artifacts for the run are downloaded. |
| `path` | No | `.` | Local destination directory |
| `merge-multiple` | No | `false` | When true, merge all matched artifacts into a single directory |
## Usage
Download a single artifact by name:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/download-artifact@download-artifact-v1
with:
name: my-artifact
path: dist/
```
Download all artifacts matching a pattern and merge into one directory:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/download-artifact@download-artifact-v1
with:
name: blob-report-*
path: all-blob-reports/
merge-multiple: "true"
```
Download all artifacts for the run:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/download-artifact@download-artifact-v1
with:
path: artifacts/
```
## Notes
- Wraps `forgejo/download-artifact` v4 (node20), compatible with Ubuntu 22 runners.
- The underlying action is pinned to a commit SHA rather than a mutable tag to prevent supply chain attacks.
- When `merge-multiple` is false (default), each matched artifact is extracted into its own subdirectory under `path`.

View file

@ -1,30 +0,0 @@
name: Schmalz Download Artifact
description: >
Download Forgejo Actions artifacts by name or pattern.
Thin wrapper around forgejo/download-artifact, pinned to a specific SHA.
inputs:
name:
description: Exact artifact name or glob pattern (e.g. 'blob-report-*'). If omitted, all artifacts for the run are downloaded.
required: false
default: ""
path:
description: Local destination directory
required: false
default: "."
merge-multiple:
description: When true, merge all matched artifacts into a single directory
required: false
default: "false"
runs:
using: composite
steps:
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# forgejo/download-artifact v4 — https://code.forgejo.org/forgejo/download-artifact/commits/tag/v4
- name: Download artifact
uses: https://code.forgejo.org/forgejo/download-artifact@d8d0a99033603453ad2255e58720b460a0555e1e
with:
name: ${{ inputs.name }}
path: ${{ inputs.path }}
merge-multiple: ${{ inputs.merge-multiple }}

View file

@ -1,32 +0,0 @@
# esb-deploy
Deploy a service to an ESB docker host.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `docker-host` | Yes | - | esbdb3.schmalzgroup.net, esbdb4.schmalzgroup.net, esbdb2-stage.schmalzgroup.net|
| `java-version` | Yes | 25 | Same as default of the maven-build action |
| `maven-profile` | No | `test` | Maven profile to activate during deploy |
| `maven-settings` | **Yes** | — | Secret containing the `settings.xml` content used for repository authentication |
| `service` | Yes | — | Name of the service to deploy |
| `stage` | No | true | If true this is a stage deployment |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/esb-deploy@esb-deploy-v1
with:
service: my-service
docker-host: esbdocker2-stage.schmalzgroup.net
java-version: 8
maven-profile: test
maven-settings: ${{ secrets.MAVEN_SETTINGS }}
stage: true
```
## Notes
- The compose files are extracted from variables. They can be provided on the organization or repository level.
- The action uses the maven-build action to build the service. The pom.xml has to be in the root directory

View file

@ -1,64 +0,0 @@
name: Deploy ESB
description: Deploy a service to an ESB docker host.
inputs:
docker-host:
description: Docker host to deploy to
required: true
maven-profile:
required: false
default: 'test'
description: 'Maven profile to use for the build'
maven-settings:
description: Secret containing the settings.xml content used for repository authentication
required: true
java-version:
description: Java version to use for the build
required: true
service:
description: Name of the service to deploy
required: true
stage:
description: Whether to deploy to stage environment (true) or production environment (false)
required: false
default: 'true'
runs:
using: composite
steps:
- name: Create compose files
shell: bash
env:
BASE_COMPOSE: ${{ vars.DOCKER_COMPOSE }}
SU_COMPOSE: ${{ vars.DOCKER_COMPOSE_SU }}
run: |
printf '%s\n' "$BASE_COMPOSE" > compose.yml
printf '%s\n' "$SU_COMPOSE" > compose-su.yml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/maven-build@maven-build-v1
with:
phase: verify
maven-settings: ${{ inputs.maven-settings }}
verify-goals: clean package
java-version: ${{ inputs.java-version }}
maven-profile: ${{ inputs.maven-profile}}
- name: Compose stage
if: ${{ inputs.stage == 'true' }}
shell: bash
env:
SERVICE: ${{ inputs.service }}
run: |
echo "Deploying $SERVICE to stage environment"
export DOCKER_HOST="tcp://${{ inputs.docker-host }}:2375"
docker compose -f compose.yml -f compose-su.yml up -d --build --no-deps "$SERVICE"
- name: Compose prod
if: ${{ inputs.stage != 'true' }}
shell: bash
env:
SERVICE: ${{ inputs.service }}
run: |
echo "Deploying $SERVICE to production environment"
export DOCKER_HOST="tcp://${{ inputs.docker-host }}:2375"
docker compose -f compose.yml up -d --build --no-deps "$SERVICE"

View file

@ -1,34 +0,0 @@
# helm-deploy
Deploy a service to Kubernetes via Helm over SSH.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `service-name` | Yes | — | Helm release name |
| `helm-cluster` | Yes | — | Cluster to deploye to (one of `internal_stage` or `internal_prod`) |
| `image-tag` | Yes | — | Docker image tag to deploy |
| `ssh-key` | Yes | — | Private SSH key content |
| `overrides-file` | No | `kubernetes/overrides-pu.yaml` | Local path to Helm values override file |
| `namespace` | No | `dsp` | Kubernetes namespace |
| `helm-repo` | No | `nexus-helm-repository` | Helm chart repository name |
| `helm-chart` | No | `DSP-Blueprint` | Chart name in the repository |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/helm-deploy@helm-deploy-v1
with:
service-name: my-service
helm-host: dsp1-stage.schmalzgroup.net
image-tag: ${{ github.sha }}
ssh-key: ${{ secrets.HELM_SSH_KEY }}
```
## Notes
- The SSH key is written to a temporary file with `600` permissions and removed after the job, even on failure.
- The overrides file is copied to the remote host via `scp` before the Helm upgrade.
- `helm upgrade --install` is run with `--atomic` so a failed release is automatically rolled back.
- `StrictHostKeyChecking=no` is used; ensure the host is trusted within your network or add host verification as needed.

View file

@ -1,105 +0,0 @@
name: helm-deploy
description: Deploy a service to Kubernetes via Helm over SSH
inputs:
service-name:
description: Helm release name
required: true
helm-cluster:
description: Name of the target Kubernetes cluster to deploy to
required: true
overrides-file:
description: Local path to Helm values override file
required: false
default: kubernetes/overrides-pu.yaml
image-tag:
description: Docker image tag to deploy
required: true
ssh-key:
description: Private SSH key content
required: true
namespace:
description: Kubernetes namespace
required: false
default: dsp
helm-repo:
description: Helm chart repository name
required: false
default: nexus-helm-repository
helm-chart:
description: Chart name in the repo
required: false
default: DSP-Blueprint
runs:
using: composite
steps:
- name: Setup SSH key
shell: bash
env:
SSH_KEY: ${{ inputs.ssh-key }}
run: |
set -euo pipefail
SSH_KEY_FILE=$(mktemp)
printf '%s\n' "$SSH_KEY" > "$SSH_KEY_FILE"
chmod 600 "$SSH_KEY_FILE"
echo "SSH_KEY_FILE=$SSH_KEY_FILE" >> "$GITHUB_ENV"
- name: Map cluster name to target host
id: map-host
shell: bash
env:
HELM_CLUSTER: ${{ inputs.helm-cluster }}
run: |
case "$HELM_CLUSTER" in
internal_stage) echo "host=dsp1-stage.schmalzgroup.net" ;;
internal_prod) echo "host=dsp1.schmalzgroup.net" ;;
*) echo "Invalid cluster '$HELM_CLUSTER'. Must be 'internal_stage' or 'internal_prod'." && exit 1 ;;
esac >> "$GITHUB_OUTPUT"
- name: Copy overrides file
shell: bash
env:
HELM_HOST: ${{ steps.map-host.outputs.host }}
SERVICE_NAME: ${{ inputs.service-name }}
OVERRIDES_FILE: ${{ inputs.overrides-file }}
run: |
set -euo pipefail
scp -i "$SSH_KEY_FILE" \
-o StrictHostKeyChecking=no \
-o BatchMode=yes \
-o ConnectTimeout=10 \
"$OVERRIDES_FILE" \
"root@${HELM_HOST}:/tmp/${SERVICE_NAME}-overrides.yaml"
- name: Helm deploy
shell: bash
env:
HELM_HOST: ${{ steps.map-host.outputs.host }}
SERVICE_NAME: ${{ inputs.service-name }}
NAMESPACE: ${{ inputs.namespace }}
HELM_REPO: ${{ inputs.helm-repo }}
HELM_CHART: ${{ inputs.helm-chart }}
IMAGE_TAG: ${{ inputs.image-tag }}
run: |
set -euo pipefail
ssh -i "$SSH_KEY_FILE" \
-o StrictHostKeyChecking=no \
-o BatchMode=yes \
-o ConnectTimeout=10 \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=5 \
"root@${HELM_HOST}" \
"helm repo update && \
helm upgrade --install --create-namespace \
-n '${NAMESPACE}' \
'${SERVICE_NAME}' \
'${HELM_REPO}/${HELM_CHART}' \
-f '/tmp/${SERVICE_NAME}-overrides.yaml' \
--set image.tag='${IMAGE_TAG}' \
--atomic --debug"
- name: Cleanup SSH key
if: always()
shell: bash
run: rm -f "$SSH_KEY_FILE"

View file

@ -1,46 +0,0 @@
# i18n-sync
Fetches the latest translations from i18n.schmalz.com, commits them to a `chore/i18n` branch, and opens a pull request against the destination branch.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `i18n-application` | Yes | — | Application key in i18n.schmalz.com (e.g. `calculator`) |
| `locales-folder` | No | `locales` | Path to the locales directory relative to the repository root |
| `format-folder` | No | `""` | Directory containing the `package.json` whose format script should be run after downloading translations. Leave empty to skip formatting. |
| `format-script` | No | `format` | pnpm script name to run for formatting (e.g. `format`, `format:fix`) |
| `jfrog-token` | No | `""` | JFrog npm auth token (required when `format-folder` is set) |
| `forgejo-token` | Yes | — | Forgejo token with `contents:write` and `pull-requests:write` access |
| `destination-branch` | No | `dev` | Target branch for the pull request |
## Usage
Minimal — no formatting:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/i18n-sync@i18n-sync-v1
with:
i18n-application: my-app
forgejo-token: ${{ secrets.FORGEJO_I18N_UPDATE_TOKEN }}
```
With formatting after download:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/i18n-sync@i18n-sync-v1
with:
i18n-application: my-app
locales-folder: frontend/locales
format-folder: frontend
jfrog-token: ${{ secrets.JFROG_TOKEN }}
forgejo-token: ${{ secrets.FORGEJO_I18N_UPDATE_TOKEN }}
```
## Notes
- The action fails fast if the `chore/i18n` branch already exists, indicating a previous update is still pending review.
- If no translation changes are detected after downloading, the action exits cleanly without creating a branch or PR.
- Deleted languages (files removed from i18n.schmalz.com) are also staged for removal via `git add --all`.
- `jq` is installed automatically if not present on the runner.
- The `forgejo-token` must belong to a user or bot with `contents:write` and `pull-requests:write` permissions on the repository.

View file

@ -1,131 +0,0 @@
name: i18n Sync
description: >
Fetches the latest translations from i18n.schmalz.com, commits them to a
chore/i18n branch, and opens a pull request against the destination branch.
inputs:
i18n-application:
description: Application key in i18n.schmalz.com (e.g. "calculator")
required: true
locales-folder:
description: Path to the locales directory relative to the repository root
required: false
default: locales
format-folder:
description: >
Directory containing the package.json whose format script should be run
after downloading translations. Leave empty to skip formatting.
required: false
default: ""
format-script:
description: pnpm script name to run for formatting (e.g. "format", "format:fix")
required: false
default: format
jfrog-token:
description: JFrog npm auth token (required when format-folder is set)
required: false
default: ""
forgejo-token:
description: Forgejo token with contents:write and pull-requests:write access
required: true
destination-branch:
description: Target branch for the pull request
required: false
default: dev
runs:
using: composite
steps:
- name: Install jq if missing
shell: bash
run: |
set -euo pipefail
command -v jq >/dev/null 2>&1 || sudo apt-get install -y --no-install-recommends jq
- name: Configure git
shell: bash
env:
FORGEJO_TOKEN: ${{ inputs.forgejo-token }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
ORIGIN_URL=$(git remote get-url origin)
CLEAN_URL=$(echo "$ORIGIN_URL" | sed 's|https://[^@]*@|https://|')
git remote set-url origin "$(echo "$CLEAN_URL" | sed "s|https://|https://github-actions[bot]:${FORGEJO_TOKEN}@|")"
- name: Check if i18n branch already exists
shell: bash
run: |
set -euo pipefail
if git ls-remote --exit-code --heads origin "refs/heads/chore/i18n" >/dev/null 2>&1; then
echo "Branch chore/i18n already exists. A previous i18n update is still pending."
exit 1
fi
- name: Download translations
shell: bash
env:
I18N_APPLICATION: ${{ inputs.i18n-application }}
LOCALES_FOLDER: ${{ inputs.locales-folder }}
run: |
set -euo pipefail
git checkout -b chore/i18n
echo "Fetching available languages for application: $I18N_APPLICATION"
languages_json=$(curl --fail-with-body -sSL "https://i18n.schmalz.com/api/languages?projectKey=$I18N_APPLICATION")
echo "$languages_json" | jq -r '.[]' | while read -r lang; do
echo "Downloading language: $lang"
curl --fail-with-body -sSL -o "${LOCALES_FOLDER}/${lang}.json" \
"https://i18n.schmalz.com/api/${I18N_APPLICATION}/${lang}.json"
done
- name: Format translations
if: ${{ inputs.format-folder != '' }}
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/pnpm-build@pnpm-build-v1
with:
working-directory: ${{ inputs.format-folder }}
jfrog-token: ${{ inputs.jfrog-token }}
run-scripts: ${{ inputs.format-script }}
check-dedupe: "false"
- name: Commit and push translations
id: commit
shell: bash
env:
LOCALES_FOLDER: ${{ inputs.locales-folder }}
run: |
set -euo pipefail
git add --all "${LOCALES_FOLDER}/"
if git diff-index --cached --quiet HEAD; then
echo "No translation changes detected. Nothing to do."
echo "has_changes=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git commit -m "chore: update translations via i18n"
git push origin chore/i18n
echo "has_changes=true" >> "$GITHUB_OUTPUT"
- name: Create pull request
if: ${{ steps.commit.outputs.has_changes == 'true' }}
shell: bash
env:
TOKEN: ${{ inputs.forgejo-token }}
DESTINATION_BRANCH: ${{ inputs.destination-branch }}
run: |
set -euo pipefail
curl --fail-with-body -s -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/pulls" \
-d "$(jq -n \
--arg title "chore: update translations via i18n" \
--arg head "chore/i18n" \
--arg base "$DESTINATION_BRANCH" \
--arg body "Automated translation update from i18n.schmalz.com" \
'{title: $title, head: $head, base: $base, body: $body, delete_branch_after_merge: true}'
)"

View file

@ -1,40 +0,0 @@
# inject-content
Inject content into a file by appending or overwriting. Useful for writing secrets, configuration snippets, or any multi-line content into an existing file at runtime.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `content` | Yes | | Content to write into the target file |
| `target-file` | Yes | | Path to the target file |
| `mode` | No | `append` | Write mode: `append` adds to the end of the file; `overwrite` replaces its contents |
## Usage
Append secrets to a Java `.properties` file:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/inject-content@inject-content-v1
with:
content: ${{ secrets.APP_PROPERTIES }}
target-file: src/main/resources/application.properties
```
Overwrite a `.env` file:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/inject-content@inject-content-v1
with:
content: ${{ secrets.DOTENV_CONTENT }}
target-file: .env
mode: overwrite
```
## Notes
- Content is passed via an environment variable to avoid shell injection vulnerabilities.
- In `append` mode a blank line is inserted between existing content and the new content. If the target file is empty or does not exist, no leading blank line is added.
- In `overwrite` mode the file is replaced entirely; if the file does not exist it is created.
- Both modes ensure the written content ends with a trailing newline.
- Parent directories of `target-file` are created automatically if they do not exist.

View file

@ -1,67 +0,0 @@
name: inject-content
description: Inject content into a file by appending or overwriting
inputs:
content:
description: Content to write into the target file
required: true
target-file:
description: Path to the target file
required: true
mode:
description: "Write mode: 'append' adds to the end of the file; 'overwrite' replaces its contents"
required: false
default: append
outputs:
target-file:
description: Path of the file that was written
value: ${{ steps.inject.outputs.target-file }}
runs:
using: composite
steps:
- name: Validate target-file input
shell: bash
env:
TARGET_FILE: ${{ inputs.target-file }}
run: |
if [[ -z "$TARGET_FILE" ]]; then
echo "::error::target-file must not be empty"
exit 1
fi
- name: Validate mode input
shell: bash
env:
MODE: ${{ inputs.mode }}
run: |
if [[ "$MODE" != "append" && "$MODE" != "overwrite" ]]; then
echo "::error::Invalid mode '$MODE'. Must be 'append' or 'overwrite'."
exit 1
fi
- name: Inject content into file
id: inject
shell: bash
env:
CONTENT: ${{ inputs.content }}
TARGET_FILE: ${{ inputs.target-file }}
MODE: ${{ inputs.mode }}
run: |
set -euo pipefail
mkdir -p "$(dirname "$TARGET_FILE")"
if [[ "$MODE" == "overwrite" ]]; then
printf '%s\n' "$CONTENT" > "$TARGET_FILE"
else
if [[ -s "$TARGET_FILE" ]]; then
printf '\n%s\n' "$CONTENT" >> "$TARGET_FILE"
else
printf '%s\n' "$CONTENT" >> "$TARGET_FILE"
fi
fi
echo "Content injected into $TARGET_FILE (mode: $MODE)"
echo "target-file=$TARGET_FILE" >> "$GITHUB_OUTPUT"

View file

@ -1,58 +0,0 @@
# maven-build
Action for building and validating Maven projects.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `java-version` | No | `25` | Java version to set up for the build |
| `maven-version` | No | `3.9.15` | Maven version to set up for the build |
| `distribution` | No | `temurin` | JDK distribution to use |
| `phase` | No | `verify` | Build phase to execute: `verify` runs code-quality checks; `deploy` builds and pushes a Docker image |
| `verify-goals` | No | `spotless:check checkstyle:check test` | Space-separated Maven goals to run during the verify phase |
| `maven-profile` | No | `test` | Maven profile to activate during deploy |
| `service-dir` | No | `.` | Working directory for the Maven build |
| `maven-settings` | **Yes** | — | Secret containing the `settings.xml` content used for repository authentication |
| `extra-args` | No | `""` | Additional Maven arguments appended to the build command |
## Outputs
| Output | Description |
|--------|-------------|
| `image-tag` | The Docker image tag used during the deploy phase |
## Usage
### Verify (code quality + tests)
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/maven-build@maven-build-v1
with:
maven-settings: ${{ secrets.MAVEN_SETTINGS }}
```
### Deploy (build and push Docker image)
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/maven-build@maven-build-v1
with:
phase: deploy
maven-profile: prod
maven-settings: ${{ secrets.MAVEN_SETTINGS }}
```
### Multi-module project
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/maven-build@maven-build-v1
with:
service-dir: my-service
maven-settings: ${{ secrets.MAVEN_SETTINGS }}
```
## Notes
- The `maven-settings` input is written to a temporary file (`/tmp/maven-settings.xml`) and removed after the build, even on failure.
- During the `deploy` phase, the image tag is generated as `<FORGEJO_SHA>-<unix-timestamp>` and exposed via the `image-tag` output.
- Third-party actions used internally are pinned to exact commit SHAs to prevent supply chain attacks.

View file

@ -1,125 +0,0 @@
name: maven-build
description: Action for building and validating Maven projects
inputs:
java-version:
required: false
default: '25'
description: 'Java version to set up for the build'
maven-version:
required: false
default: '3.9.15'
description: 'Maven version to set up for the build'
distribution:
required: false
default: 'temurin'
description: 'JDK distribution to use'
phase:
required: false
default: 'verify'
description: 'Build phase to execute: "verify" runs code-quality checks; "deploy" builds and pushes a Docker image'
verify-goals:
required: false
default: 'spotless:check checkstyle:check test'
description: 'Space-separated Maven goals to run during the verify phase'
maven-profile:
required: false
default: 'test'
description: 'Maven profile to activate during deploy'
service-dir:
required: false
default: '.'
description: 'Working directory for the Maven build'
maven-settings:
required: true
description: 'Secret containing the settings.xml content used for repository authentication'
extra-args:
required: false
default: ''
description: 'Additional Maven arguments appended to the build command'
outputs:
image-tag:
description: 'The Docker image tag used during the deploy phase'
value: ${{ steps.deploy.outputs.image-tag }}
runs:
using: composite
steps:
- name: Validate phase
shell: bash
env:
BUILD_PHASE: ${{ inputs.phase }}
run: |
case "$BUILD_PHASE" in
verify|deploy) ;;
*) echo "Invalid phase '$BUILD_PHASE'. Must be 'verify' or 'deploy'." && exit 1 ;;
esac
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# actions/setup-java v4.8.0 — https://github.com/actions/setup-java/tree/v4.8.0
- name: Setup Java
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9
with:
java-version: ${{ inputs.java-version }}
distribution: ${{ inputs.distribution }}
- name: Setup Maven
shell: bash
env:
MAVEN_VERSION: ${{ inputs.maven-version }}
run: |
curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" \
| tar -xzf - -C /opt
echo "/opt/apache-maven-${MAVEN_VERSION}/bin" >> "$GITHUB_PATH"
echo "Maven ${MAVEN_VERSION} installed successfully"
- name: Cache Maven local repository
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ inputs.java-version }}-${{ hashFiles(format('{0}/**/pom.xml', inputs.service-dir)) }}
restore-keys: ${{ runner.os }}-maven-${{ inputs.java-version }}-
- name: Write Maven settings
shell: bash
env:
MAVEN_SETTINGS: ${{ inputs.maven-settings }}
run: printf '%s\n' "$MAVEN_SETTINGS" > /tmp/maven-settings.xml
- name: Verify
if: ${{ inputs.phase == 'verify' }}
shell: bash
working-directory: ${{ inputs.service-dir }}
env:
VERIFY_GOALS: ${{ inputs.verify-goals }}
EXTRA_ARGS: ${{ inputs.extra-args }}
MAVEN_PROFILE: ${{ inputs.maven-profile }}
run: |
mvn --batch-mode $VERIFY_GOALS \
-s /tmp/maven-settings.xml \
-P "$MAVEN_PROFILE" \
$EXTRA_ARGS
- name: Deploy
id: deploy
if: ${{ inputs.phase == 'deploy' }}
shell: bash
working-directory: ${{ inputs.service-dir }}
env:
MAVEN_PROFILE: ${{ inputs.maven-profile }}
EXTRA_ARGS: ${{ inputs.extra-args }}
run: |
IMAGE_TAG="${FORGEJO_SHA}-$(date +%s)"
mvn --batch-mode clean package jib:build \
-DsendCredentialsOverHttp=true \
"-Djib.to.tags=$IMAGE_TAG" \
-P "$MAVEN_PROFILE" \
-s /tmp/maven-settings.xml \
$EXTRA_ARGS
echo "image-tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
- name: Remove Maven settings
if: always()
shell: bash
run: rm -f /tmp/maven-settings.xml

View file

@ -1,39 +0,0 @@
# playwright-merge
Download all Playwright blob reports from shard jobs, merge them into a single HTML + JUnit report, and optionally upload the HTML report to S3.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `working-directory` | No | `.` | Directory containing `package.json` and `playwright.config.ts` |
| `node-version` | No | `24` | Node.js version |
| `pnpm-version` | No | `10.33` | pnpm version |
| `jfrog-token` | No | `""` | JFrog npm auth token |
| `role-arn` | Yes | — | IAM role ARN for AWS authentication |
| `aws-access-key-id` | Yes | — | AWS access key ID |
| `aws-secret-access-key` | Yes | — | AWS secret access key |
| `project-name` | Yes | — | Project name used as the folder in the report bucket (e.g. `hs-pricelist`) |
| `publish-test-reports` | No | `true` | Whether to upload the report to S3 and print the URL at `https://test-reports.schmalz.com` |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/playwright-merge@playwright-merge-v1
with:
working-directory: frontend
node-version: 22
jfrog-token: ${{ secrets.JFROG_TOKEN }}
role-arn: arn:aws:iam::953815822701:role/deployment-role
aws-access-key-id: ${{ secrets.DB_STAGE_DEPLOYMENT_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.DB_STAGE_DEPLOYMENT_SECRET_ACCESS_KEY }}
project-name: hs-pricelist
```
## Notes
- Intended as a follow-up job after all `playwright-run` shard jobs complete.
- Downloads shard artifacts into `all-blob-reports/` and merges them with `playwright merge-reports`.
- Generates HTML report in `playwright-report/` and JUnit XML in `test-results/junit.xml`.
- Uploads report files to `s3://com.schmalz.db.stage.test-reports/reports/<project-name>/<timestamp>/` when `publish-test-reports` is `true`.
- Prints the final report URL on `https://test-reports.schmalz.com`.

View file

@ -1,104 +0,0 @@
name: Playwright Merge
description: >
Download all blob reports from shard jobs, merge them into a single HTML + JUnit
report, upload the HTML report to S3, and upload the JUnit report as an artifact.
Call this in a follow-up job after all playwright-run shard jobs complete.
inputs:
working-directory:
description: Directory containing package.json and playwright.config.ts
required: false
default: "."
node-version:
description: Node.js version
required: false
default: "24"
pnpm-version:
description: pnpm version
required: false
default: "10.33"
jfrog-token:
description: JFrog npm auth token
required: false
default: ""
role-arn:
description: IAM role ARN for AWS authentication
required: true
aws-access-key-id:
description: AWS access key ID
required: true
aws-secret-access-key:
description: AWS secret access key
required: true
project-name:
description: Project name used as the folder in the report bucket (e.g. hs-pricelist)
required: true
publish-test-reports:
description: Whether to upload the report to S3 and print the URL at https://test-reports.schmalz.com. Set to false to skip upload entirely.
required: false
default: "true"
runs:
using: composite
steps:
- name: Install AWS CLI
shell: bash
run: |
if ! command -v aws &>/dev/null; then
curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip
unzip -q /tmp/awscliv2.zip -d /tmp
sudo /tmp/aws/install
rm -rf /tmp/awscliv2.zip /tmp/aws
fi
- name: Configure AWS
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aws-configure@aws-configure-v1
with:
role-arn: ${{ inputs.role-arn }}
aws-access-key-id: ${{ inputs.aws-access-key-id }}
aws-secret-access-key: ${{ inputs.aws-secret-access-key }}
- name: Install dependencies
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/pnpm-build@pnpm-build-v1
with:
working-directory: ${{ inputs.working-directory }}
node-version: ${{ inputs.node-version }}
pnpm-version: ${{ inputs.pnpm-version }}
jfrog-token: ${{ inputs.jfrog-token }}
run-scripts: ""
frozen-lockfile: "true"
check-dedupe: "false"
- name: Download blob reports
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/download-artifact@download-artifact-v1
with:
path: ${{ inputs.working-directory }}/all-blob-reports/
merge-multiple: "true"
- name: Merge blob reports
shell: bash
env:
CI: "true"
WORKING_DIR: ${{ inputs.working-directory }}
PLAYWRIGHT_JUNIT_OUTPUT_NAME: test-results/junit.xml
run: |
cd "${WORKING_DIR}"
pnpm exec playwright merge-reports \
--reporter=html,junit \
all-blob-reports/
- name: Upload report to S3
if: ${{ inputs.publish-test-reports == 'true' }}
shell: bash
env:
WORKING_DIR: ${{ inputs.working-directory }}
PROJECT_NAME: ${{ inputs.project-name }}
run: |
TIMESTAMP=$(date +%s)
S3_BUCKET=com.schmalz.db.stage.test-reports
S3_PATH="s3://${S3_BUCKET}/reports/${PROJECT_NAME}/${TIMESTAMP}"
aws s3 sync "${WORKING_DIR}/playwright-report/" "${S3_PATH}/"
if [ -f "${WORKING_DIR}/test-results/junit.xml" ]; then
aws s3 cp "${WORKING_DIR}/test-results/junit.xml" "${S3_PATH}/junit.xml"
fi
echo "Report URL: https://test-reports.schmalz.com/reports/${PROJECT_NAME}/${TIMESTAMP}/index.html"

View file

@ -1,33 +0,0 @@
# playwright-run
Run Playwright E2E tests for one shard and upload the blob report as an artifact.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `working-directory` | No | `.` | Directory containing `package.json` and `playwright.config.ts` |
| `node-version` | No | `24` | Node.js version |
| `pnpm-version` | No | `10.33` | pnpm version |
| `jfrog-token` | No | `""` | JFrog npm auth token |
| `shard-index` | No | `1` | Current shard index (1-based). Set to `1` when not sharding. |
| `shard-total` | No | `1` | Total number of shards. Set to `1` to disable sharding. |
| `artifact-retention-days` | No | `3` | Number of days to retain the blob report artifact |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/playwright-run@playwright-run-v1
with:
working-directory: frontend
node-version: 22
jfrog-token: ${{ secrets.JFROG_TOKEN }}
shard-index: ${{ matrix.shard-index }}
shard-total: 5
```
## Notes
- Intended for matrix shard jobs.
- Uploads one artifact per shard named `blob-report-<shard-index>`.
- Use `playwright-merge` in a follow-up job to merge shard reports.

View file

@ -1,87 +0,0 @@
name: Playwright Run
description: >
Run Playwright E2E tests for one shard and upload the blob report as an artifact.
Call this from a matrix job. Use the playwright-merge action in a follow-up job
to produce the final HTML + JUnit report.
inputs:
working-directory:
description: Directory containing package.json and playwright.config.ts
required: false
default: "."
node-version:
description: Node.js version
required: false
default: "24"
pnpm-version:
description: pnpm version
required: false
default: "10.33"
jfrog-token:
description: JFrog npm auth token
required: false
default: ""
shard-index:
description: Current shard index (1-based). Set to 1 when not sharding.
required: false
default: "1"
shard-total:
description: Total number of shards. Set to 1 to disable sharding.
required: false
default: "1"
artifact-retention-days:
description: Number of days to retain the blob report artifact
required: false
default: "3"
runs:
using: composite
steps:
- name: Install dependencies
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/pnpm-build@pnpm-build-v1
with:
working-directory: ${{ inputs.working-directory }}
node-version: ${{ inputs.node-version }}
pnpm-version: ${{ inputs.pnpm-version }}
jfrog-token: ${{ inputs.jfrog-token }}
run-scripts: ""
frozen-lockfile: "true"
check-dedupe: "false"
- name: Cache Playwright browsers
id: playwright-cache
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: ${{ runner.os }}-playwright-
- name: Install Playwright browsers
if: ${{ steps.playwright-cache.outputs.cache-hit != 'true' }}
shell: bash
env:
WORKING_DIR: ${{ inputs.working-directory }}
run: pnpm --prefix="${WORKING_DIR}" exec playwright install --with-deps
- name: Run Playwright tests
shell: bash
env:
CI: "true"
WORKING_DIR: ${{ inputs.working-directory }}
SHARD_INDEX: ${{ inputs.shard-index }}
SHARD_TOTAL: ${{ inputs.shard-total }}
run: |
SHARD_ARG=""
if [ "${SHARD_TOTAL}" != "1" ]; then
SHARD_ARG="--shard=${SHARD_INDEX}/${SHARD_TOTAL}"
fi
pnpm --prefix="${WORKING_DIR}" exec playwright test ${SHARD_ARG} --reporter=blob,dot
- name: Upload blob report
if: ${{ !cancelled() }}
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/upload-artifact@upload-artifact-v1
with:
name: blob-report-${{ inputs.shard-index }}
path: ${{ inputs.working-directory }}/blob-report/
retention-days: ${{ inputs.artifact-retention-days }}
if-no-files-found: ignore

View file

@ -19,10 +19,6 @@ inputs:
description: JFrog npm auth token description: JFrog npm auth token
required: false required: false
default: "" default: ""
nexus-token:
description: Nexus npm auth token
required: false
default: ""
run-scripts: run-scripts:
description: Comma-separated list of pnpm run scripts description: Comma-separated list of pnpm run scripts
required: false required: false
@ -40,54 +36,25 @@ runs:
using: composite using: composite
steps: steps:
# Pinned to commit SHA instead of a tag to prevent supply chain attacks. # Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# actions/setup-node v4.4.0 — https://code.forgejo.org/actions/setup-node/commits/tag/v4.4.0 # actions/setup-node v6.4.0 — https://code.forgejo.org/actions/setup-node/commits/tag/v6.4.0
- name: Setup Node - name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
with: with:
node-version: ${{ inputs.node-version }} node-version: ${{ inputs.node-version }}
# Pinned to commit SHA instead of a tag to prevent supply chain attacks. # Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# pnpm/action-setup v4.3.0 — https://code.forgejo.org/pnpm/action-setup/commits/tag/v4.3.0 # pnpm/action-setup v6.0.3 — https://code.forgejo.org/pnpm/action-setup/commits/tag/v6.0.3
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e
env:
# Override any registry configured in .npmrc (e.g. JFrog or Nexus).
# pnpm/action-setup bootstraps itself via npm before pnpm is available,
# so it must reach the public npm registry. Auth for private registries
# is configured in a later step, after pnpm is installed.
NPM_CONFIG_REGISTRY: https://registry.npmjs.org
with: with:
version: ${{ inputs.pnpm-version }} version: ${{ inputs.pnpm-version }}
- name: Get pnpm store directory - name: Configure pnpm registry authentication
id: pnpm-store
shell: bash
run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
- name: Cache pnpm store
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: ${{ runner.os }}-pnpm-${{ inputs.pnpm-version }}-${{ hashFiles(format('{0}/pnpm-lock.yaml', inputs.working-directory)) }}
restore-keys: ${{ runner.os }}-pnpm-${{ inputs.pnpm-version }}-
- name: Configure JFrog registry authentication
if: ${{ inputs.jfrog-token != '' }} if: ${{ inputs.jfrog-token != '' }}
shell: bash shell: bash
env: env:
JFROG_TOKEN: ${{ inputs.jfrog-token }} JFROG_TOKEN: ${{ inputs.jfrog-token }}
run: | run: pnpm set //schmalz.jfrog.io/artifactory/api/npm/default-npm/:_authToken "$JFROG_TOKEN"
pnpm set registry https://schmalz.jfrog.io/artifactory/api/npm/default-npm/
pnpm set //schmalz.jfrog.io/artifactory/api/npm/default-npm/:_authToken "$JFROG_TOKEN"
- name: Configure Nexus registry authentication
if: ${{ inputs.nexus-token != '' }}
shell: bash
env:
NEXUS_TOKEN: ${{ inputs.nexus-token }}
run: |
pnpm set registry https://nexus.schmalzgroup.com/repository/npm-all/
pnpm set //nexus.schmalzgroup.com/repository/npm-all/:_authToken "$NEXUS_TOKEN"
- name: Build - name: Build
shell: bash shell: bash

View file

@ -1,28 +0,0 @@
# publish-npm-package
Publish a PNPM package to JFrog Artifactory.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `working-directory` | No | `.` | Directory containing `package.json` |
| `node-version` | No | `24` | Node.js version |
| `pnpm-version` | No | `10.33` | pnpm version |
| `jfrog-token` | Yes | — | JFrog npm auth token |
| `registry-url` | No | `https://schmalz.jfrog.io/artifactory/api/npm/default-npm/` | npm registry URL |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-npm-package@publish-npm-package-v1
with:
working-directory: .
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- Publishes with `pnpm publish`.
- Configures the registry auth token from `registry-url` and `jfrog-token`.
- Third-party actions used internally are pinned to exact commit SHAs to prevent supply chain attacks.

View file

@ -1,64 +0,0 @@
name: publish-npm-package
description: Publish a PNPM package to JFrog Artifactory.
inputs:
working-directory:
description: Directory containing package.json
required: false
default: "."
node-version:
description: Node.js version
required: false
default: "24"
pnpm-version:
description: pnpm version
required: false
default: "10.33"
jfrog-token:
description: JFrog npm auth token
required: true
registry-url:
description: npm registry URL
required: false
default: "https://schmalz.jfrog.io/artifactory/api/npm/default-npm/"
runs:
using: composite
steps:
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# actions/setup-node v4.4.0 — https://code.forgejo.org/actions/setup-node/commits/tag/v4.4.0
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: ${{ inputs.node-version }}
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# pnpm/action-setup v4.3.0 — https://code.forgejo.org/pnpm/action-setup/commits/tag/v4.3.0
- name: Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1
env:
# pnpm/action-setup bootstraps itself via npm before pnpm is available,
# so it must reach the public npm registry.
NPM_CONFIG_REGISTRY: https://registry.npmjs.org
with:
version: ${{ inputs.pnpm-version }}
- name: Configure JFrog registry authentication
shell: bash
env:
JFROG_TOKEN: ${{ inputs.jfrog-token }}
REGISTRY_URL: ${{ inputs.registry-url }}
run: |
set -euo pipefail
pnpm set registry "${REGISTRY_URL}"
AUTHORITY="${REGISTRY_URL#https://}"
AUTHORITY="${AUTHORITY#http://}"
AUTHORITY="${AUTHORITY%/}"
pnpm set "//${AUTHORITY}/:_authToken" "${JFROG_TOKEN}"
- name: Publish
shell: bash
working-directory: ${{ inputs.working-directory }}
run: pnpm publish

View file

@ -1,29 +0,0 @@
# publish-rust-crate
Publish a Rust crate to JFrog Artifactory.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `working-directory` | No | `.` | Directory containing `Cargo.toml` |
| `rust-version` | No | `1.95.0` | Rust toolchain version |
| `jfrog-token` | Yes | — | JFrog token for the Artifactory Cargo registry |
| `registry-name` | No | `artifactory` | Cargo registry name |
| `registry-index` | No | `sparse+https://schmalz.jfrog.io/artifactory/api/cargo/schmalz-cargo-local/index/` | Cargo registry index URL |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-rust-crate@publish-rust-crate-v1
with:
working-directory: .
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- Configures Cargo registry settings in `${CARGO_HOME}/config.toml` and `${CARGO_HOME}/credentials.toml`.
- Falls back to `$HOME/.cargo` when `CARGO_HOME` is not set.
- Publishes with `cargo publish --registry <registry-name>`.
- Third-party actions used internally are pinned to exact commit SHAs to prevent supply chain attacks.

View file

@ -1,64 +0,0 @@
name: publish-rust-crate
description: Publish a Rust crate to JFrog Artifactory.
inputs:
working-directory:
description: Directory containing Cargo.toml
required: false
default: "."
rust-version:
description: Rust toolchain version
required: false
default: "1.95.0"
jfrog-token:
description: JFrog token for the Artifactory Cargo registry
required: true
registry-name:
description: Cargo registry name
required: false
default: artifactory
registry-index:
description: Cargo registry index URL
required: false
default: "sparse+https://schmalz.jfrog.io/artifactory/api/cargo/schmalz-cargo-local/index/"
runs:
using: composite
steps:
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# dtolnay/rust-toolchain v1 (2026-03-27) — https://github.com/dtolnay/rust-toolchain/commit/3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
with:
toolchain: ${{ inputs.rust-version }}
- name: Configure Cargo registry (JFrog Artifactory)
shell: bash
env:
JFROG_TOKEN: ${{ inputs.jfrog-token }}
REGISTRY_NAME: ${{ inputs.registry-name }}
REGISTRY_INDEX: ${{ inputs.registry-index }}
run: |
set -euo pipefail
CARGO_HOME_DIR="${CARGO_HOME:-$HOME/.cargo}"
mkdir -p "${CARGO_HOME_DIR}"
cat >> "${CARGO_HOME_DIR}/config.toml" <<EOF
[registries.${REGISTRY_NAME}]
index = "${REGISTRY_INDEX}"
[registry]
global-credential-providers = ["cargo:token"]
EOF
cat >> "${CARGO_HOME_DIR}/credentials.toml" <<EOF
[registries.${REGISTRY_NAME}]
token = "Bearer ${JFROG_TOKEN}"
EOF
- name: Publish
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
REGISTRY_NAME: ${{ inputs.registry-name }}
run: cargo publish --registry "${REGISTRY_NAME}"

View file

@ -9,14 +9,10 @@ Syncs frontend assets to S3 and invalidates a CloudFront distribution. Optionall
| `dist_dir` | No | `frontend/dist` | Path to the frontend dist directory | | `dist_dir` | No | `frontend/dist` | Path to the frontend dist directory |
| `s3_bucket_name` | Yes | — | Name of the S3 bucket to sync assets to | | `s3_bucket_name` | Yes | — | Name of the S3 bucket to sync assets to |
| `cloudfront_distribution_ids` | No | `''` | Space-separated list of CloudFront distribution IDs to invalidate | | `cloudfront_distribution_ids` | No | `''` | Space-separated list of CloudFront distribution IDs to invalidate |
| `versioning` | No | `false` | When `true`, enables versioned builds. All assets get `Cache-Control: public, max-age=31536000, immutable`. Old versions older than 7 days are deleted, keeping at least the 2 newest | | `versioned_static_prefix` | No | `''` | S3 prefix under which versioned builds are stored (e.g. `_static`). When set, old versions older than 7 days are deleted, keeping at least the 2 newest |
| `versioning_prefix` | No | `''` | S3 prefix under which versioned builds are stored (e.g. `_static` → `_static/1234567890/`). Requires `versioning: true` |
| `cache_rules` | No | `[]` | JSON array of per-file cache overrides. Each matched file is uploaded individually with the given headers and excluded from the bulk sync. When `versioning` is `true` and `cache_rules` is empty and `index.html` exists, defaults to short-caching `index.html` |
## Usage ## Usage
Plain sync:
```yaml ```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-static-contents@publish-static-contents-v1 - uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-static-contents@publish-static-contents-v1
with: with:
@ -29,41 +25,15 @@ With versioned static assets:
```yaml ```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-static-contents@publish-static-contents-v1 - uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-static-contents@publish-static-contents-v1
with: with:
dist_dir: frontend/dist
s3_bucket_name: my-bucket s3_bucket_name: my-bucket
cloudfront_distribution_ids: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} cloudfront_distribution_ids: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}
versioning: true versioned_static_prefix: _static
```
With versioned static assets under a prefix:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-static-contents@publish-static-contents-v1
with:
s3_bucket_name: my-bucket
cloudfront_distribution_ids: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}
versioning: true
versioning_prefix: _static
```
With custom per-file cache rules (e.g. for a PWA):
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-static-contents@publish-static-contents-v1
with:
s3_bucket_name: my-bucket
cloudfront_distribution_ids: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}
versioning: true
cache_rules: |
[
{"file": "index.html", "cache_control": "no-cache, no-store, must-revalidate", "content_type": "text/html"},
{"file": "sw.js", "cache_control": "no-cache, no-store, must-revalidate", "content_type": "application/javascript"},
{"file": "manifest.webmanifest", "cache_control": "no-cache, max-age=300", "content_type": "application/manifest+json"}
]
``` ```
## Notes ## Notes
- When `versioning` is `true` and `cache_rules` is empty, `index.html` is automatically short-cached (`no-cache, max-age=300`) if it exists in the dist directory. - When `versioned_static_prefix` is set, all assets are synced with `Cache-Control: public, max-age=31536000, immutable`.
- `cache_rules` works independently of `versioning` and can be used for plain syncs too. - `index.html` is always synced separately with `Cache-Control: no-cache, max-age=300` so browsers pick up new deployments quickly.
- Old versioned builds are pruned: any version folder older than 7 days is deleted, with at least the 2 newest versions always retained. - Old versioned builds are pruned: any version folder older than 7 days is deleted, with at least the 2 newest versions always retained.
- CloudFront invalidation is skipped when `cloudfront_distribution_ids` is empty. - CloudFront invalidation is skipped when `cloudfront_distribution_ids` is empty.

View file

@ -13,110 +13,49 @@ inputs:
description: Space-separated list of CloudFront distribution IDs to invalidate description: Space-separated list of CloudFront distribution IDs to invalidate
required: false required: false
default: '' default: ''
versioning: versioned_static_prefix:
description: > description: S3 prefix under which versioned builds are stored (e.g. _static). When set, old versions older than 7 days are deleted, keeping at least the 2 newest.
When set to true, enables versioned builds: assets are synced with
"public, max-age=31536000, immutable" and old versions older than 7 days
are deleted, keeping at least the 2 newest.
required: false
default: 'false'
versioning_prefix:
description: >
S3 prefix under which versioned builds are stored
(e.g. "_static" → _static/1234567890/). When omitted, versions are
stored at the bucket root. Requires versioning to be true.
required: false required: false
default: '' default: ''
cache_rules:
description: >
JSON array of per-file cache overrides. Each matched file is uploaded
individually with the given headers and excluded from the bulk sync.
content_type is optional. Applied independently of versioning.
When versioning is enabled and cache_rules is empty, defaults to
short-caching index.html — the standard SPA behaviour.
Example:
cache_rules: |
[
{"file": "index.html", "cache_control": "no-cache, no-store, must-revalidate", "content_type": "text/html"},
{"file": "sw.js", "cache_control": "no-cache, no-store, must-revalidate", "content_type": "application/javascript"},
{"file": "manifest.webmanifest", "cache_control": "no-cache, max-age=300", "content_type": "application/manifest+json"}
]
required: false
default: '[]'
runs: runs:
using: composite using: composite
steps: steps:
- name: Install AWS CLI
shell: bash
run: |
if ! command -v aws &> /dev/null; then
curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip
unzip -q /tmp/awscliv2.zip -d /tmp
sudo /tmp/aws/install
rm -rf /tmp/awscliv2.zip /tmp/aws
fi
- name: Publish frontend assets - name: Publish frontend assets
shell: bash shell: bash
env: env:
INPUT_DIST_DIR: ${{ inputs.dist_dir }} INPUT_DIST_DIR: ${{ inputs.dist_dir }}
INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }} INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }}
INPUT_VERSIONING: ${{ inputs.versioning }} INPUT_VERSIONED_STATIC_PREFIX: ${{ inputs.versioned_static_prefix }}
INPUT_VERSIONING_PREFIX: ${{ inputs.versioning_prefix }}
INPUT_CACHE_RULES: ${{ inputs.cache_rules }}
run: | run: |
# When versioning is active and no cache_rules provided, short-cache index.html by default CACHE_CONTROL_ARG=""
EFFECTIVE_CACHE_RULES="${INPUT_CACHE_RULES}" if [[ -n "${INPUT_VERSIONED_STATIC_PREFIX}" ]]; then
if [[ "${INPUT_VERSIONING}" == "true" && "${EFFECTIVE_CACHE_RULES}" == "[]" && -f "${INPUT_DIST_DIR}/index.html" ]]; then CACHE_CONTROL_ARG="--cache-control 'public, max-age=31536000, immutable'"
EFFECTIVE_CACHE_RULES='[{"file": "index.html", "cache_control": "no-cache, max-age=300", "content_type": "text/html"}]'
fi fi
EXCLUDE_INDEX_ARG=""
# Upload each file from cache_rules with its own headers, if [[ -n "${INPUT_VERSIONED_STATIC_PREFIX}" && -f "${INPUT_DIST_DIR}/index.html" ]]; then
# and collect --exclude args so the bulk sync skips them EXCLUDE_INDEX_ARG="--exclude index.html"
EXCLUDE_ARGS=()
if [[ "${EFFECTIVE_CACHE_RULES}" != "[]" ]]; then
while IFS= read -r rule; do
file=$(echo "$rule" | jq -r '.file')
cache_control=$(echo "$rule" | jq -r '.cache_control')
content_type=$(echo "$rule" | jq -r '.content_type // empty')
EXCLUDE_ARGS+=("--exclude" "$file")
if [[ -f "${INPUT_DIST_DIR}/${file}" ]]; then
CT_ARG=()
if [[ -n "$content_type" ]]; then
CT_ARG=("--content-type" "$content_type")
fi
aws s3 cp "${INPUT_DIST_DIR}/${file}" "s3://${INPUT_S3_BUCKET_NAME}/${file}" \
--cache-control "$cache_control" \
--metadata-directive REPLACE \
"${CT_ARG[@]}"
fi
done < <(echo "$EFFECTIVE_CACHE_RULES" | jq -c '.[]')
fi fi
aws s3 sync "${INPUT_DIST_DIR}" "s3://${INPUT_S3_BUCKET_NAME}" $CACHE_CONTROL_ARG $EXCLUDE_INDEX_ARG
# Bulk sync remaining files; versioned builds get immutable cache headers - name: Publish index.html without immutable cache
CACHE_CONTROL_ARG=() if: ${{ inputs.versioned_static_prefix != '' }}
if [[ "${INPUT_VERSIONING}" == "true" ]]; then shell: bash
CACHE_CONTROL_ARG=("--cache-control" "public, max-age=31536000, immutable") run: |
if [[ -f "${INPUT_DIST_DIR}/index.html" ]]; then
aws s3 cp "${INPUT_DIST_DIR}/index.html" "s3://${INPUT_S3_BUCKET_NAME}/index.html" \
--cache-control "no-cache, max-age=300" \
--metadata-directive REPLACE
fi fi
aws s3 sync "${INPUT_DIST_DIR}" "s3://${INPUT_S3_BUCKET_NAME}" "${CACHE_CONTROL_ARG[@]}" "${EXCLUDE_ARGS[@]}"
- name: Clean up old versioned static builds - name: Clean up old versioned static builds
if: ${{ inputs.versioning == 'true' }} if: ${{ inputs.versioned_static_prefix != '' }}
shell: bash shell: bash
env: env:
INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }} INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }}
INPUT_VERSIONING_PREFIX: ${{ inputs.versioning_prefix }} INPUT_VERSIONED_STATIC_PREFIX: ${{ inputs.versioned_static_prefix }}
run: | run: |
S3_PATH="s3://$INPUT_S3_BUCKET_NAME" aws s3 ls "s3://$INPUT_S3_BUCKET_NAME/$INPUT_VERSIONED_STATIC_PREFIX/" \
if [[ -n "${INPUT_VERSIONING_PREFIX}" ]]; then
S3_PATH="s3://$INPUT_S3_BUCKET_NAME/$INPUT_VERSIONING_PREFIX"
fi
aws s3 ls "${S3_PATH}/" \
| grep -oP '(?<=PRE )[0-9]+' \ | grep -oP '(?<=PRE )[0-9]+' \
| sort --stable --reverse \ | sort --stable --reverse \
| tail -n +3 \ | tail -n +3 \
@ -126,7 +65,7 @@ runs:
# delete if older than 7 days # delete if older than 7 days
if [ $diff -gt 604800000 ]; then if [ $diff -gt 604800000 ]; then
echo "Deleting $version" echo "Deleting $version"
aws s3 rm --recursive "${S3_PATH}/$version" aws s3 rm --recursive "s3://$INPUT_S3_BUCKET_NAME/$INPUT_VERSIONED_STATIC_PREFIX/$version"
fi fi
done done

View file

@ -1,42 +0,0 @@
# rust-build
Set up Rust toolchain, configure Cargo registry, cache dependencies, run optional checks, and build via the project's `build.sh` script.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `working-directory` | No | `.` | Directory containing `Cargo.toml` and `build.sh` |
| `rust-version` | No | `1.95.0` | Rust toolchain version |
| `cross-target` | No | `x86_64-unknown-linux-musl` | Cross-compilation target triple |
| `build-mode` | No | `release` | Build mode — `release` or `debug` |
| `run-checks` | No | `""` | Comma-separated checks to run before building — `fmt`, `clippy`, `test` |
| `jfrog-token` | No | `""` | JFrog token for the Artifactory Cargo registry |
## Usage
### PR check (checks + debug build)
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/rust-build@rust-build-v1
with:
working-directory: backend-rs
build-mode: debug
run-checks: fmt,clippy,test
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
### Release build
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/rust-build@rust-build-v1
with:
working-directory: backend-rs
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- Requires a `build.sh` in `working-directory` that accepts `--target <triple>` and optionally `--release`. The script is responsible for running `cargo build` and copying binaries to `target/deploy/`.
- Configures the Artifactory Cargo registry only if `jfrog-token` is provided.
- Third-party actions used internally are pinned to exact commit SHAs to prevent supply chain attacks.

View file

@ -1,112 +0,0 @@
name: rust-build
description: >
Set up Rust toolchain, configure Cargo registry, cache dependencies,
run optional checks, and build via the project's build.sh script.
inputs:
working-directory:
description: Directory containing Cargo.toml and build.sh
required: false
default: "."
rust-version:
description: Rust toolchain version (passed to dtolnay/rust-toolchain)
required: false
default: "1.95.0"
cross-target:
description: Cross-compilation target triple
required: false
default: x86_64-unknown-linux-musl
build-mode:
description: Build mode — 'release' or 'debug'
required: false
default: release
run-checks:
description: Comma-separated checks to run before building — 'fmt', 'clippy', 'test'
required: false
default: ""
jfrog-token:
description: JFrog token for the Artifactory Cargo registry
required: false
default: ""
runs:
using: composite
steps:
- name: Install musl tools
shell: bash
run: |
if ! command -v musl-gcc &>/dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq musl-tools
fi
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# dtolnay/rust-toolchain v1 (2026-03-27) — https://github.com/dtolnay/rust-toolchain/commit/3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
- name: Setup Rust toolchain
id: rust-toolchain
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
with:
toolchain: ${{ inputs.rust-version }}
targets: ${{ inputs.cross-target }}
components: rustfmt,clippy
- name: Configure Cargo registry (JFrog Artifactory)
if: ${{ inputs.jfrog-token != '' }}
shell: bash
env:
JFROG_TOKEN: ${{ inputs.jfrog-token }}
run: |
mkdir -p "${CARGO_HOME}"
cat >> "${CARGO_HOME}/config.toml" <<'EOF'
[registries.artifactory]
index = "sparse+https://schmalz.jfrog.io/artifactory/api/cargo/schmalz-cargo-local/index/"
[registry]
global-credential-providers = ["cargo:token"]
EOF
cat >> "${CARGO_HOME}/credentials.toml" <<EOF
[registries.artifactory]
token = "Bearer ${JFROG_TOKEN}"
EOF
- name: Cache cargo registry
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
key: ${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-${{ inputs.cross-target }}-${{ hashFiles(format('{0}/Cargo.lock', inputs.working-directory)) }}
restore-keys: |
${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-${{ inputs.cross-target }}-
${{ runner.os }}-cargo-${{ steps.rust-toolchain.outputs.cachekey }}-
- name: Run checks
if: ${{ inputs.run-checks != '' }}
shell: bash
env:
WORKING_DIR: ${{ inputs.working-directory }}
CROSS_TARGET: ${{ inputs.cross-target }}
RUN_CHECKS: ${{ inputs.run-checks }}
run: |
IFS=',' read -ra CHECKS <<< "${RUN_CHECKS}"
for check in "${CHECKS[@]}"; do
case "${check}" in
fmt) cargo fmt --manifest-path="${WORKING_DIR}/Cargo.toml" --check ;;
clippy) cargo clippy --manifest-path="${WORKING_DIR}/Cargo.toml" --target="${CROSS_TARGET}" -- -D warnings ;;
test) cargo test --manifest-path="${WORKING_DIR}/Cargo.toml" ;;
*) echo "Unknown check: ${check}"; exit 1 ;;
esac
done
- name: Build
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
CROSS_TARGET: ${{ inputs.cross-target }}
BUILD_MODE: ${{ inputs.build-mode }}
run: |
BUILD_ARGS="--target ${CROSS_TARGET}"
if [ "${BUILD_MODE}" = "release" ]; then
BUILD_ARGS="${BUILD_ARGS} --release"
fi
./build.sh ${BUILD_ARGS}

View file

@ -1,37 +0,0 @@
# terraform-apply
Apply Terraform configuration files using the official Terraform CLI.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `terraform-dir` | No | `terraform` | Directory containing `.tf` files |
| `terraform-version` | No | `~1.15` | Terraform version to use |
| `var-file` | No | `""` | Path to `.tfvars` file, relative to `terraform-dir` |
| `workspace` | No | `""` | Terraform workspace to select |
| `jfrog-token` | No | `""` | JFrog Artifactory token for the Terraform provider registry (`TF_TOKEN_schmalz_jfrog_io`) |
## Outputs
Non-sensitive Terraform outputs are automatically exported after apply. They are accessible on the calling step via `steps.<id>.outputs.<terraform-output-name>`. Complex types (lists, maps) are JSON-encoded. Outputs marked as `sensitive = true` in Terraform are excluded.
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/terraform-apply@terraform-apply-v1
id: tf-apply
with:
workspace: stage
var-file: stage.tfvars
jfrog-token: ${{ secrets.JFROG_TOKEN }}
- run: echo ${{ steps.tf-apply.outputs.s3_bucket_name }}
```
## Notes
- Runs `terraform init`, selects the workspace (if provided), and applies with `-auto-approve`.
- Sets `TF_TOKEN_schmalz_jfrog_io` on both `init` and `apply` steps if `jfrog-token` is provided.
- If `var-file` is provided, it is passed as `-var-file` to the apply command.
- Non-sensitive Terraform outputs are written to `$GITHUB_OUTPUT` after apply — no separate `terraform output` step needed. Sensitive outputs are excluded to prevent secret leakage.

View file

@ -1,90 +0,0 @@
name: Terraform Apply
description: >
Init and apply Terraform configuration files using the official Terraform CLI.
inputs:
terraform-dir:
description: Directory containing .tf files
required: false
default: terraform
terraform-version:
description: Terraform version to use
required: false
default: "~1.15"
var-file:
description: Path to .tfvars file, relative to terraform-dir
required: false
default: ""
workspace:
description: Terraform workspace to use
required: false
default: ""
jfrog-token:
description: JFrog Artifactory token used for Terraform provider registry (sets TF_TOKEN_schmalz_jfrog_io)
required: false
default: ""
runs:
using: composite
steps:
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# hashicorp/setup-terraform v4.0.0 — https://github.com/hashicorp/setup-terraform/commits/v4.0.0/
- name: Setup Terraform
uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85
with:
terraform_version: ${{ inputs.terraform-version }}
- name: Set Terraform plugin cache directory
shell: bash
run: |
mkdir -p ~/.terraform.d/plugin-cache
echo "TF_PLUGIN_CACHE_DIR=$HOME/.terraform.d/plugin-cache" >> "$GITHUB_ENV"
- name: Cache Terraform providers
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: ~/.terraform.d/plugin-cache
key: ${{ runner.os }}-terraform-providers-${{ inputs.terraform-version }}-${{ hashFiles(format('{0}/.terraform.lock.hcl', inputs.terraform-dir)) }}
restore-keys: ${{ runner.os }}-terraform-providers-${{ inputs.terraform-version }}-
- name: Terraform Init
shell: bash
env:
TF_TOKEN_schmalz_jfrog_io: ${{ inputs.jfrog-token }}
TF_DIR: ${{ inputs.terraform-dir }}
run: terraform -chdir="$TF_DIR" init -no-color
- name: Terraform Select Workspace
if: ${{ inputs.workspace != '' }}
shell: bash
env:
TF_DIR: ${{ inputs.terraform-dir }}
TF_WORKSPACE_NAME: ${{ inputs.workspace }}
run: |
terraform -chdir="$TF_DIR" workspace select -or-create "$TF_WORKSPACE_NAME"
- name: Terraform Apply
shell: bash
env:
TF_TOKEN_schmalz_jfrog_io: ${{ inputs.jfrog-token }}
TF_DIR: ${{ inputs.terraform-dir }}
VAR_FILE: ${{ inputs.var-file }}
run: |
ARGS="-auto-approve -no-color"
if [ -n "$VAR_FILE" ]; then
ARGS="$ARGS -var-file=$VAR_FILE"
fi
terraform -chdir="$TF_DIR" apply $ARGS
- name: Export Terraform Outputs
shell: bash
env:
TF_DIR: ${{ inputs.terraform-dir }}
run: |
terraform -chdir="$TF_DIR" output -json | jq -r '
to_entries[]
| select(.value.sensitive != true)
| .key as $k
| (.value.value | if type == "string" then . else tojson end) as $v
| "\($k)<<__TF_OUT__\n\($v)\n__TF_OUT__"
' >> "$GITHUB_OUTPUT"

View file

@ -10,7 +10,6 @@ Validate Terraform configuration files using the official Terraform CLI.
| `terraform-version` | No | `~1.15` | Terraform version to use | | `terraform-version` | No | `~1.15` | Terraform version to use |
| `workspace` | No | `""` | Terraform workspace to use | | `workspace` | No | `""` | Terraform workspace to use |
| `jfrog-token` | No | `""` | JFrog Artifactory token for the Terraform provider registry (`TF_TOKEN_schmalz_jfrog_io`) | | `jfrog-token` | No | `""` | JFrog Artifactory token for the Terraform provider registry (`TF_TOKEN_schmalz_jfrog_io`) |
| `mock-files` | No | `""` | Newline-separated list of file paths (relative to repo root) to create as empty files before validation. Useful when Terraform uses `file()` references that do not exist in CI. |
## Usage ## Usage
@ -21,22 +20,8 @@ Validate Terraform configuration files using the official Terraform CLI.
jfrog-token: ${{ secrets.JFROG_TOKEN }} jfrog-token: ${{ secrets.JFROG_TOKEN }}
``` ```
With mock files for `file()` dependencies:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/terraform-validate@terraform-validate-v1
with:
workspace: stage
jfrog-token: ${{ secrets.JFROG_TOKEN }}
mock-files: |
config/app.json
secrets/tls.crt
secrets/tls.key
```
## Notes ## Notes
- Runs `terraform init -backend=false`, `terraform fmt -check -recursive`, and `terraform validate`. - Runs `terraform init -backend=false`, `terraform fmt -check -recursive`, and `terraform validate`.
- Sets `TF_WORKSPACE` during validate if `workspace` is provided. - Sets `TF_WORKSPACE` during validate if `workspace` is provided.
- Sets `TF_TOKEN_schmalz_jfrog_io` on both `init` and `validate` steps if `jfrog-token` is provided. - Sets `TF_TOKEN_schmalz_jfrog_io` on both `init` and `validate` steps if `jfrog-token` is provided.
- When `mock-files` is set, empty files are created at the given paths (including any missing parent directories) before `terraform init` runs. This allows validation of configurations that reference external files via `file()`.

View file

@ -19,13 +19,6 @@ inputs:
description: JFrog Artifactory token used for Terraform provider registry (sets TF_TOKEN_schmalz_jfrog_io) description: JFrog Artifactory token used for Terraform provider registry (sets TF_TOKEN_schmalz_jfrog_io)
required: false required: false
default: "" default: ""
mock-files:
description: |-
Newline-separated list of file paths to create as empty files before validation.
Useful when Terraform configurations reference external files via file() that do not exist in CI.
Paths are relative to the repository root.
required: false
default: ""
runs: runs:
using: composite using: composite
@ -37,31 +30,6 @@ runs:
with: with:
terraform_version: ${{ inputs.terraform-version }} terraform_version: ${{ inputs.terraform-version }}
- name: Set Terraform plugin cache directory
shell: bash
run: |
mkdir -p ~/.terraform.d/plugin-cache
echo "TF_PLUGIN_CACHE_DIR=$HOME/.terraform.d/plugin-cache" >> "$GITHUB_ENV"
- name: Cache Terraform providers
uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/cache@cache-v1
with:
path: ~/.terraform.d/plugin-cache
key: ${{ runner.os }}-terraform-providers-${{ inputs.terraform-version }}-${{ hashFiles(format('{0}/.terraform.lock.hcl', inputs.terraform-dir)) }}
restore-keys: ${{ runner.os }}-terraform-providers-${{ inputs.terraform-version }}-
- name: Create mock files
if: ${{ inputs.mock-files != '' }}
shell: bash
env:
MOCK_FILES: ${{ inputs.mock-files }}
run: |
while IFS= read -r mock_file; do
[ -z "$mock_file" ] && continue
mkdir -p "$(dirname "$mock_file")"
touch "$mock_file"
done <<< "$MOCK_FILES"
- name: Terraform Init - name: Terraform Init
shell: bash shell: bash
env: env:

View file

@ -1,37 +0,0 @@
# upload-artifact
Upload files as a Forgejo Actions artifact. Thin wrapper around `forgejo/upload-artifact` pinned to a specific commit SHA to prevent supply chain attacks.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `name` | Yes | — | Artifact name |
| `path` | Yes | — | File or directory path to upload |
| `retention-days` | No | `30` | Number of days to retain the artifact |
| `if-no-files-found` | No | `warn` | Behaviour when no files are found — `warn`, `error`, or `ignore` |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/upload-artifact@upload-artifact-v1
with:
name: my-artifact
path: dist/
```
Upload and ignore if no files exist:
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/upload-artifact@upload-artifact-v1
with:
name: blob-report-${{ matrix.shard-index }}
path: frontend/blob-report/
retention-days: 3
if-no-files-found: ignore
```
## Notes
- Wraps `forgejo/upload-artifact` v4 (node20), compatible with Ubuntu 22 runners.
- The underlying action is pinned to a commit SHA rather than a mutable tag to prevent supply chain attacks.

View file

@ -1,33 +0,0 @@
name: Schmalz Upload Artifact
description: >
Upload files as a Forgejo Actions artifact.
Thin wrapper around forgejo/upload-artifact, pinned to a specific SHA.
inputs:
name:
description: Artifact name
required: true
path:
description: File or directory path to upload
required: true
retention-days:
description: Number of days to retain the artifact
required: false
default: "30"
if-no-files-found:
description: Behaviour when no files are found — 'warn', 'error', or 'ignore'
required: false
default: warn
runs:
using: composite
steps:
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
# forgejo/upload-artifact v4 — https://code.forgejo.org/forgejo/upload-artifact/commits/tag/v4
- name: Upload artifact
uses: https://code.forgejo.org/forgejo/upload-artifact@16871d9e8cfcf27ff31822cac382bbb5450f1e1e
with:
name: ${{ inputs.name }}
path: ${{ inputs.path }}
retention-days: ${{ inputs.retention-days }}
if-no-files-found: ${{ inputs.if-no-files-found }}

View file

@ -1,25 +0,0 @@
# vacuum-lint
Action for validating and linting OpenAPI specifications using [Vacuum](https://github.com/daveshanley/vacuum).
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `spec-dir` | No | `spec` | Directory containing the OpenAPI spec |
| `spec-filename` | No | `openapi.json` | Filename of the OpenAPI spec |
| `rules-filename` | No | `vacuum.rules.yaml` | Filename of the lint rules config file |
| `ignore-filename` | No | `vacuum.ignore.yaml` | Filename of the lint ignore file |
| `min-score` | No | `70` | Minimum linting score for the check to pass |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/vacuum-lint@vacuum-lint-v1
```
## Notes
- If `rules-filename` is found inside `spec-dir`, it is passed to Vacuum via `-r` to apply custom rulesets; otherwise Vacuum uses its default rules.
- If `ignore-filename` is found inside `spec-dir`, it is passed to Vacuum via `--ignore-file` to suppress known violations.
- The action fails when the computed linting score falls below `min-score`.

View file

@ -1,65 +0,0 @@
name: Vacuum Lint
description: >
Validate and lint OpenAPI specifications using Vacuum.
inputs:
spec-dir:
description: Directory containing OpenAPI spec
required: false
default: "spec"
spec-filename:
description: Filename of the OpenAPI spec
required: false
default: "openapi.json"
rules-filename:
description: Filename of the lint rules config file
required: false
default: "vacuum.rules.yaml"
ignore-filename:
description: Filename of the lint ignore file
required: false
default: "vacuum.ignore.yaml"
min-score:
description: Minimum linting score for the check to pass
required: false
default: "70"
runs:
using: composite
steps:
# Pinned to commit SHA instead of a tag to prevent supply chain attacks.
- name: Install Vacuum
shell: bash
run: curl -fsSL https://raw.githubusercontent.com/daveshanley/vacuum/8222bba0c8b21a3a94faf472e06c4db06f06c6ce/bin/install.sh | sudo sh > /dev/null 2>&1
- name: Lint Spec
shell: bash
env:
SPEC_DIR: ${{ inputs.spec-dir }}
SPEC_FILE: ${{ inputs.spec-filename }}
RULES_FILE: ${{ inputs.rules-filename }}
IGNORE_FILE: ${{ inputs.ignore-filename }}
MIN_SCORE: ${{ inputs.min-score }}
run: |
echo "Linting: [$SPEC_DIR/$SPEC_FILE]"
# base command
CMD="vacuum lint $SPEC_DIR/$SPEC_FILE -x --min-score $MIN_SCORE"
# check for rules file
if [ -f "$SPEC_DIR/$RULES_FILE" ]; then
CMD="$CMD -r $SPEC_DIR/$RULES_FILE"
echo " - using ruleset [$SPEC_DIR/$RULES_FILE]"
fi
# check for ignore file
if [ -f "$SPEC_DIR/$IGNORE_FILE" ]; then
CMD="$CMD --ignore-file $SPEC_DIR/$IGNORE_FILE"
echo " - using ignore file [$SPEC_DIR/$IGNORE_FILE]"
fi
# execute command
$CMD
echo "Linted: [$SPEC_DIR/$SPEC_FILE]"
echo ""