Merge pull request 'feature/shared-actions' (#8) from feature/shared-actions into main

Reviewed-on: #8
This commit is contained in:
Michael.Seele@schmalz.de 2026-04-30 12:35:47 +00:00
commit 092f0c75a8
Signed by: schmalz-git.git.onstackit.cloud
GPG key ID: 569DFBE669A0D544
15 changed files with 314 additions and 2 deletions

View file

@ -10,6 +10,9 @@ Shared actions for Forgejo CI/CD pipelines.
| [aikido-pr-scan](aikido-pr-scan) | Aikido PR scan |
| [aws-configure](aws-configure) | Authenticate with AWS via OIDC |
| [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-validate](terraform-validate) | Validate Terraform configuration files using the official Terraform CLI |
## Security

View file

@ -12,7 +12,7 @@ inputs:
runs:
using: composite
steps:
- uses: ./actions/internal-aikido-full-scan
- uses: ./.forgejo/actions/internal-aikido-full-scan
with:
apikey: ${{ inputs.apikey }}
organization: ${{ forgejo.repository_owner }}

View file

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

30
pnpm-build/README.md Normal file
View file

@ -0,0 +1,30 @@
# pnpm-build
Action for building and validating with PNPM.
## 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` | No | `""` | JFrog npm auth token for the Artifactory registry |
| `run-scripts` | No | `ci,typecheck,build` | Comma-separated list of `pnpm run` scripts to execute |
| `frozen-lockfile` | No | `true` | Pass `--frozen-lockfile` to `pnpm install` |
| `check-dedupe` | No | `true` | Run `pnpm dedupe --check` before install |
## Usage
```yaml
- uses: https://schmalz-git.git.onstackit.cloud/schmalz/shared-actions/pnpm-build@pnpm-build-v1
with:
working-directory: frontend
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- Configures the Artifactory npm registry authentication only if `jfrog-token` is provided.
- Runs `pnpm dedupe --check` before install when `check-dedupe` is `true`.
- Executes each script in `run-scripts` in order via `pnpm run`.

81
pnpm-build/action.yml Normal file
View file

@ -0,0 +1,81 @@
name: PNPM Build
description: >
Build and validate frontend using PNPM.
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: false
default: ""
run-scripts:
description: Comma-separated list of pnpm run scripts
required: false
default: "ci,typecheck,build"
frozen-lockfile:
description: Pass --frozen-lockfile to pnpm install
required: false
default: "true"
check-dedupe:
description: Run pnpm dedupe --check
required: false
default: "true"
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
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
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
- name: Install pnpm
uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e
with:
version: ${{ inputs.pnpm-version }}
- name: Configure pnpm 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"
- name: Build
shell: bash
env:
PNPM_VERSION: ${{ inputs.pnpm-version }}
WORKING_DIR: ${{ inputs.working-directory }}
RUN_SCRIPTS: ${{ inputs.run-scripts }}
FROZEN_LOCKFILE: ${{ inputs.frozen-lockfile }}
CHECK_DEDUPE: ${{ inputs.check-dedupe }}
run: |
if [ "${CHECK_DEDUPE}" = "true" ]; then
pnpm --prefix="${WORKING_DIR}" dedupe --check
fi
INSTALL_ARGS=""
if [ "${FROZEN_LOCKFILE}" = "true" ]; then
INSTALL_ARGS="--frozen-lockfile"
fi
pnpm --prefix="${WORKING_DIR}" install $INSTALL_ARGS
IFS=',' read -ra SCRIPTS <<< "${RUN_SCRIPTS}"
for script in "${SCRIPTS[@]}"; do
pnpm --prefix="${WORKING_DIR}" run "${script}"
done

View file

@ -0,0 +1,39 @@
# publish-static-contents
Syncs frontend assets to S3 and invalidates a CloudFront distribution. Optionally manages versioned static builds by cleaning up old versions.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `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 |
## Usage
```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 }}
```
With versioned static assets:
```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
```
## Notes
- When `versioned_static_prefix` is set, 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.

View file

@ -0,0 +1,80 @@
name: Publish to CloudFront
description: Syncs frontend assets to S3 and invalidates the CloudFront distribution
inputs:
dist_dir:
description: Path to the frontend dist directory
required: false
default: frontend/dist
s3_bucket_name:
description: Name of the S3 bucket to sync assets to
required: true
cloudfront_distribution_ids:
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.
required: false
default: ''
runs:
using: composite
steps:
- name: Publish frontend assets
shell: bash
env:
INPUT_DIST_DIR: ${{ inputs.dist_dir }}
INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }}
INPUT_VERSIONED_STATIC_PREFIX: ${{ inputs.versioned_static_prefix }}
run: |
CACHE_CONTROL_ARG=""
if [[ -n "${INPUT_VERSIONED_STATIC_PREFIX}" ]]; 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
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 != '' }}
shell: bash
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
- name: Clean up old versioned static builds
if: ${{ inputs.versioned_static_prefix != '' }}
shell: bash
env:
INPUT_S3_BUCKET_NAME: ${{ inputs.s3_bucket_name }}
INPUT_VERSIONED_STATIC_PREFIX: ${{ inputs.versioned_static_prefix }}
run: |
aws s3 ls "s3://$INPUT_S3_BUCKET_NAME/$INPUT_VERSIONED_STATIC_PREFIX/" \
| grep -oP '(?<=PRE )[0-9]+' \
| sort --stable --reverse \
| tail -n +3 \
| while read version; do
now=$(($(date +%s%N)/1000000))
diff=$(($now-$version))
# 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"
fi
done
- name: Invalidate CloudFront
if: ${{ inputs.cloudfront_distribution_ids != '' }}
shell: bash
env:
INPUT_CLOUDFRONT_DISTRIBUTION_IDS: ${{ inputs.cloudfront_distribution_ids }}
run: |
echo "$INPUT_CLOUDFRONT_DISTRIBUTION_IDS" \
| tr ' ' '\n' \
| xargs -I% aws cloudfront create-invalidation --distribution-id % --paths '/*'

View file

@ -0,0 +1,27 @@
# terraform-validate
Validate 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 |
| `workspace` | No | `""` | Terraform workspace to use |
| `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-validate@terraform-validate-v1
with:
workspace: stage
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## 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.

View file

@ -0,0 +1,52 @@
name: Terraform Validate
description: >
Validate 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"
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_DIR: ${{ inputs.terraform-dir }}
TF_TOKEN_schmalz_jfrog_io: ${{ inputs.jfrog-token }}
run: terraform -chdir=${{ env.TF_DIR }} init -backend=false -no-color
- name: Terraform Format Check
shell: bash
env:
TF_DIR: ${{ inputs.terraform-dir }}
run: terraform -chdir=${{ env.TF_DIR }} fmt -check -recursive
- name: Terraform Validate
shell: bash
env:
TF_DIR: ${{ inputs.terraform-dir }}
TF_WORKSPACE: ${{ inputs.workspace }}
TF_TOKEN_schmalz_jfrog_io: ${{ inputs.jfrog-token }}
run: terraform -chdir=${{ env.TF_DIR }} validate