From 6e276d4e674922b701efe9b44c1c00e50a6ba825 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Thu, 30 Apr 2026 14:40:56 +0200 Subject: [PATCH 01/46] feat: add Aikido security workflows for full scan and PR check --- .forgejo/workflows/full-scan-aikido.yml | 18 ++++++++++++++++++ .forgejo/workflows/pr-check-aikido.yml | 23 +++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 .forgejo/workflows/full-scan-aikido.yml create mode 100644 .forgejo/workflows/pr-check-aikido.yml diff --git a/.forgejo/workflows/full-scan-aikido.yml b/.forgejo/workflows/full-scan-aikido.yml new file mode 100644 index 0000000..3019df3 --- /dev/null +++ b/.forgejo/workflows/full-scan-aikido.yml @@ -0,0 +1,18 @@ +name: Aikido Security Full Scan + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + aikido-full-scan: + name: Aikido Security Full Scan + runs-on: stackit-ubuntu-22 + steps: + - name: Checkout repository + uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/checkout@checkout-v1 + + - name: Run Aikido full scan + uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aikido-full-scan@aikido-full-scan-v1 + with: + apikey: ${{ secrets.AIKIDO_CLIENT_API_KEY }} \ No newline at end of file diff --git a/.forgejo/workflows/pr-check-aikido.yml b/.forgejo/workflows/pr-check-aikido.yml new file mode 100644 index 0000000..e75423e --- /dev/null +++ b/.forgejo/workflows/pr-check-aikido.yml @@ -0,0 +1,23 @@ +name: Aikido Security PR Check + +on: + pull_request: + branches: + - '*' + +concurrency: + group: ${{ forgejo.workflow }}-${{ forgejo.ref }} + cancel-in-progress: true + +jobs: + aikido-security: + name: Aikido Security Scan + runs-on: stackit-ubuntu-22 + steps: + - name: Checkout repository + uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/checkout@checkout-v1 + + - name: Security scan + uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aikido-pr-scan@aikido-pr-scan-v1 + with: + apikey: ${{ secrets.AIKIDO_CLIENT_API_KEY }} \ No newline at end of file From b009c23bb77b7c4e7407687b6a5624b04c615844 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 4 May 2026 06:59:47 +0000 Subject: [PATCH 02/46] fix: move aikido internal actions into the shared actions to make it work --- aikido-full-scan/action.yml | 2 +- .../docker}/Dockerfile | 0 .../docker}/action.yml | 0 .../docker}/entrypoint.sh | 0 aikido-pr-scan/action.yml | 2 +- .../docker}/Dockerfile | 0 .../docker}/action.yml | 0 .../docker}/entrypoint.sh | 0 8 files changed, 2 insertions(+), 2 deletions(-) rename {.forgejo/actions/internal-aikido-full-scan => aikido-full-scan/docker}/Dockerfile (100%) rename {.forgejo/actions/internal-aikido-full-scan => aikido-full-scan/docker}/action.yml (100%) rename {.forgejo/actions/internal-aikido-full-scan => aikido-full-scan/docker}/entrypoint.sh (100%) rename {.forgejo/actions/internal-aikido-pr-scan => aikido-pr-scan/docker}/Dockerfile (100%) rename {.forgejo/actions/internal-aikido-pr-scan => aikido-pr-scan/docker}/action.yml (100%) rename {.forgejo/actions/internal-aikido-pr-scan => aikido-pr-scan/docker}/entrypoint.sh (100%) diff --git a/aikido-full-scan/action.yml b/aikido-full-scan/action.yml index fb54ef9..608f7b0 100644 --- a/aikido-full-scan/action.yml +++ b/aikido-full-scan/action.yml @@ -12,7 +12,7 @@ inputs: runs: using: composite steps: - - uses: ./.forgejo/actions/internal-aikido-full-scan + - uses: ./aikido-full-scan/docker with: apikey: ${{ inputs.apikey }} organization: ${{ forgejo.repository_owner }} diff --git a/.forgejo/actions/internal-aikido-full-scan/Dockerfile b/aikido-full-scan/docker/Dockerfile similarity index 100% rename from .forgejo/actions/internal-aikido-full-scan/Dockerfile rename to aikido-full-scan/docker/Dockerfile diff --git a/.forgejo/actions/internal-aikido-full-scan/action.yml b/aikido-full-scan/docker/action.yml similarity index 100% rename from .forgejo/actions/internal-aikido-full-scan/action.yml rename to aikido-full-scan/docker/action.yml diff --git a/.forgejo/actions/internal-aikido-full-scan/entrypoint.sh b/aikido-full-scan/docker/entrypoint.sh similarity index 100% rename from .forgejo/actions/internal-aikido-full-scan/entrypoint.sh rename to aikido-full-scan/docker/entrypoint.sh diff --git a/aikido-pr-scan/action.yml b/aikido-pr-scan/action.yml index 714cd79..52c3094 100644 --- a/aikido-pr-scan/action.yml +++ b/aikido-pr-scan/action.yml @@ -16,7 +16,7 @@ inputs: runs: using: composite steps: - - uses: ./.forgejo/actions/internal-aikido-pr-scan + - uses: ./aikido-pr-scan/docker with: apikey: ${{ inputs.apikey }} organization: ${{ forgejo.repository_owner }} diff --git a/.forgejo/actions/internal-aikido-pr-scan/Dockerfile b/aikido-pr-scan/docker/Dockerfile similarity index 100% rename from .forgejo/actions/internal-aikido-pr-scan/Dockerfile rename to aikido-pr-scan/docker/Dockerfile diff --git a/.forgejo/actions/internal-aikido-pr-scan/action.yml b/aikido-pr-scan/docker/action.yml similarity index 100% rename from .forgejo/actions/internal-aikido-pr-scan/action.yml rename to aikido-pr-scan/docker/action.yml diff --git a/.forgejo/actions/internal-aikido-pr-scan/entrypoint.sh b/aikido-pr-scan/docker/entrypoint.sh similarity index 100% rename from .forgejo/actions/internal-aikido-pr-scan/entrypoint.sh rename to aikido-pr-scan/docker/entrypoint.sh From 2a5f1e2fe77d8f0707df3b4487ee939b094909eb Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 4 May 2026 07:08:44 +0000 Subject: [PATCH 03/46] feat: add Aikido security workflows for full scan and PR check --- .forgejo/workflows/full-scan-aikido.yml | 18 ++++++++++++++++++ .forgejo/workflows/pr-check-aikido.yml | 23 +++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 .forgejo/workflows/full-scan-aikido.yml create mode 100644 .forgejo/workflows/pr-check-aikido.yml diff --git a/.forgejo/workflows/full-scan-aikido.yml b/.forgejo/workflows/full-scan-aikido.yml new file mode 100644 index 0000000..193323a --- /dev/null +++ b/.forgejo/workflows/full-scan-aikido.yml @@ -0,0 +1,18 @@ +name: Aikido Security Full Scan + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + aikido-full-scan: + name: Aikido Security Full Scan + runs-on: stackit-ubuntu-22 + steps: + - name: Checkout repository + uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/checkout@checkout-v1 + + - name: Run Aikido full scan + uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aikido-full-scan@aikido-full-scan-v1 + with: + apikey: ${{ secrets.AIKIDO_CLIENT_API_KEY }} diff --git a/.forgejo/workflows/pr-check-aikido.yml b/.forgejo/workflows/pr-check-aikido.yml new file mode 100644 index 0000000..8845713 --- /dev/null +++ b/.forgejo/workflows/pr-check-aikido.yml @@ -0,0 +1,23 @@ +name: Aikido Security PR Check + +on: + pull_request: + branches: + - '*' + +concurrency: + group: ${{ forgejo.workflow }}-${{ forgejo.ref }} + cancel-in-progress: true + +jobs: + aikido-security: + name: Aikido Security Scan + runs-on: stackit-ubuntu-22 + steps: + - name: Checkout repository + uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/checkout@checkout-v1 + + - name: Security scan + uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/aikido-pr-scan@aikido-pr-scan-v1 + with: + apikey: ${{ secrets.AIKIDO_CLIENT_API_KEY }} From 5f542e7a12c87739e5860b711cb322accec37cdf Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 4 May 2026 07:13:53 +0000 Subject: [PATCH 04/46] chore: remove unused extensions --- .devcontainer/devcontainer.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 50ad4c6..fdde937 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,9 +14,7 @@ "customizations": { "vscode": { "extensions": [ - "amazonwebservices.aws-toolkit-vscode", - "biomejs.biome", - "likec4.likec4-vscode" + "amazonwebservices.aws-toolkit-vscode" ] } } From f3878d443b9f82a9314c638a58e845f0a5d792e2 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 4 May 2026 07:19:43 +0000 Subject: [PATCH 05/46] feat: add terraform-apply action Co-authored-by: Copilot --- README.md | 1 + terraform-apply/README.md | 29 +++++++++++++++++ terraform-apply/action.yml | 64 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 terraform-apply/README.md create mode 100644 terraform-apply/action.yml diff --git a/README.md b/README.md index c0964d4..72161b1 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Shared actions for Forgejo CI/CD pipelines. | [checkout](checkout) | Action for checking out a repository | | [pnpm-build](pnpm-build) | Action for building and validating with PNPM | | [publish-static-contents](publish-static-contents) | Syncs frontend assets to S3 and invalidates a CloudFront distribution | +| [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 | diff --git a/terraform-apply/README.md b/terraform-apply/README.md new file mode 100644 index 0000000..2cb60c6 --- /dev/null +++ b/terraform-apply/README.md @@ -0,0 +1,29 @@ +# 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`) | + +## Usage + +```yaml +- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/terraform-apply@terraform-apply-v1 + with: + workspace: stage + var-file: stage.tfvars + jfrog-token: ${{ secrets.JFROG_TOKEN }} +``` + +## 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. \ No newline at end of file diff --git a/terraform-apply/action.yml b/terraform-apply/action.yml new file mode 100644 index 0000000..23899ac --- /dev/null +++ b/terraform-apply/action.yml @@ -0,0 +1,64 @@ +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: 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 "$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 \ No newline at end of file From 7a45b38c62f3864c13d8d834b7ecacb8f8f8d228 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 4 May 2026 08:04:13 +0000 Subject: [PATCH 06/46] fix: update checkout action to use shared-actions version --- .forgejo/workflows/validate-shared-actions.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/validate-shared-actions.yml b/.forgejo/workflows/validate-shared-actions.yml index 9c08e89..b2ef0ec 100644 --- a/.forgejo/workflows/validate-shared-actions.yml +++ b/.forgejo/workflows/validate-shared-actions.yml @@ -12,7 +12,8 @@ jobs: runs-on: stackit-ubuntu-22 steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/checkout@checkout-v1 + - name: Validate shared action metadata uses: docker://data.forgejo.org/forgejo/runner:12 with: From 14bf6ad0dab33cb2ea25581b9ac60a74b1970a41 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 4 May 2026 08:06:16 +0000 Subject: [PATCH 07/46] feat: add tag-release workflow for manual major release tagging --- .forgejo/workflows/tag-release.yml | 75 ++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .forgejo/workflows/tag-release.yml diff --git a/.forgejo/workflows/tag-release.yml b/.forgejo/workflows/tag-release.yml new file mode 100644 index 0000000..e311a51 --- /dev/null +++ b/.forgejo/workflows/tag-release.yml @@ -0,0 +1,75 @@ +name: tag-release + +# Manually create or move a major release tag for a shared action. +# Tag format: -v (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 + - checkout + - pnpm-build + - publish-static-contents + - terraform-apply + - terraform-validate + 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" From 7bdca13059ae16dea4da6cfd929948e1e20009df Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 4 May 2026 09:04:49 +0000 Subject: [PATCH 08/46] feat: add aws-access-key-id and aws-secret-access-key inputs to aws-configure action Co-authored-by: Copilot --- aws-configure/README.md | 2 ++ aws-configure/action.yml | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/aws-configure/README.md b/aws-configure/README.md index 7654185..9127afb 100644 --- a/aws-configure/README.md +++ b/aws-configure/README.md @@ -8,6 +8,8 @@ Authenticate with AWS via OIDC and export credentials to the environment. |-------|----------|---------|-------------| | `role-arn` | Yes | | Full IAM role ARN | | `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 diff --git a/aws-configure/action.yml b/aws-configure/action.yml index cc3e978..7b63f98 100644 --- a/aws-configure/action.yml +++ b/aws-configure/action.yml @@ -9,6 +9,12 @@ inputs: description: AWS region required: false 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: using: composite @@ -20,3 +26,5 @@ runs: with: role-to-assume: ${{ inputs.role-arn }} aws-region: ${{ inputs.region }} + aws-access-key-id: ${{ inputs.aws-access-key-id }} + aws-secret-access-key: ${{ inputs.aws-secret-access-key }} From 3eaba16b6798fcb7f597aaa329a348d9c08a945b Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 4 May 2026 12:42:11 +0000 Subject: [PATCH 09/46] XXX --- aikido-pr-scan/action.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/aikido-pr-scan/action.yml b/aikido-pr-scan/action.yml index 52c3094..ec83c6b 100644 --- a/aikido-pr-scan/action.yml +++ b/aikido-pr-scan/action.yml @@ -16,7 +16,14 @@ inputs: runs: using: composite steps: - - uses: ./aikido-pr-scan/docker + - name: Debug action path + shell: bash + run: | + echo "PWD: $PWD" + echo "GITHUB_ACTION_PATH: $GITHUB_ACTION_PATH" + find "$GITHUB_ACTION_PATH" -maxdepth 3 + + - uses: ./docker with: apikey: ${{ inputs.apikey }} organization: ${{ forgejo.repository_owner }} From a9ea87cafe72f2c0cc5bb4e565d9e33d4cf20396 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 4 May 2026 12:56:45 +0000 Subject: [PATCH 10/46] fix: reference internal actions as full qualified shared actions --- aikido-full-scan/action.yml | 2 +- aikido-pr-scan/action.yml | 9 +-------- .../docker => internal-aikido-full-scan}/Dockerfile | 0 .../docker => internal-aikido-full-scan}/action.yml | 0 .../docker => internal-aikido-full-scan}/entrypoint.sh | 0 .../docker => internal-aikido-pr-scan}/Dockerfile | 0 .../docker => internal-aikido-pr-scan}/action.yml | 0 .../docker => internal-aikido-pr-scan}/entrypoint.sh | 0 8 files changed, 2 insertions(+), 9 deletions(-) rename {aikido-full-scan/docker => internal-aikido-full-scan}/Dockerfile (100%) rename {aikido-full-scan/docker => internal-aikido-full-scan}/action.yml (100%) rename {aikido-full-scan/docker => internal-aikido-full-scan}/entrypoint.sh (100%) rename {aikido-pr-scan/docker => internal-aikido-pr-scan}/Dockerfile (100%) rename {aikido-pr-scan/docker => internal-aikido-pr-scan}/action.yml (100%) rename {aikido-pr-scan/docker => internal-aikido-pr-scan}/entrypoint.sh (100%) diff --git a/aikido-full-scan/action.yml b/aikido-full-scan/action.yml index 608f7b0..8ce9334 100644 --- a/aikido-full-scan/action.yml +++ b/aikido-full-scan/action.yml @@ -12,7 +12,7 @@ inputs: runs: using: composite steps: - - uses: ./aikido-full-scan/docker + - uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/internal-aikido-full-scan@internal-aikido-full-scan-v1 with: apikey: ${{ inputs.apikey }} organization: ${{ forgejo.repository_owner }} diff --git a/aikido-pr-scan/action.yml b/aikido-pr-scan/action.yml index ec83c6b..d58b19d 100644 --- a/aikido-pr-scan/action.yml +++ b/aikido-pr-scan/action.yml @@ -16,14 +16,7 @@ inputs: runs: using: composite steps: - - name: Debug action path - shell: bash - run: | - echo "PWD: $PWD" - echo "GITHUB_ACTION_PATH: $GITHUB_ACTION_PATH" - find "$GITHUB_ACTION_PATH" -maxdepth 3 - - - uses: ./docker + - uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/internal-aikido-pr-scan@internal-aikido-pr-scan-v1 with: apikey: ${{ inputs.apikey }} organization: ${{ forgejo.repository_owner }} diff --git a/aikido-full-scan/docker/Dockerfile b/internal-aikido-full-scan/Dockerfile similarity index 100% rename from aikido-full-scan/docker/Dockerfile rename to internal-aikido-full-scan/Dockerfile diff --git a/aikido-full-scan/docker/action.yml b/internal-aikido-full-scan/action.yml similarity index 100% rename from aikido-full-scan/docker/action.yml rename to internal-aikido-full-scan/action.yml diff --git a/aikido-full-scan/docker/entrypoint.sh b/internal-aikido-full-scan/entrypoint.sh similarity index 100% rename from aikido-full-scan/docker/entrypoint.sh rename to internal-aikido-full-scan/entrypoint.sh diff --git a/aikido-pr-scan/docker/Dockerfile b/internal-aikido-pr-scan/Dockerfile similarity index 100% rename from aikido-pr-scan/docker/Dockerfile rename to internal-aikido-pr-scan/Dockerfile diff --git a/aikido-pr-scan/docker/action.yml b/internal-aikido-pr-scan/action.yml similarity index 100% rename from aikido-pr-scan/docker/action.yml rename to internal-aikido-pr-scan/action.yml diff --git a/aikido-pr-scan/docker/entrypoint.sh b/internal-aikido-pr-scan/entrypoint.sh similarity index 100% rename from aikido-pr-scan/docker/entrypoint.sh rename to internal-aikido-pr-scan/entrypoint.sh From f6a9668f6e5e65175ba3eae7fc362a78da45bcdd Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 4 May 2026 13:34:39 +0000 Subject: [PATCH 11/46] fix: downgrade checkout ...to avoid usage of node24 - not supported from stackit right now --- checkout/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/checkout/action.yml b/checkout/action.yml index 4292e84..2a4919c 100644 --- a/checkout/action.yml +++ b/checkout/action.yml @@ -33,9 +33,9 @@ runs: using: composite steps: # Pinned to commit SHA instead of a tag to prevent supply chain attacks. - # actions/checkout v6.0.2 — https://code.forgejo.org/actions/checkout/commits/tag/v6.0.2 + # actions/checkout v5.0.1 — https://code.forgejo.org/actions/checkout/commits/tag/v5.0.1 - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd with: ref: ${{ inputs.ref }} repository: ${{ inputs.repository }} From 3097bd89e2fca81085086fa8063e1dc4bc9553c4 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 4 May 2026 14:27:04 +0000 Subject: [PATCH 12/46] fix: downgrade checkout ...to avoid usage of node24 - not supported from stackit right now --- checkout/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/checkout/action.yml b/checkout/action.yml index 2a4919c..8a27f89 100644 --- a/checkout/action.yml +++ b/checkout/action.yml @@ -33,9 +33,9 @@ runs: using: composite steps: # Pinned to commit SHA instead of a tag to prevent supply chain attacks. - # actions/checkout v5.0.1 — https://code.forgejo.org/actions/checkout/commits/tag/v5.0.1 + # actions/checkout v4.3.1 — https://code.forgejo.org/actions/checkout/commits/tag/v4.3.1 - name: Checkout repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 with: ref: ${{ inputs.ref }} repository: ${{ inputs.repository }} From 1e68ce68c7128688649a65a43260aa20bba16310 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Tue, 5 May 2026 06:04:14 +0000 Subject: [PATCH 13/46] feat: add cache action Co-authored-by: Copilot --- .forgejo/workflows/tag-release.yml | 1 + README.md | 1 + cache/README.md | 51 ++++++++++++++++++++++++++++ cache/action.yml | 54 ++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 cache/README.md create mode 100644 cache/action.yml diff --git a/.forgejo/workflows/tag-release.yml b/.forgejo/workflows/tag-release.yml index e311a51..b1c3e06 100644 --- a/.forgejo/workflows/tag-release.yml +++ b/.forgejo/workflows/tag-release.yml @@ -16,6 +16,7 @@ on: - aikido-full-scan - aikido-pr-scan - aws-configure + - cache - checkout - pnpm-build - publish-static-contents diff --git a/README.md b/README.md index 72161b1..d1a1bf6 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Shared actions for Forgejo CI/CD pipelines. | [aikido-full-scan](aikido-full-scan) | Aikido full scan | | [aikido-pr-scan](aikido-pr-scan) | Aikido PR scan | | [aws-configure](aws-configure) | Authenticate with AWS via OIDC | +| [cache](cache) | Cache files between workflow runs | | [checkout](checkout) | Action for checking out a repository | | [pnpm-build](pnpm-build) | Action for building and validating with PNPM | | [publish-static-contents](publish-static-contents) | Syncs frontend assets to S3 and invalidates a CloudFront distribution | diff --git a/cache/README.md b/cache/README.md new file mode 100644 index 0000000..8e5512a --- /dev/null +++ b/cache/README.md @@ -0,0 +1,51 @@ +# 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). diff --git a/cache/action.yml b/cache/action.yml new file mode 100644 index 0000000..fd408dc --- /dev/null +++ b/cache/action.yml @@ -0,0 +1,54 @@ +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 }} From 9ab5db7b6199b9615e8eeebc22fe98e00dda18f1 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Tue, 5 May 2026 06:11:20 +0000 Subject: [PATCH 14/46] feat: add inject-content action --- .forgejo/workflows/tag-release.yml | 1 + README.md | 1 + inject-content/README.md | 40 ++++++++++++++++++ inject-content/action.yml | 67 ++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+) create mode 100644 inject-content/README.md create mode 100644 inject-content/action.yml diff --git a/.forgejo/workflows/tag-release.yml b/.forgejo/workflows/tag-release.yml index b1c3e06..0c7be9f 100644 --- a/.forgejo/workflows/tag-release.yml +++ b/.forgejo/workflows/tag-release.yml @@ -18,6 +18,7 @@ on: - aws-configure - cache - checkout + - inject-content - pnpm-build - publish-static-contents - terraform-apply diff --git a/README.md b/README.md index d1a1bf6..4c67c09 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Shared actions for Forgejo CI/CD pipelines. | [aws-configure](aws-configure) | Authenticate with AWS via OIDC | | [cache](cache) | Cache files between workflow runs | | [checkout](checkout) | Action for checking out a repository | +| [inject-content](inject-content) | Inject content into a file by appending or overwriting | | [pnpm-build](pnpm-build) | Action for building and validating with PNPM | | [publish-static-contents](publish-static-contents) | Syncs frontend assets to S3 and invalidates a CloudFront distribution | | [terraform-apply](terraform-apply) | Apply Terraform configuration files using the official Terraform CLI | diff --git a/inject-content/README.md b/inject-content/README.md new file mode 100644 index 0000000..b44d324 --- /dev/null +++ b/inject-content/README.md @@ -0,0 +1,40 @@ +# 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. diff --git a/inject-content/action.yml b/inject-content/action.yml new file mode 100644 index 0000000..6ab2ade --- /dev/null +++ b/inject-content/action.yml @@ -0,0 +1,67 @@ +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" From a0e6adf3db2c3bac6068f4ecd451fac79e738be7 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Tue, 5 May 2026 09:22:49 +0000 Subject: [PATCH 15/46] feat: add output export for Terraform apply action --- terraform-apply/README.md | 10 +++++++++- terraform-apply/action.yml | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/terraform-apply/README.md b/terraform-apply/README.md index 2cb60c6..5df0ae8 100644 --- a/terraform-apply/README.md +++ b/terraform-apply/README.md @@ -12,18 +12,26 @@ Apply Terraform configuration files using the official Terraform CLI. | `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..outputs.`. 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. \ No newline at end of file +- 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. \ No newline at end of file diff --git a/terraform-apply/action.yml b/terraform-apply/action.yml index 23899ac..b0b5e7d 100644 --- a/terraform-apply/action.yml +++ b/terraform-apply/action.yml @@ -61,4 +61,17 @@ runs: if [ -n "$VAR_FILE" ]; then ARGS="$ARGS -var-file=$VAR_FILE" fi - terraform -chdir="$TF_DIR" apply $ARGS \ No newline at end of file + 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" \ No newline at end of file From 0aa9f4274dba9d5d2c6280ec1403ab3a44fd3bc1 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Tue, 5 May 2026 12:47:00 +0000 Subject: [PATCH 16/46] feat: add helm-deploy action --- .forgejo/workflows/tag-release.yml | 1 + README.md | 1 + helm-deploy/README.md | 34 +++++++++++ helm-deploy/action.yml | 93 ++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 helm-deploy/README.md create mode 100644 helm-deploy/action.yml diff --git a/.forgejo/workflows/tag-release.yml b/.forgejo/workflows/tag-release.yml index 0c7be9f..9724a16 100644 --- a/.forgejo/workflows/tag-release.yml +++ b/.forgejo/workflows/tag-release.yml @@ -18,6 +18,7 @@ on: - aws-configure - cache - checkout + - helm-deploy - inject-content - pnpm-build - publish-static-contents diff --git a/README.md b/README.md index 4c67c09..2fe1278 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Shared actions for Forgejo CI/CD pipelines. | [aws-configure](aws-configure) | Authenticate with AWS via OIDC | | [cache](cache) | Cache files between workflow runs | | [checkout](checkout) | Action for checking out a repository | +| [helm-deploy](helm-deploy) | Deploy a service to Kubernetes via Helm over SSH | | [inject-content](inject-content) | Inject content into a file by appending or overwriting | | [pnpm-build](pnpm-build) | Action for building and validating with PNPM | | [publish-static-contents](publish-static-contents) | Syncs frontend assets to S3 and invalidates a CloudFront distribution | diff --git a/helm-deploy/README.md b/helm-deploy/README.md new file mode 100644 index 0000000..75c4cfa --- /dev/null +++ b/helm-deploy/README.md @@ -0,0 +1,34 @@ +# helm-deploy + +Deploy a service to Kubernetes via Helm over SSH. + +## Inputs + +| Input | Required | Default | Description | +|-------|----------|---------|-------------| +| `service-name` | Yes | — | Helm release name | +| `helm-host` | Yes | — | SSH target host (e.g. `dsp1-stage.schmalzgroup.net`) | +| `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. diff --git a/helm-deploy/action.yml b/helm-deploy/action.yml new file mode 100644 index 0000000..be3cfc5 --- /dev/null +++ b/helm-deploy/action.yml @@ -0,0 +1,93 @@ +name: helm-deploy +description: Deploy a service to Kubernetes via Helm over SSH + +inputs: + service-name: + description: Helm release name + required: true + helm-host: + description: SSH target (e.g., dsp1-stage.schmalzgroup.net) + 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: Copy overrides file + shell: bash + env: + HELM_HOST: ${{ inputs.helm-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: ${{ inputs.helm-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" From 4030dee789de91befaf6545cb7e205e3e539ec72 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Tue, 5 May 2026 13:06:35 +0000 Subject: [PATCH 17/46] feat: add caching for pnpm store and Terraform providers --- pnpm-build/action.yml | 12 ++++++++++++ terraform-apply/action.yml | 13 +++++++++++++ terraform-validate/action.yml | 13 +++++++++++++ 3 files changed, 38 insertions(+) diff --git a/pnpm-build/action.yml b/pnpm-build/action.yml index 30ff3f2..c0c02f1 100644 --- a/pnpm-build/action.yml +++ b/pnpm-build/action.yml @@ -49,6 +49,18 @@ runs: with: version: ${{ inputs.pnpm-version }} + - name: Get pnpm store directory + 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 pnpm registry authentication if: ${{ inputs.jfrog-token != '' }} shell: bash diff --git a/terraform-apply/action.yml b/terraform-apply/action.yml index 23899ac..fe50795 100644 --- a/terraform-apply/action.yml +++ b/terraform-apply/action.yml @@ -34,6 +34,19 @@ runs: 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: diff --git a/terraform-validate/action.yml b/terraform-validate/action.yml index 56cff6f..9ca015a 100644 --- a/terraform-validate/action.yml +++ b/terraform-validate/action.yml @@ -30,6 +30,19 @@ runs: 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: From a8be9bf8dcc386f280388c643b1d7c37240016eb Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Tue, 5 May 2026 12:42:00 +0000 Subject: [PATCH 18/46] feat: add maven-build action --- .forgejo/workflows/tag-release.yml | 1 + README.md | 1 + maven-build/README.md | 58 ++++++++++++++ maven-build/action.yml | 123 +++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 maven-build/README.md create mode 100644 maven-build/action.yml diff --git a/.forgejo/workflows/tag-release.yml b/.forgejo/workflows/tag-release.yml index 0c7be9f..2209052 100644 --- a/.forgejo/workflows/tag-release.yml +++ b/.forgejo/workflows/tag-release.yml @@ -19,6 +19,7 @@ on: - cache - checkout - inject-content + - maven-build - pnpm-build - publish-static-contents - terraform-apply diff --git a/README.md b/README.md index 4c67c09..1955a88 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Shared actions for Forgejo CI/CD pipelines. | [cache](cache) | Cache files between workflow runs | | [checkout](checkout) | Action for checking out a repository | | [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 | | [publish-static-contents](publish-static-contents) | Syncs frontend assets to S3 and invalidates a CloudFront distribution | | [terraform-apply](terraform-apply) | Apply Terraform configuration files using the official Terraform CLI | diff --git a/maven-build/README.md b/maven-build/README.md new file mode 100644 index 0000000..8e243d5 --- /dev/null +++ b/maven-build/README.md @@ -0,0 +1,58 @@ +# 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 `-` and exposed via the `image-tag` output. +- Third-party actions used internally are pinned to exact commit SHAs to prevent supply chain attacks. diff --git a/maven-build/action.yml b/maven-build/action.yml new file mode 100644 index 0000000..961bc28 --- /dev/null +++ b/maven-build/action.yml @@ -0,0 +1,123 @@ +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 }} + run: | + mvn --batch-mode $VERIFY_GOALS \ + -s /tmp/maven-settings.xml \ + $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 From 45794cdf82f8a73cc049db7d247ea5426e291a67 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Tue, 5 May 2026 13:08:43 +0000 Subject: [PATCH 19/46] feat: enable manual triggering for Aikido Security Full Scan workflow --- .forgejo/workflows/full-scan-aikido.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.forgejo/workflows/full-scan-aikido.yml b/.forgejo/workflows/full-scan-aikido.yml index 193323a..494ad60 100644 --- a/.forgejo/workflows/full-scan-aikido.yml +++ b/.forgejo/workflows/full-scan-aikido.yml @@ -3,6 +3,7 @@ name: Aikido Security Full Scan on: schedule: - cron: '0 0 * * *' + workflow_dispatch: jobs: aikido-full-scan: From 4af880a1f093428daa9fd1c9099022cccd76b409 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Wed, 6 May 2026 14:03:11 +0000 Subject: [PATCH 20/46] fix: use actions with node20 support node24 is not available in latest stackit runner containers Co-authored-by: Copilot --- pnpm-build/action.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-build/action.yml b/pnpm-build/action.yml index c0c02f1..e126461 100644 --- a/pnpm-build/action.yml +++ b/pnpm-build/action.yml @@ -36,16 +36,16 @@ runs: using: composite steps: # Pinned to commit SHA instead of a tag to prevent supply chain attacks. - # actions/setup-node v6.4.0 — https://code.forgejo.org/actions/setup-node/commits/tag/v6.4.0 + # 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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + 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 v6.0.3 — https://code.forgejo.org/pnpm/action-setup/commits/tag/v6.0.3 + # 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@903f9c1a6ebcba6cf41d87230be49611ac97822e + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 with: version: ${{ inputs.pnpm-version }} From c87077f8b0f7715734f8049f5e272b86454a9790 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Wed, 6 May 2026 14:14:07 +0000 Subject: [PATCH 21/46] feat: add support for nexus npm registry Co-authored-by: Copilot --- pnpm-build/action.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/pnpm-build/action.yml b/pnpm-build/action.yml index e126461..4d3397e 100644 --- a/pnpm-build/action.yml +++ b/pnpm-build/action.yml @@ -19,6 +19,10 @@ inputs: description: JFrog npm auth token required: false default: "" + nexus-token: + description: Nexus npm auth token + required: false + default: "" run-scripts: description: Comma-separated list of pnpm run scripts required: false @@ -61,12 +65,23 @@ runs: 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 pnpm registry authentication + - name: Configure JFrog registry authentication if: ${{ inputs.jfrog-token != '' }} shell: bash env: JFROG_TOKEN: ${{ inputs.jfrog-token }} - run: pnpm set //schmalz.jfrog.io/artifactory/api/npm/default-npm/:_authToken "$JFROG_TOKEN" + run: | + 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 shell: bash From d89b7842e21a2870953b99b28028ad788c862bd5 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 11 May 2026 13:52:20 +0000 Subject: [PATCH 22/46] fix: install AWS CLI if not present before publishing assets Co-authored-by: Copilot --- publish-static-contents/action.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/publish-static-contents/action.yml b/publish-static-contents/action.yml index f1ed672..6d7341d 100644 --- a/publish-static-contents/action.yml +++ b/publish-static-contents/action.yml @@ -21,6 +21,16 @@ inputs: 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: Publish frontend assets shell: bash env: From 4d2a9815d72445dfa6158f2923275593599cda80 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Tue, 19 May 2026 15:07:29 +0000 Subject: [PATCH 23/46] fix: update versioning parameters in README and action.yml for clarity Co-authored-by: Copilot --- publish-static-contents/README.md | 21 ++++++++++++++----- publish-static-contents/action.yml | 33 +++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/publish-static-contents/README.md b/publish-static-contents/README.md index 9ebc930..5d5c6b4 100644 --- a/publish-static-contents/README.md +++ b/publish-static-contents/README.md @@ -9,7 +9,8 @@ Syncs frontend assets to S3 and invalidates a CloudFront distribution. Optionall | `dist_dir` | No | `frontend/dist` | Path to the frontend dist directory | | `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 | -| `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` | No | `false` | When `true`, enables versioned builds. 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/`). When omitted, versions are stored at the bucket root (e.g. `1234567890/`) | ## Usage @@ -20,20 +21,30 @@ Syncs frontend assets to S3 and invalidates a CloudFront distribution. Optionall cloudfront_distribution_ids: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} ``` -With versioned static assets: +With versioned static assets at the bucket root: ```yaml - uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-static-contents@publish-static-contents-v1 with: - dist_dir: frontend/dist s3_bucket_name: my-bucket cloudfront_distribution_ids: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} - versioned_static_prefix: _static + versioning: true +``` + +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 ``` ## Notes -- When `versioned_static_prefix` is set, all assets are synced with `Cache-Control: public, max-age=31536000, immutable`. +- When `versioning` is `true`, all assets are synced with `Cache-Control: public, max-age=31536000, immutable`. - `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. - CloudFront invalidation is skipped when `cloudfront_distribution_ids` is empty. diff --git a/publish-static-contents/action.yml b/publish-static-contents/action.yml index 6d7341d..4eadf6b 100644 --- a/publish-static-contents/action.yml +++ b/publish-static-contents/action.yml @@ -13,8 +13,12 @@ inputs: description: Space-separated list of CloudFront distribution IDs to invalidate required: false default: '' - versioned_static_prefix: - 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. + versioning: + description: 'When set to true, enables versioned builds. 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 (e.g. 1234567890/).' required: false default: '' @@ -36,21 +40,25 @@ runs: env: INPUT_DIST_DIR: ${{ inputs.dist_dir }} INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }} - INPUT_VERSIONED_STATIC_PREFIX: ${{ inputs.versioned_static_prefix }} + INPUT_VERSIONING: ${{ inputs.versioning }} + INPUT_VERSIONING_PREFIX: ${{ inputs.versioning_prefix }} run: | CACHE_CONTROL_ARG="" - if [[ -n "${INPUT_VERSIONED_STATIC_PREFIX}" ]]; then + if [[ "${INPUT_VERSIONING}" == "true" ]]; then CACHE_CONTROL_ARG="--cache-control 'public, max-age=31536000, immutable'" fi EXCLUDE_INDEX_ARG="" - if [[ -n "${INPUT_VERSIONED_STATIC_PREFIX}" && -f "${INPUT_DIST_DIR}/index.html" ]]; then + if [[ "${INPUT_VERSIONING}" == "true" && -f "${INPUT_DIST_DIR}/index.html" ]]; then EXCLUDE_INDEX_ARG="--exclude index.html" fi aws s3 sync "${INPUT_DIST_DIR}" "s3://${INPUT_S3_BUCKET_NAME}" $CACHE_CONTROL_ARG $EXCLUDE_INDEX_ARG - name: Publish index.html without immutable cache - if: ${{ inputs.versioned_static_prefix != '' }} + if: ${{ inputs.versioning == 'true' }} shell: bash + env: + INPUT_DIST_DIR: ${{ inputs.dist_dir }} + INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }} run: | if [[ -f "${INPUT_DIST_DIR}/index.html" ]]; then aws s3 cp "${INPUT_DIST_DIR}/index.html" "s3://${INPUT_S3_BUCKET_NAME}/index.html" \ @@ -59,13 +67,18 @@ runs: fi - name: Clean up old versioned static builds - if: ${{ inputs.versioned_static_prefix != '' }} + if: ${{ inputs.versioning == 'true' }} shell: bash env: INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }} - INPUT_VERSIONED_STATIC_PREFIX: ${{ inputs.versioned_static_prefix }} + INPUT_VERSIONING_PREFIX: ${{ inputs.versioning_prefix }} run: | - aws s3 ls "s3://$INPUT_S3_BUCKET_NAME/$INPUT_VERSIONED_STATIC_PREFIX/" \ + S3_PATH="s3://$INPUT_S3_BUCKET_NAME" + 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]+' \ | sort --stable --reverse \ | tail -n +3 \ @@ -75,7 +88,7 @@ runs: # delete if older than 7 days if [ $diff -gt 604800000 ]; then echo "Deleting $version" - aws s3 rm --recursive "s3://$INPUT_S3_BUCKET_NAME/$INPUT_VERSIONED_STATIC_PREFIX/$version" + aws s3 rm --recursive "${S3_PATH}/$version" fi done From 44164494c89f15d02102eb57de04aaaffecfa353 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Thu, 21 May 2026 07:37:05 +0000 Subject: [PATCH 24/46] fix: use forgejo.repository for aikido full scan schedule compatibility forgejo.event.repository.name is absent on schedule triggers. forgejo.repository is always set to 'owner/repo' regardless of trigger type. --- aikido-full-scan/action.yml | 3 +-- internal-aikido-full-scan/action.yml | 8 ++------ 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/aikido-full-scan/action.yml b/aikido-full-scan/action.yml index 8ce9334..c0f1887 100644 --- a/aikido-full-scan/action.yml +++ b/aikido-full-scan/action.yml @@ -15,6 +15,5 @@ runs: - uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/internal-aikido-full-scan@internal-aikido-full-scan-v1 with: apikey: ${{ inputs.apikey }} - organization: ${{ forgejo.repository_owner }} - repository-name: ${{ forgejo.event.repository.name }} + repository-name: ${{ forgejo.repository }} branch-name: ${{ forgejo.ref_name }} diff --git a/internal-aikido-full-scan/action.yml b/internal-aikido-full-scan/action.yml index d2241ee..a72efa1 100644 --- a/internal-aikido-full-scan/action.yml +++ b/internal-aikido-full-scan/action.yml @@ -5,11 +5,8 @@ inputs: apikey: description: Aikido CI API key required: true - organization: - description: Organization or owner name - required: true repository-name: - description: Repository name + description: Full repository name (owner/repo) required: true branch-name: description: Branch to scan against @@ -23,9 +20,8 @@ runs: - --apikey - ${{ inputs.apikey }} - --repositoryname - - ${{ inputs.organization }}/${{ inputs.repository-name }} + - ${{ inputs.repository-name }} - --branchname - ${{ inputs.branch-name }} - --force-create-repository-for-branch - --include-dev-deps - From e6012e11c08752f3d219f0bd2b46c159ff0e7475 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Thu, 21 May 2026 07:50:37 +0000 Subject: [PATCH 25/46] fix: strip leading slash from forgejo.repository in aikido full scan --- aikido-full-scan/action.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/aikido-full-scan/action.yml b/aikido-full-scan/action.yml index c0f1887..4db0f4b 100644 --- a/aikido-full-scan/action.yml +++ b/aikido-full-scan/action.yml @@ -12,8 +12,15 @@ inputs: runs: using: composite steps: + - name: Normalize repository name + 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: apikey: ${{ inputs.apikey }} - repository-name: ${{ forgejo.repository }} + repository-name: ${{ steps.repo.outputs.name }} branch-name: ${{ forgejo.ref_name }} From bd5e3add2309505dd14eefd7788e2d9350a51ef9 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Thu, 21 May 2026 13:00:14 +0000 Subject: [PATCH 26/46] feat: add mock-files input to create empty files for Terraform validation --- terraform-validate/README.md | 17 ++++++++++++++++- terraform-validate/action.yml | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/terraform-validate/README.md b/terraform-validate/README.md index 8709593..4e29a4e 100644 --- a/terraform-validate/README.md +++ b/terraform-validate/README.md @@ -10,6 +10,7 @@ Validate Terraform configuration files using the official Terraform CLI. | `terraform-version` | No | `~1.15` | Terraform version to use | | `workspace` | No | `""` | Terraform workspace to use | | `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 @@ -20,8 +21,22 @@ Validate Terraform configuration files using the official Terraform CLI. 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 - Runs `terraform init -backend=false`, `terraform fmt -check -recursive`, and `terraform validate`. - 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. \ No newline at end of file +- 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()`. \ No newline at end of file diff --git a/terraform-validate/action.yml b/terraform-validate/action.yml index 9ca015a..0fcde1f 100644 --- a/terraform-validate/action.yml +++ b/terraform-validate/action.yml @@ -19,6 +19,13 @@ inputs: description: JFrog Artifactory token used for Terraform provider registry (sets TF_TOKEN_schmalz_jfrog_io) required: false 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: using: composite @@ -43,6 +50,18 @@ runs: 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 shell: bash env: From c69017885861edd50dde08cc91905fa63a5cf822 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Thu, 21 May 2026 15:48:41 +0000 Subject: [PATCH 27/46] feat: add per-file cache_rules and fix argument escaping - Add cache_rules input: JSON array of per-file cache overrides, each uploaded individually with its own Cache-Control and Content-Type headers and excluded from the bulk sync - When versioning is true and cache_rules is empty, auto short-cache index.html (no-cache, max-age=300) if it exists in dist_dir - Fix EXCLUDE_ARGS and CT_ARG to use bash arrays for correct handling of filenames and content-type values containing spaces --- publish-static-contents/README.md | 29 +++++++++-- publish-static-contents/action.yml | 80 ++++++++++++++++++++++-------- 2 files changed, 83 insertions(+), 26 deletions(-) diff --git a/publish-static-contents/README.md b/publish-static-contents/README.md index 5d5c6b4..6c51a52 100644 --- a/publish-static-contents/README.md +++ b/publish-static-contents/README.md @@ -9,11 +9,14 @@ Syncs frontend assets to S3 and invalidates a CloudFront distribution. Optionall | `dist_dir` | No | `frontend/dist` | Path to the frontend dist directory | | `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 | -| `versioning` | No | `false` | When `true`, enables versioned builds. 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/`). When omitted, versions are stored at the bucket root (e.g. `1234567890/`) | +| `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 | +| `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 +Plain sync: + ```yaml - uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-static-contents@publish-static-contents-v1 with: @@ -21,7 +24,7 @@ Syncs frontend assets to S3 and invalidates a CloudFront distribution. Optionall cloudfront_distribution_ids: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} ``` -With versioned static assets at the bucket root: +With versioned static assets: ```yaml - uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/publish-static-contents@publish-static-contents-v1 @@ -42,9 +45,25 @@ With versioned static assets under a prefix: 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 -- When `versioning` is `true`, all assets are synced with `Cache-Control: public, max-age=31536000, immutable`. -- `index.html` is always synced separately with `Cache-Control: no-cache, max-age=300` so browsers pick up new deployments quickly. +- 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. +- `cache_rules` works independently of `versioning` and can be used for plain syncs too. - 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. diff --git a/publish-static-contents/action.yml b/publish-static-contents/action.yml index 4eadf6b..9e42cac 100644 --- a/publish-static-contents/action.yml +++ b/publish-static-contents/action.yml @@ -14,13 +14,35 @@ inputs: required: false default: '' versioning: - description: 'When set to true, enables versioned builds. Old versions older than 7 days are deleted, keeping at least the 2 newest.' + description: > + 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 + 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 (e.g. 1234567890/).' + 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 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: using: composite @@ -42,29 +64,45 @@ runs: INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }} INPUT_VERSIONING: ${{ inputs.versioning }} INPUT_VERSIONING_PREFIX: ${{ inputs.versioning_prefix }} + INPUT_CACHE_RULES: ${{ inputs.cache_rules }} run: | + # When versioning is active and no cache_rules provided, short-cache index.html by default + EFFECTIVE_CACHE_RULES="${INPUT_CACHE_RULES}" + if [[ "${INPUT_VERSIONING}" == "true" && "${EFFECTIVE_CACHE_RULES}" == "[]" && -f "${INPUT_DIST_DIR}/index.html" ]]; then + EFFECTIVE_CACHE_RULES='[{"file": "index.html", "cache_control": "no-cache, max-age=300", "content_type": "text/html"}]' + fi + + # Upload each file from cache_rules with its own headers, + # and collect --exclude args so the bulk sync skips them + 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 + + # Bulk sync remaining files; versioned builds get immutable cache headers CACHE_CONTROL_ARG="" if [[ "${INPUT_VERSIONING}" == "true" ]]; then CACHE_CONTROL_ARG="--cache-control 'public, max-age=31536000, immutable'" fi - EXCLUDE_INDEX_ARG="" - if [[ "${INPUT_VERSIONING}" == "true" && -f "${INPUT_DIST_DIR}/index.html" ]]; then - EXCLUDE_INDEX_ARG="--exclude index.html" - fi - aws s3 sync "${INPUT_DIST_DIR}" "s3://${INPUT_S3_BUCKET_NAME}" $CACHE_CONTROL_ARG $EXCLUDE_INDEX_ARG - - name: Publish index.html without immutable cache - if: ${{ inputs.versioning == 'true' }} - shell: bash - env: - INPUT_DIST_DIR: ${{ inputs.dist_dir }} - INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }} - 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 + aws s3 sync "${INPUT_DIST_DIR}" "s3://${INPUT_S3_BUCKET_NAME}" $CACHE_CONTROL_ARG "${EXCLUDE_ARGS[@]}" - name: Clean up old versioned static builds if: ${{ inputs.versioning == 'true' }} @@ -77,7 +115,7 @@ runs: 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]+' \ | sort --stable --reverse \ From 6dc474f7591b05ea081716319f00ce44db202d11 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Fri, 22 May 2026 09:29:42 +0200 Subject: [PATCH 28/46] fix: force public npm registry for pnpm self-installer bootstrap pnpm/action-setup bootstraps itself via npm before pnpm is available. If a repo has a custom registry in .npmrc (e.g. pointing to JFrog or Nexus), the self-installer tries to fetch pnpm from that registry without credentials and fails with exit code 1. Setting NPM_CONFIG_REGISTRY overrides .npmrc for this step only, ensuring pnpm is always fetched from the public registry. Private registry auth is configured in subsequent steps once pnpm is ready. --- pnpm-build/action.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pnpm-build/action.yml b/pnpm-build/action.yml index 4d3397e..e5b3501 100644 --- a/pnpm-build/action.yml +++ b/pnpm-build/action.yml @@ -50,6 +50,12 @@ runs: # 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: + # 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: version: ${{ inputs.pnpm-version }} From afa2cfc241b7b109a8a68f5429649ba82b1d2286 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Fri, 22 May 2026 09:15:42 +0000 Subject: [PATCH 29/46] feat: add i18n-sync action to fetch translations and create pull requests --- .forgejo/workflows/tag-release.yml | 1 + README.md | 1 + i18n-sync/README.md | 46 ++++++++++ i18n-sync/action.yml | 131 +++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 i18n-sync/README.md create mode 100644 i18n-sync/action.yml diff --git a/.forgejo/workflows/tag-release.yml b/.forgejo/workflows/tag-release.yml index 224bfb0..91b476b 100644 --- a/.forgejo/workflows/tag-release.yml +++ b/.forgejo/workflows/tag-release.yml @@ -19,6 +19,7 @@ on: - cache - checkout - helm-deploy + - i18n-sync - inject-content - maven-build - pnpm-build diff --git a/README.md b/README.md index 07aa3e3..079207e 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Shared actions for Forgejo CI/CD pipelines. | [cache](cache) | Cache files between workflow runs | | [checkout](checkout) | Action for checking out a repository | | [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 | diff --git a/i18n-sync/README.md b/i18n-sync/README.md new file mode 100644 index 0000000..5adeee7 --- /dev/null +++ b/i18n-sync/README.md @@ -0,0 +1,46 @@ +# 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. diff --git a/i18n-sync/action.yml b/i18n-sync/action.yml new file mode 100644 index 0000000..ac28fd4 --- /dev/null +++ b/i18n-sync/action.yml @@ -0,0 +1,131 @@ +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}' + )" From ed2dcca9bb81faf61954f59e801725a7ba662840 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Fri, 22 May 2026 09:55:02 +0000 Subject: [PATCH 30/46] feat: add download-artifact and upload-artifact actions with documentation --- .forgejo/workflows/tag-release.yml | 2 ++ README.md | 2 ++ download-artifact/README.md | 46 ++++++++++++++++++++++++++++++ download-artifact/action.yml | 30 +++++++++++++++++++ upload-artifact/README.md | 37 ++++++++++++++++++++++++ upload-artifact/action.yml | 33 +++++++++++++++++++++ 6 files changed, 150 insertions(+) create mode 100644 download-artifact/README.md create mode 100644 download-artifact/action.yml create mode 100644 upload-artifact/README.md create mode 100644 upload-artifact/action.yml diff --git a/.forgejo/workflows/tag-release.yml b/.forgejo/workflows/tag-release.yml index 224bfb0..5928733 100644 --- a/.forgejo/workflows/tag-release.yml +++ b/.forgejo/workflows/tag-release.yml @@ -18,6 +18,7 @@ on: - aws-configure - cache - checkout + - download-artifact - helm-deploy - inject-content - maven-build @@ -25,6 +26,7 @@ on: - publish-static-contents - terraform-apply - terraform-validate + - upload-artifact major-version: description: 'Major version number (e.g. 1)' required: true diff --git a/README.md b/README.md index 07aa3e3..561812a 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Shared actions for Forgejo CI/CD pipelines. | [aws-configure](aws-configure) | Authenticate with AWS via OIDC | | [cache](cache) | Cache files between workflow runs | | [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 | | [inject-content](inject-content) | Inject content into a file by appending or overwriting | | [maven-build](maven-build) | Action for building and validating Maven projects | @@ -18,6 +19,7 @@ Shared actions for Forgejo CI/CD pipelines. | [publish-static-contents](publish-static-contents) | Syncs frontend assets to S3 and invalidates a CloudFront distribution | | [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 | +| [upload-artifact](upload-artifact) | Upload files as a Forgejo Actions artifact | ## Security diff --git a/download-artifact/README.md b/download-artifact/README.md new file mode 100644 index 0000000..e99bb09 --- /dev/null +++ b/download-artifact/README.md @@ -0,0 +1,46 @@ +# 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`. diff --git a/download-artifact/action.yml b/download-artifact/action.yml new file mode 100644 index 0000000..343135f --- /dev/null +++ b/download-artifact/action.yml @@ -0,0 +1,30 @@ +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 }} diff --git a/upload-artifact/README.md b/upload-artifact/README.md new file mode 100644 index 0000000..3e93149 --- /dev/null +++ b/upload-artifact/README.md @@ -0,0 +1,37 @@ +# 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. diff --git a/upload-artifact/action.yml b/upload-artifact/action.yml new file mode 100644 index 0000000..ccec5a4 --- /dev/null +++ b/upload-artifact/action.yml @@ -0,0 +1,33 @@ +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 }} From dcb17b88bff17e487f0d62b9e0a39e26933a51e3 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Fri, 22 May 2026 09:57:52 +0000 Subject: [PATCH 31/46] chore: use the workflow standards --- .forgejo/workflows/pr-check-aikido.yml | 2 -- ...{validate-shared-actions.yml => pr-check-shared-actions.yml} | 1 - 2 files changed, 3 deletions(-) rename .forgejo/workflows/{validate-shared-actions.yml => pr-check-shared-actions.yml} (92%) diff --git a/.forgejo/workflows/pr-check-aikido.yml b/.forgejo/workflows/pr-check-aikido.yml index 8845713..1687d9c 100644 --- a/.forgejo/workflows/pr-check-aikido.yml +++ b/.forgejo/workflows/pr-check-aikido.yml @@ -2,8 +2,6 @@ name: Aikido Security PR Check on: pull_request: - branches: - - '*' concurrency: group: ${{ forgejo.workflow }}-${{ forgejo.ref }} diff --git a/.forgejo/workflows/validate-shared-actions.yml b/.forgejo/workflows/pr-check-shared-actions.yml similarity index 92% rename from .forgejo/workflows/validate-shared-actions.yml rename to .forgejo/workflows/pr-check-shared-actions.yml index b2ef0ec..25d68e7 100644 --- a/.forgejo/workflows/validate-shared-actions.yml +++ b/.forgejo/workflows/pr-check-shared-actions.yml @@ -2,7 +2,6 @@ name: validate-shared-actions on: pull_request: - types: [opened, reopened, synchronize] permissions: contents: read From 452039b6fd8e099ce147c7f8cd5b1ddb596850ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6hringer?= Date: Mon, 1 Jun 2026 11:06:31 +0200 Subject: [PATCH 32/46] feat: decouple hostname from action Refs: ITDO-339 --- helm-deploy/README.md | 2 +- helm-deploy/action.yml | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/helm-deploy/README.md b/helm-deploy/README.md index 75c4cfa..30b016b 100644 --- a/helm-deploy/README.md +++ b/helm-deploy/README.md @@ -7,7 +7,7 @@ Deploy a service to Kubernetes via Helm over SSH. | Input | Required | Default | Description | |-------|----------|---------|-------------| | `service-name` | Yes | — | Helm release name | -| `helm-host` | Yes | — | SSH target host (e.g. `dsp1-stage.schmalzgroup.net`) | +| `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 | diff --git a/helm-deploy/action.yml b/helm-deploy/action.yml index be3cfc5..cf75ad7 100644 --- a/helm-deploy/action.yml +++ b/helm-deploy/action.yml @@ -5,8 +5,8 @@ inputs: service-name: description: Helm release name required: true - helm-host: - description: SSH target (e.g., dsp1-stage.schmalzgroup.net) + helm-cluster: + description: Name of the target Kubernetes cluster to deploy to required: true overrides-file: description: Local path to Helm values override file @@ -45,10 +45,22 @@ runs: 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: ${{ inputs.helm-host }} + HELM_HOST: ${{ steps.map-host.outputs.host }} SERVICE_NAME: ${{ inputs.service-name }} OVERRIDES_FILE: ${{ inputs.overrides-file }} run: | @@ -63,7 +75,7 @@ runs: - name: Helm deploy shell: bash env: - HELM_HOST: ${{ inputs.helm-host }} + HELM_HOST: ${{ steps.map-host.outputs.host }} SERVICE_NAME: ${{ inputs.service-name }} NAMESPACE: ${{ inputs.namespace }} HELM_REPO: ${{ inputs.helm-repo }} From 08123e3f5677e203e7980343a97f9e541dbfb45b Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 1 Jun 2026 12:35:17 +0000 Subject: [PATCH 33/46] fix: update cache control argument handling for S3 sync --- publish-static-contents/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/publish-static-contents/action.yml b/publish-static-contents/action.yml index 9e42cac..7e63359 100644 --- a/publish-static-contents/action.yml +++ b/publish-static-contents/action.yml @@ -97,12 +97,12 @@ runs: fi # Bulk sync remaining files; versioned builds get immutable cache headers - CACHE_CONTROL_ARG="" + CACHE_CONTROL_ARG=() if [[ "${INPUT_VERSIONING}" == "true" ]]; then - CACHE_CONTROL_ARG="--cache-control 'public, max-age=31536000, immutable'" + CACHE_CONTROL_ARG=("--cache-control" "public, max-age=31536000, immutable") fi - aws s3 sync "${INPUT_DIST_DIR}" "s3://${INPUT_S3_BUCKET_NAME}" $CACHE_CONTROL_ARG "${EXCLUDE_ARGS[@]}" + aws s3 sync "${INPUT_DIST_DIR}" "s3://${INPUT_S3_BUCKET_NAME}" "${CACHE_CONTROL_ARG[@]}" "${EXCLUDE_ARGS[@]}" - name: Clean up old versioned static builds if: ${{ inputs.versioning == 'true' }} From 802aa7d6fefc76bcabc75c053840cc37d11a9ffb Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 1 Jun 2026 14:30:16 +0000 Subject: [PATCH 34/46] feat: add rust-build action with documentation --- .forgejo/workflows/tag-release.yml | 1 + README.md | 1 + rust-build/README.md | 42 +++++++++++ rust-build/action.yml | 112 +++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 rust-build/README.md create mode 100644 rust-build/action.yml diff --git a/.forgejo/workflows/tag-release.yml b/.forgejo/workflows/tag-release.yml index b69eee4..6dffa2a 100644 --- a/.forgejo/workflows/tag-release.yml +++ b/.forgejo/workflows/tag-release.yml @@ -25,6 +25,7 @@ on: - maven-build - pnpm-build - publish-static-contents + - rust-build - terraform-apply - terraform-validate - upload-artifact diff --git a/README.md b/README.md index eb8b0f0..c35a5bb 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Shared actions for Forgejo CI/CD pipelines. | [maven-build](maven-build) | Action for building and validating Maven projects | | [pnpm-build](pnpm-build) | Action for building and validating with PNPM | | [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 | | [upload-artifact](upload-artifact) | Upload files as a Forgejo Actions artifact | diff --git a/rust-build/README.md b/rust-build/README.md new file mode 100644 index 0000000..7245382 --- /dev/null +++ b/rust-build/README.md @@ -0,0 +1,42 @@ +# 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 ` 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. diff --git a/rust-build/action.yml b/rust-build/action.yml new file mode 100644 index 0000000..d12b6cb --- /dev/null +++ b/rust-build/action.yml @@ -0,0 +1,112 @@ +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" < Date: Wed, 3 Jun 2026 14:38:39 +0000 Subject: [PATCH 35/46] feat: add vacuum-lint action with documentation --- .forgejo/workflows/tag-release.yml | 1 + README.md | 4 +- vacuum-lint/README.md | 25 ++++++++++++ vacuum-lint/action.yml | 65 ++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 vacuum-lint/README.md create mode 100644 vacuum-lint/action.yml diff --git a/.forgejo/workflows/tag-release.yml b/.forgejo/workflows/tag-release.yml index 6dffa2a..21f5c87 100644 --- a/.forgejo/workflows/tag-release.yml +++ b/.forgejo/workflows/tag-release.yml @@ -29,6 +29,7 @@ on: - terraform-apply - terraform-validate - upload-artifact + - vacuum-lint major-version: description: 'Major version number (e.g. 1)' required: true diff --git a/README.md b/README.md index c35a5bb..bfb6f69 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Shared actions for Forgejo CI/CD pipelines. | [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 | | [upload-artifact](upload-artifact) | Upload files as a Forgejo Actions artifact | - +| [vacuum-lint](vacuum-lint) | Validate and lint OpenAPI specifications using Vacuum | ## Security @@ -38,4 +38,4 @@ Reference actions from your project's workflow: # see each action's README for inputs ``` -Each action has its own README with inputs, usage examples, and notes. \ No newline at end of file +Each action has its own README with inputs, usage examples, and notes. diff --git a/vacuum-lint/README.md b/vacuum-lint/README.md new file mode 100644 index 0000000..4193a98 --- /dev/null +++ b/vacuum-lint/README.md @@ -0,0 +1,25 @@ +# 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`. diff --git a/vacuum-lint/action.yml b/vacuum-lint/action.yml new file mode 100644 index 0000000..222640a --- /dev/null +++ b/vacuum-lint/action.yml @@ -0,0 +1,65 @@ +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 "" From 3c3aa7a8ce825240e0b8c906f62661274eb88ddb Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Mon, 8 Jun 2026 15:06:22 +0000 Subject: [PATCH 36/46] feat: add playwright actions with documentation --- .forgejo/workflows/tag-release.yml | 2 + README.md | 2 + playwright-merge/README.md | 39 +++++++++++ playwright-merge/action.yml | 104 +++++++++++++++++++++++++++++ playwright-run/README.md | 33 +++++++++ playwright-run/action.yml | 87 ++++++++++++++++++++++++ 6 files changed, 267 insertions(+) create mode 100644 playwright-merge/README.md create mode 100644 playwright-merge/action.yml create mode 100644 playwright-run/README.md create mode 100644 playwright-run/action.yml diff --git a/.forgejo/workflows/tag-release.yml b/.forgejo/workflows/tag-release.yml index 21f5c87..3bd09b0 100644 --- a/.forgejo/workflows/tag-release.yml +++ b/.forgejo/workflows/tag-release.yml @@ -24,6 +24,8 @@ on: - inject-content - maven-build - pnpm-build + - playwright-merge + - playwright-run - publish-static-contents - rust-build - terraform-apply diff --git a/README.md b/README.md index bfb6f69..cf223c2 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ Shared actions for Forgejo CI/CD pipelines. | [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 | +| [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-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 | diff --git a/playwright-merge/README.md b/playwright-merge/README.md new file mode 100644 index 0000000..fdc9845 --- /dev/null +++ b/playwright-merge/README.md @@ -0,0 +1,39 @@ +# 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///` when `publish-test-reports` is `true`. +- Prints the final report URL on `https://test-reports.schmalz.com`. diff --git a/playwright-merge/action.yml b/playwright-merge/action.yml new file mode 100644 index 0000000..f7ae409 --- /dev/null +++ b/playwright-merge/action.yml @@ -0,0 +1,104 @@ +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" diff --git a/playwright-run/README.md b/playwright-run/README.md new file mode 100644 index 0000000..825b354 --- /dev/null +++ b/playwright-run/README.md @@ -0,0 +1,33 @@ +# 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-`. +- Use `playwright-merge` in a follow-up job to merge shard reports. diff --git a/playwright-run/action.yml b/playwright-run/action.yml new file mode 100644 index 0000000..b07b29b --- /dev/null +++ b/playwright-run/action.yml @@ -0,0 +1,87 @@ +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 From 801c7e224931fc193043927f3d0b60629f8f3ac5 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Thu, 11 Jun 2026 07:34:01 +0000 Subject: [PATCH 37/46] feat: add publish actions with documentation --- .forgejo/workflows/tag-release.yml | 2 + README.md | 2 + publish-npm-package/README.md | 28 +++++++++++++ publish-npm-package/action.yml | 64 ++++++++++++++++++++++++++++++ publish-rust-crate/README.md | 29 ++++++++++++++ publish-rust-crate/action.yml | 64 ++++++++++++++++++++++++++++++ 6 files changed, 189 insertions(+) create mode 100644 publish-npm-package/README.md create mode 100644 publish-npm-package/action.yml create mode 100644 publish-rust-crate/README.md create mode 100644 publish-rust-crate/action.yml diff --git a/.forgejo/workflows/tag-release.yml b/.forgejo/workflows/tag-release.yml index 3bd09b0..79c61b0 100644 --- a/.forgejo/workflows/tag-release.yml +++ b/.forgejo/workflows/tag-release.yml @@ -26,6 +26,8 @@ on: - pnpm-build - playwright-merge - playwright-run + - publish-npm-package + - publish-rust-crate - publish-static-contents - rust-build - terraform-apply diff --git a/README.md b/README.md index cf223c2..66f3ecf 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ Shared actions for Forgejo CI/CD pipelines. | [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 | | [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 | diff --git a/publish-npm-package/README.md b/publish-npm-package/README.md new file mode 100644 index 0000000..7411507 --- /dev/null +++ b/publish-npm-package/README.md @@ -0,0 +1,28 @@ +# 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. diff --git a/publish-npm-package/action.yml b/publish-npm-package/action.yml new file mode 100644 index 0000000..69b8fa3 --- /dev/null +++ b/publish-npm-package/action.yml @@ -0,0 +1,64 @@ +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 diff --git a/publish-rust-crate/README.md b/publish-rust-crate/README.md new file mode 100644 index 0000000..69a44c2 --- /dev/null +++ b/publish-rust-crate/README.md @@ -0,0 +1,29 @@ +# 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 `. +- Third-party actions used internally are pinned to exact commit SHAs to prevent supply chain attacks. diff --git a/publish-rust-crate/action.yml b/publish-rust-crate/action.yml new file mode 100644 index 0000000..11b26b3 --- /dev/null +++ b/publish-rust-crate/action.yml @@ -0,0 +1,64 @@ +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" <> "${CARGO_HOME_DIR}/credentials.toml" < Date: Mon, 15 Jun 2026 12:09:09 +0530 Subject: [PATCH 38/46] fix: create terraform workspaces automatically in shared actions --- terraform-apply/action.yml | 2 +- terraform-validate/action.yml | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/terraform-apply/action.yml b/terraform-apply/action.yml index 3776737..8d76de9 100644 --- a/terraform-apply/action.yml +++ b/terraform-apply/action.yml @@ -61,7 +61,7 @@ runs: TF_DIR: ${{ inputs.terraform-dir }} TF_WORKSPACE_NAME: ${{ inputs.workspace }} run: | - terraform -chdir="$TF_DIR" workspace select "$TF_WORKSPACE_NAME" + terraform -chdir="$TF_DIR" workspace select -or-create "$TF_WORKSPACE_NAME" - name: Terraform Apply shell: bash diff --git a/terraform-validate/action.yml b/terraform-validate/action.yml index 0fcde1f..b4389db 100644 --- a/terraform-validate/action.yml +++ b/terraform-validate/action.yml @@ -69,6 +69,15 @@ runs: TF_TOKEN_schmalz_jfrog_io: ${{ inputs.jfrog-token }} run: terraform -chdir=${{ env.TF_DIR }} init -backend=false -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 Format Check shell: bash env: From 36343e0a7903bc0ff96496c870bde5adb33b8bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6hringer?= Date: Mon, 15 Jun 2026 09:28:00 +0200 Subject: [PATCH 39/46] fix: revert workspace selection as validate does not initialize backend --- terraform-validate/action.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/terraform-validate/action.yml b/terraform-validate/action.yml index b4389db..0fcde1f 100644 --- a/terraform-validate/action.yml +++ b/terraform-validate/action.yml @@ -69,15 +69,6 @@ runs: TF_TOKEN_schmalz_jfrog_io: ${{ inputs.jfrog-token }} run: terraform -chdir=${{ env.TF_DIR }} init -backend=false -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 Format Check shell: bash env: From 5b6f2cfd289b2345339b91f05e827630791e6533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6hringer?= Date: Tue, 16 Jun 2026 13:33:16 +0200 Subject: [PATCH 40/46] feat: create deploy-esb action --- .forgejo/workflows/tag-release.yml | 1 + esb-deploy/README.md | 30 +++++++++++++++ esb-deploy/action.yml | 60 ++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 esb-deploy/README.md create mode 100644 esb-deploy/action.yml diff --git a/.forgejo/workflows/tag-release.yml b/.forgejo/workflows/tag-release.yml index 79c61b0..f17834c 100644 --- a/.forgejo/workflows/tag-release.yml +++ b/.forgejo/workflows/tag-release.yml @@ -18,6 +18,7 @@ on: - aws-configure - cache - checkout + - deploy-esb - download-artifact - helm-deploy - i18n-sync diff --git a/esb-deploy/README.md b/esb-deploy/README.md new file mode 100644 index 0000000..1b761ac --- /dev/null +++ b/esb-deploy/README.md @@ -0,0 +1,30 @@ +# 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-name` | Yes | — | Name of the service to deploy | + +## Usage + +```yaml +- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/esb-deploy@esb-deploy-v1 + with: + service-name: my-service + docker-host: esbdocker2-stage.schmalzgroup.net + java-version: 8 + maven-profile: test + maven-settings: ${{ secrets.MAVEN_SETTINGS }} +``` + +## 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 diff --git a/esb-deploy/action.yml b/esb-deploy/action.yml new file mode 100644 index 0000000..96a6593 --- /dev/null +++ b/esb-deploy/action.yml @@ -0,0 +1,60 @@ +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: 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 }} + + - 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" From 4e15383d233328604bbd4c37d9dc737d9784e165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6hringer?= Date: Tue, 16 Jun 2026 16:25:43 +0200 Subject: [PATCH 41/46] fix: use correct name in tag-release workflow --- .forgejo/workflows/tag-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/tag-release.yml b/.forgejo/workflows/tag-release.yml index f17834c..428ec72 100644 --- a/.forgejo/workflows/tag-release.yml +++ b/.forgejo/workflows/tag-release.yml @@ -18,8 +18,8 @@ on: - aws-configure - cache - checkout - - deploy-esb - download-artifact + - esb-deploy - helm-deploy - i18n-sync - inject-content From 115300a7e1614eaee4fc5d40ac7baf6e151ce73b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6hringer?= Date: Tue, 16 Jun 2026 16:32:34 +0200 Subject: [PATCH 42/46] fix: readme mentioned wrong parameters --- esb-deploy/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esb-deploy/README.md b/esb-deploy/README.md index 1b761ac..51eae81 100644 --- a/esb-deploy/README.md +++ b/esb-deploy/README.md @@ -10,14 +10,14 @@ Deploy a service to an ESB docker host. | `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-name` | Yes | — | Name of the service to deploy | +| `service` | Yes | — | Name of the service to deploy | ## Usage ```yaml - uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/esb-deploy@esb-deploy-v1 with: - service-name: my-service + service: my-service docker-host: esbdocker2-stage.schmalzgroup.net java-version: 8 maven-profile: test From 0134da8ac7cab452a944bfd3abe25487739952cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6hringer?= Date: Wed, 17 Jun 2026 07:42:35 +0200 Subject: [PATCH 43/46] fix: propagate maven profile in esb-deploy action --- esb-deploy/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/esb-deploy/action.yml b/esb-deploy/action.yml index 96a6593..c8fd97f 100644 --- a/esb-deploy/action.yml +++ b/esb-deploy/action.yml @@ -38,6 +38,7 @@ runs: 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' }} From 91494155758370e2731738b33ed27543e9113989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6hringer?= Date: Wed, 17 Jun 2026 08:01:38 +0200 Subject: [PATCH 44/46] fix: action did not provide stage toggle --- esb-deploy/README.md | 2 ++ esb-deploy/action.yml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/esb-deploy/README.md b/esb-deploy/README.md index 51eae81..ae02466 100644 --- a/esb-deploy/README.md +++ b/esb-deploy/README.md @@ -11,6 +11,7 @@ Deploy a service to an ESB docker host. | `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 @@ -22,6 +23,7 @@ Deploy a service to an ESB docker host. java-version: 8 maven-profile: test maven-settings: ${{ secrets.MAVEN_SETTINGS }} + stage: true ``` ## Notes diff --git a/esb-deploy/action.yml b/esb-deploy/action.yml index c8fd97f..584274e 100644 --- a/esb-deploy/action.yml +++ b/esb-deploy/action.yml @@ -17,6 +17,9 @@ inputs: 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' From a49611f28806801b87e0d31dd13ab949b5fae7d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6hringer?= Date: Wed, 17 Jun 2026 08:52:37 +0200 Subject: [PATCH 45/46] fix: maven action did not set profile in verify mode --- maven-build/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/maven-build/action.yml b/maven-build/action.yml index 961bc28..1c9b161 100644 --- a/maven-build/action.yml +++ b/maven-build/action.yml @@ -97,6 +97,7 @@ runs: run: | mvn --batch-mode $VERIFY_GOALS \ -s /tmp/maven-settings.xml \ + -P "$MAVEN_PROFILE" \ $EXTRA_ARGS - name: Deploy From 9783972537edc63ff438168f5c4348148512b4b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B6hringer?= Date: Wed, 17 Jun 2026 09:41:57 +0200 Subject: [PATCH 46/46] fix: actually provide maven profile --- maven-build/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/maven-build/action.yml b/maven-build/action.yml index 1c9b161..6b16ded 100644 --- a/maven-build/action.yml +++ b/maven-build/action.yml @@ -94,6 +94,7 @@ runs: 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 \