From ffffecd074bfe756f84c429723330e7971500e55 Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Fri, 17 Apr 2026 12:33:21 +0000 Subject: [PATCH 1/5] Initial commit --- .gitignore | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b24d71e --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# These are some examples of commonly ignored file patterns. +# You should customize this list as applicable to your project. +# Learn more about .gitignore: +# https://www.atlassian.com/git/tutorials/saving-changes/gitignore + +# Node artifact files +node_modules/ +dist/ + +# Compiled Java class files +*.class + +# Compiled Python bytecode +*.py[cod] + +# Log files +*.log + +# Package files +*.jar + +# Maven +target/ +dist/ + +# JetBrains IDE +.idea/ + +# Unit test reports +TEST*.xml + +# Generated by MacOS +.DS_Store + +# Generated by Windows +Thumbs.db + +# Applications +*.app +*.exe +*.war + +# Large media files +*.mp4 +*.tiff +*.avi +*.flv +*.mov +*.wmv + From 553786f00030b46ff7444325af11688024ce556d Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Fri, 17 Apr 2026 13:59:57 +0200 Subject: [PATCH 2/5] Add Forgejo shared actions migration working document --- ...-04-17-forgejo-shared-actions-migration.md | 1280 +++++++++++++++++ 1 file changed, 1280 insertions(+) create mode 100644 2026-04-17-forgejo-shared-actions-migration.md diff --git a/2026-04-17-forgejo-shared-actions-migration.md b/2026-04-17-forgejo-shared-actions-migration.md new file mode 100644 index 0000000..6837c71 --- /dev/null +++ b/2026-04-17-forgejo-shared-actions-migration.md @@ -0,0 +1,1280 @@ +# Forgejo Shared Actions Migration Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Migrate all Bitbucket + Jenkins CI/CD pipelines to STACKIT Git (Forgejo Actions) using a shared-actions repository with one reusable composite action per responsibility. + +**Architecture:** A single `schmalz/shared-actions` repository contains all reusable composite actions and reusable workflows. Consumer repositories call them via `uses: schmalz/shared-actions/.github/actions/@v1`. Each action has exactly one responsibility and is fully self-contained. AWS authentication moves from EC2InstanceMetadata to OIDC federation. + +> **STACKIT Git URL:** `https://schmalz-git.git.onstackit.cloud` (DNS may move to `git.schmalzgroup.com` — update all `uses:` references and OIDC trust policies when it does). + +**Tech Stack:** Forgejo Actions (GitHub Actions-compatible YAML), OIDC AWS auth, pnpm, Maven/Jib, Terraform, Playwright, Rust/Cargo, JFrog Artifactory, Helm/Kubernetes + +--- + +## Context & Background + +### Source Systems Being Replaced + +| System | Count | Details | +|--------|-------|---------| +| Jenkins pipelines | 18 | Java/Maven, Helm deploy to on-prem Kubernetes | +| Bitbucket pipelines | ~53 | TypeScript Lambda, Terraform, Playwright, Rust | +| Custom Bitbucket Pipes | 2 | Docker-based: SBOM upload, i18n translation sync | + +### Key Design Decisions + +| Decision | Choice | Reason | +|----------|--------|--------| +| Shared repo | `schmalz/shared-actions` (new repo) | Clean separation, versioned independently | +| Version pinning | Semver tags `@v1` / `@v1.2.3` | Stability with explicit upgrade path | +| Runners | STACKIT-hosted runners | No self-hosted infrastructure to maintain | +| AWS auth | OIDC federation | EC2InstanceMetadata not available on STACKIT runners | +| Custom pipes | Convert to composite actions (scripts) | No Docker overhead, easier to maintain | +| K8s target (Jenkins) | On-prem — services stay on `dsp1.schmalzgroup.net` / `dsp1-stage.schmalzgroup.net`; `helm-deploy` is permanent | | +| Third-party actions | Use fully qualified `https://data.forgejo.org/` URLs | Avoids `DEFAULT_ACTIONS_URL` misconfiguration on the STACKIT instance | + +### AWS Accounts (preserved from existing pipelines) + +| Environment | Account ID | IAM Role | +|-------------|-----------|----------| +| Stage | `953815822701` | `arn:aws:iam::953815822701:role/deployment-role` | +| Prod | `409901728745` | `arn:aws:iam::409901728745:role/deployment-role` | +| Dev | `916033256208` | `arn:aws:iam::916033256208:role/deployment-role` | + +### JFrog Artifactory (preserved) + +- npm registry: `https://schmalz.jfrog.io/artifactory/api/npm/default-npm/` +- Docker registry: `schmalz.jfrog.io/default-docker` +- Terraform modules: `https://schmalz.jfrog.io/artifactory/terraform/` +- Cargo registry: `schmalz.jfrog.io` +- Auth: `JFROG_TOKEN` secret (set per-repo or at org level) + +--- + +## Repository Structure: `schmalz/shared-actions` + +``` +schmalz/shared-actions/ +├── .github/ +│ ├── actions/ +│ │ ├── aws-configure/ +│ │ │ └── action.yml +│ │ ├── maven-build/ +│ │ │ └── action.yml +│ │ ├── helm-deploy/ +│ │ │ └── action.yml +│ │ ├── secrets-inject/ +│ │ │ └── action.yml +│ │ ├── pnpm-build/ +│ │ │ └── action.yml +│ │ ├── terraform-validate/ +│ │ │ └── action.yml +│ │ ├── terraform-apply/ +│ │ │ └── action.yml +│ │ ├── aws-s3-sync/ +│ │ │ └── action.yml +│ │ ├── aws-lambda-update/ +│ │ │ └── action.yml +│ │ ├── playwright-e2e/ +│ │ │ └── action.yml +│ │ ├── rust-build/ +│ │ │ └── action.yml +│ │ ├── publish-npm-package/ +│ │ │ └── action.yml +│ │ ├── sbom-upload/ +│ │ │ └── action.yml +│ │ ├── terraform-module-publish/ +│ │ │ └── action.yml +│ │ ├── docker-build-push/ +│ │ │ └── action.yml +│ │ ├── publish-rust-crate/ +│ │ │ └── action.yml +│ │ └── cloudfront-invalidate/ +│ │ └── action.yml +│ └── workflows/ +│ └── (reusable workflows if needed later) +└── README.md +``` + +--- + +## One-Time Infrastructure Setup (Before Any Migration) + +### OIDC Setup in AWS (required — blocks all migration work) + +1. In AWS IAM console for **both** accounts (stage `953815822701`, prod `409901728745`): + - Add an OIDC Identity Provider for the STACKIT Git instance + - URL: `https://schmalz-git.git.onstackit.cloud` + - Audience: `sts.amazonaws.com` +2. Update the trust policy on `deployment-role` in each account to allow the OIDC provider: + ```json + { + "Effect": "Allow", + "Principal": { + "Federated": "arn:aws:iam:::oidc-provider/schmalz-git.git.onstackit.cloud" + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringLike": { + "schmalz-git.git.onstackit.cloud:sub": "repo:schmalz/*:*" + }, + "StringEquals": { + "schmalz-git.git.onstackit.cloud:aud": "sts.amazonaws.com" + } + } + } + ``` + > **Note:** The Forgejo OIDC `iss` claim is `https://schmalz-git.git.onstackit.cloud/api/actions`. AWS uses the hostname only (without `/api/actions`) as the IdP identifier. + > + > If the DNS moves to `git.schmalzgroup.com`, a new OIDC provider entry must be added in AWS and both `sub` + `aud` conditions must be updated. +3. Set `JFROG_TOKEN` as an organization-level secret in STACKIT Git (avoid per-repo duplication). + +### Third-Party Action URLs (required — must be confirmed before Phase 1) + +Forgejo resolves bare action references like `actions/checkout@v4` by prepending `DEFAULT_ACTIONS_URL`, which defaults to `https://data.forgejo.org/` but can be overridden by the instance admin. + +**Action:** Confirm with the STACKIT instance admin what `DEFAULT_ACTIONS_URL` is set to on the managed instance. + +Until confirmed, all consumer workflows use **fully qualified URLs**: + +| Action | Qualified URL | +|--------|---------------| +| `actions/checkout@v4` | `https://data.forgejo.org/actions/checkout@v4` | +| `actions/upload-artifact@v4` | `https://data.forgejo.org/actions/upload-artifact@v4` | +| `actions/download-artifact@v4` | `https://data.forgejo.org/actions/download-artifact@v4` | + +The `aws-configure` action does **not** depend on `aws-actions/configure-aws-credentials`. It performs the OIDC token exchange directly via shell (see action spec below) — no external action required. + +### `enable-openid-connect` (required for all AWS workflows) + +Forgejo uses `enable-openid-connect: true` at the workflow or job level to enable OIDC token generation. This is **not** the same as GitHub's `permissions: id-token: write`. Every workflow or job that calls `aws-configure` must include this flag. + +--- + +## Shared Action Specifications + +### Action 1: `aws-configure` + +**Responsibility:** Authenticate with AWS via OIDC. Called before any step that needs AWS CLI. Replaces all `aws configure --profile ... set credential_source Ec2InstanceMetadata` blocks. + +**Inputs:** + +| Input | Required | Default | Description | +|-------|----------|---------|-------------| +| `role-arn` | yes | — | Full IAM role ARN | +| `aws-profile` | no | `default` | Profile name written to `~/.aws/config` | +| `region` | no | `eu-central-1` | AWS region | + +**What it does** (pure shell, no external action dependency): +```bash +# Exchange Forgejo OIDC token for AWS credentials +OIDC_TOKEN=$(curl -sf \ + -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ + "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | jq -r .value) + +CREDS=$(aws sts assume-role-with-web-identity \ + --role-arn "$INPUT_ROLE_ARN" \ + --role-session-name forgejo-ci \ + --web-identity-token "$OIDC_TOKEN" \ + --region "$INPUT_REGION" \ + --query 'Credentials' --output json) + +mkdir -p ~/.aws + +# Write environment credentials (used by AWS CLI/SDK by default) +echo "AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r .AccessKeyId)" >> $FORGEJO_ENV +echo "AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r .SecretAccessKey)" >> $FORGEJO_ENV +echo "AWS_SESSION_TOKEN=$(echo $CREDS | jq -r .SessionToken)" >> $FORGEJO_ENV +echo "AWS_DEFAULT_REGION=$INPUT_REGION" >> $FORGEJO_ENV + +# Write named profile if requested +if [ "$INPUT_AWS_PROFILE" != "default" ]; then + aws configure set aws_access_key_id "$(echo $CREDS | jq -r .AccessKeyId)" --profile "$INPUT_AWS_PROFILE" + aws configure set aws_secret_access_key "$(echo $CREDS | jq -r .SecretAccessKey)" --profile "$INPUT_AWS_PROFILE" + aws configure set aws_session_token "$(echo $CREDS | jq -r .SessionToken)" --profile "$INPUT_AWS_PROFILE" + aws configure set region "$INPUT_REGION" --profile "$INPUT_AWS_PROFILE" +fi +``` + +**Requires:** `enable-openid-connect: true` at the job or workflow level in the calling workflow (Forgejo-specific; injects `ACTIONS_ID_TOKEN_REQUEST_TOKEN` and `ACTIONS_ID_TOKEN_REQUEST_URL` into the runner environment). + +--- + +### Action 2: `maven-build` + +**Responsibility:** Run Maven build for Java services — either verify-only (for PRs) or full package+jib push (for deploy branches). Replaces the duplicated Maven steps in all 18 Jenkinsfiles. + +**Inputs:** + +| Input | Required | Default | Description | +|-------|----------|---------|-------------| +| `java-version` | no | `8` | JDK version (8, 17, 25) | +| `maven-image` | no | `maven:3.8.5-openjdk-8` | Full Docker image for the build | +| `phase` | no | `verify` | `verify` (PR) or `package-and-push` (deploy) | +| `verify-goals` | no | `spotless:check checkstyle:check compile` | Maven goals for `verify` phase. Override per service if needed (e.g., `employee` uses `spotless:check checkstyle:check test`) | +| `maven-profile` | no | `test` | Maven `-P` profile (test, prod) | +| `service-dir` | no | `.` | Subdirectory containing the service pom.xml (for ESB multi-service) | +| `skip-tests` | no | `false` | Pass `-DskipTests` | +| `image-tag` | no | `${{ github.sha }}-${{ github.run_id }}` | Docker image tag for jib push | +| `maven-settings` | yes | — | Secret name containing `settings.xml` content | +| `extra-args` | no | `` | Additional Maven arguments | + +**Phase `verify` runs** (PR validation — matches existing Jenkins behavior): +``` +mvn -s +``` +> **Note:** The default `verify-goals` is `spotless:check checkstyle:check compile`, matching the current Jenkins PR behavior. This is intentionally lighter than `mvn verify` (which runs the full lifecycle including tests). Only `employee` overrides this to include `test`. + +**Phase `package-and-push` runs:** +``` +mvn clean package jib:build -DsendCredentialsOverHttp=true \ + -Djib.to.tags= -P -s +``` + +**Notes:** +- `maven-image` input allows overriding the full image (some services use `maven:3.9.12-eclipse-temurin-25` for Java 25) +- The action writes `settings.xml` from the secret to a temp file, passes it via `-s`, and deletes it after + +--- + +### Action 3: `helm-deploy` + +**Responsibility:** Deploy a service to Kubernetes via Helm over SSH. Replaces the `deployService()` Groovy function duplicated in every Jenkinsfile. + +**Inputs:** + +| Input | Required | Default | Description | +|-------|----------|---------|-------------| +| `service-name` | yes | — | Helm release name | +| `helm-host` | yes | — | SSH target (e.g., `dsp1-stage.schmalzgroup.net`) | +| `overrides-file` | no | `kubernetes/overrides-su.yaml` | Local path to Helm values override file | +| `image-tag` | yes | — | Docker image tag to deploy | +| `ssh-key` | yes | — | Secret name containing private SSH key | +| `namespace` | no | `dsp` | Kubernetes namespace | +| `helm-repo` | no | `nexus-helm-repository` | Helm chart repository name | +| `helm-chart` | no | `DSP-Blueprint` | Chart name in the repo | + +**What it does:** +```bash +# SCP overrides file to remote host +scp -i -o StrictHostKeyChecking=no root@:/tmp/-overrides.yaml +# Helm upgrade +ssh -i -o StrictHostKeyChecking=no root@ \ + "helm repo update && \ + helm upgrade --install --create-namespace -n \ + / -f /tmp/-overrides.yaml \ + --set image.tag= --atomic --debug" +``` + +--- + +### Action 4: `secrets-inject` + +**Responsibility:** Append a secrets file (from CI secret store) to a Java `.properties` file. Replaces the "Load secrets" stage in every Jenkinsfile. + +**Inputs:** + +| Input | Required | Default | Description | +|-------|----------|---------|-------------| +| `secret-content` | yes | — | Secret value (the properties content to append) | +| `target-file` | yes | — | Path to the `.properties` file (e.g., `src/main/resources/application-su.properties`) | + +**What it does:** +```bash +echo "" >> +echo "" >> +``` + +**Note:** The secret is passed as a value (not a file path) to work naturally with Forgejo Secrets. + +--- + +### Action 5: `pnpm-build` + +**Responsibility:** Set up pnpm, authenticate JFrog npm registry, install dependencies, run configurable npm scripts. Replaces the near-identical pnpm blocks in all ~50 Bitbucket TypeScript pipelines. + +**Inputs:** + +| Input | Required | Default | Description | +|-------|----------|---------|-------------| +| `working-directory` | no | `.` | Directory containing `package.json` | +| `pnpm-version` | no | `10.14` | pnpm version to install globally | +| `jfrog-token` | yes | — | JFrog npm auth token | +| `run-scripts` | no | `ci,typecheck,build` | Comma-separated list of `pnpm run