Compare commits

...
Sign in to create a new pull request.

5 commits

39 changed files with 3005 additions and 1 deletions

View file

@ -0,0 +1,23 @@
name: validate-shared-actions
on:
pull_request:
types: [opened, reopened, synchronize]
permissions:
contents: read
jobs:
validate-shared-actions:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Validate shared action metadata
uses: mpalmer/action-validator@v0.9.0
with:
version: 0.9.0
patterns: |
:(glob)**/action.yml
:(glob)**/action.yaml

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,24 @@ Shared composite actions for Forgejo CI/CD pipelines.
| Action | Description |
|--------|-------------|
| | |
| [aikido-full-scan](.github/actions/aikido-full-scan) | Run a full Aikido security scan (for nightly/scheduled runs) |
| [aikido-pr-scan](.github/actions/aikido-pr-scan) | Run Aikido security scan on a PR in gating mode (fails on new vulnerabilities) |
| [aws-configure](.github/actions/aws-configure) | Authenticate with AWS via OIDC |
| [aws-lambda-update](.github/actions/aws-lambda-update) | Update Lambda function alias to a new version, optionally wait for provisioned concurrency |
| [aws-s3-sync](.github/actions/aws-s3-sync) | Sync build artifacts to S3, clean up old versioned assets, optionally invalidate CloudFront |
| [cloudfront-invalidate](.github/actions/cloudfront-invalidate) | Invalidate one or more CloudFront distributions |
| [docker-build-push](.github/actions/docker-build-push) | Build Docker image and push to JFrog with semver tags (major, minor, patch) |
| [helm-deploy](.github/actions/helm-deploy) | Deploy a service to Kubernetes via Helm over SSH |
| [maven-build](.github/actions/maven-build) | Run Maven build — verify-only (PRs) or package+jib push (deploy) |
| [playwright-e2e](.github/actions/playwright-e2e) | Run Playwright E2E tests with optional sharding, upload results to S3 |
| [pnpm-build](.github/actions/pnpm-build) | Set up pnpm, authenticate JFrog npm registry, install deps, run scripts |
| [publish-npm-package](.github/actions/publish-npm-package) | Build and publish npm package to JFrog Artifactory |
| [publish-rust-crate](.github/actions/publish-rust-crate) | Build, test, and publish Rust crate to JFrog Cargo registry |
| [rust-build](.github/actions/rust-build) | Run Rust CI — fmt, clippy, tests, optional cross-compilation |
| [secrets-inject](.github/actions/secrets-inject) | Append a secrets file to a Java .properties file |
| [terraform-apply](.github/actions/terraform-apply) | Full Terraform init + workspace + apply + output capture |
| [terraform-module-publish](.github/actions/terraform-module-publish) | Zip a Terraform module and publish to JFrog Artifactory |
| [terraform-validate](.github/actions/terraform-validate) | Validate Terraform code without backend (for PR checks) |
## Usage

View file

@ -0,0 +1,44 @@
# aikido-full-scan
Run a full Aikido security scan — intended for nightly or scheduled runs.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `aikido-api-key` | Yes | | Aikido CI API key (`AIK_CI_xxx`) |
| `branch-name` | Yes | | Branch to scan (`dev` for applications, `main` for libraries) |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/aikido-full-scan@v1
with:
aikido-api-key: ${{ secrets.AIKIDO_API_KEY }}
branch-name: dev
```
### Nightly schedule example
```yaml
on:
schedule:
- cron: '0 2 * * *'
jobs:
aikido-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: schmalz/shared-actions/.github/actions/aikido-full-scan@v1
with:
aikido-api-key: ${{ secrets.AIKIDO_API_KEY }}
branch-name: dev
```
## Notes
- Uses the `aikidosecurity/local-scanner` Docker image to scan inside the CI runner.
- Performs a full scan (all scan types: code, dependencies, IaC, secrets) and uploads results to the Aikido dashboard.
- Repository name is auto-detected from Forgejo environment variables.
- Does not run in gating mode — the scan reports findings but does not fail the workflow unless the scanner itself errors.

View file

@ -0,0 +1,29 @@
name: aikido-full-scan
description: Run a full Aikido security scan (for nightly/scheduled runs)
inputs:
aikido-api-key:
description: 'Aikido CI API key (AIK_CI_xxx)'
required: true
branch-name:
description: 'Branch to scan (e.g., dev for applications, main for libraries)'
required: true
runs:
using: composite
steps:
- name: Aikido full scan
shell: bash
env:
AIKIDO_API_KEY: ${{ inputs.aikido-api-key }}
INPUT_BRANCH_NAME: ${{ inputs.branch-name }}
run: |
REPO_NAME="${GITHUB_REPOSITORY##*/}"
docker run --rm \
-v "$GITHUB_WORKSPACE:/repo" \
-e AIKIDO_API_KEY \
aikidosecurity/local-scanner:latest \
scan /repo \
--repositoryname "$REPO_NAME" \
--branchname "$INPUT_BRANCH_NAME"

26
aikido-pr-scan/README.md Normal file
View file

@ -0,0 +1,26 @@
# aikido-pr-scan
Run Aikido security scan on a PR in gating mode — fails if new vulnerabilities are introduced.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `aikido-api-key` | Yes | | Aikido CI API key (`AIK_CI_xxx`) |
| `fail-on` | No | `high` | Minimum severity to fail on (`low`, `medium`, `high`, `critical`) |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/aikido-pr-scan@v1
with:
aikido-api-key: ${{ secrets.AIKIDO_API_KEY }}
```
## Notes
- Uses the `aikidosecurity/local-scanner` Docker image to scan inside the CI runner.
- Runs in PR gating mode: only detects **new** vulnerabilities introduced by the PR (diff-based).
- Repository name and branch are auto-detected from Forgejo environment variables.
- The API key is passed via environment variable, never interpolated in shell commands.
- Base and head commit IDs are derived from `GITHUB_BASE_SHA` and `GITHUB_SHA`.

37
aikido-pr-scan/action.yml Normal file
View file

@ -0,0 +1,37 @@
name: aikido-pr-scan
description: Run Aikido security scan on a PR in gating mode (fails on new vulnerabilities)
inputs:
aikido-api-key:
description: 'Aikido CI API key (AIK_CI_xxx)'
required: true
fail-on:
description: 'Minimum severity to fail on (low, medium, high, critical)'
required: false
default: 'high'
runs:
using: composite
steps:
- name: Aikido PR scan
shell: bash
env:
AIKIDO_API_KEY: ${{ inputs.aikido-api-key }}
INPUT_FAIL_ON: ${{ inputs.fail-on }}
run: |
REPO_NAME="${GITHUB_REPOSITORY##*/}"
BRANCH_NAME="${GITHUB_HEAD_REF}"
BASE_COMMIT="${GITHUB_BASE_SHA:-$(git rev-parse HEAD~1)}"
HEAD_COMMIT="${GITHUB_SHA}"
docker run --rm \
-v "$GITHUB_WORKSPACE:/repo" \
-e AIKIDO_API_KEY \
aikidosecurity/local-scanner:latest \
scan /repo \
--repositoryname "$REPO_NAME" \
--branchname "$BRANCH_NAME" \
--gating-mode pr \
--fail-on "$INPUT_FAIL_ON" \
--base-commit-id "$BASE_COMMIT" \
--head-commit-id "$HEAD_COMMIT"

25
aws-configure/README.md Normal file
View file

@ -0,0 +1,25 @@
# aws-configure
Authenticate with AWS via OIDC and export credentials to the environment.
## 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 |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/aws-configure@v1
with:
role-arn: arn:aws:iam::123456789012:role/my-role
```
## Notes
- Requires `enable-openid-connect: true` on the Forgejo runner job.
- Credentials are exported via `$FORGEJO_ENV` so subsequent steps can use them.
- When `aws-profile` is not `default`, a named AWS CLI profile is also configured.

45
aws-configure/action.yml Normal file
View file

@ -0,0 +1,45 @@
name: aws-configure
description: Authenticate with AWS via OIDC
inputs:
role-arn:
description: Full IAM role ARN
required: true
aws-profile:
description: Profile name written to ~/.aws/config
required: false
default: default
region:
description: AWS region
required: false
default: eu-central-1
runs:
using: composite
steps:
- run: |
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
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
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
shell: bash

View file

@ -0,0 +1,50 @@
# aws-lambda-update
Update Lambda function alias to a new version, optionally wait for provisioned concurrency.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `function-name` | Yes | | Lambda function name |
| `function-version` | Yes | | Lambda version number |
| `alias-name` | Yes | | Alias name |
| `aws-role-arn` | Yes | | IAM role via OIDC |
| `wait-provisioned-concurrency` | No | `false` | Poll until provisioned concurrency is READY |
| `aws-profile` | No | `default` | AWS CLI profile name |
| `region` | No | `eu-central-1` | AWS region |
| `lambda-alias-updates-json` | No | `""` | JSON array of `{function_name, version, alias_name}` objects for batch updates |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/aws-lambda-update@v1
with:
function-name: my-function
function-version: "42"
alias-name: live
aws-role-arn: ${{ secrets.AWS_ROLE_ARN }}
wait-provisioned-concurrency: "true"
```
### Batch update
```yaml
- uses: schmalz/shared-actions/.github/actions/aws-lambda-update@v1
with:
function-name: unused
function-version: "0"
alias-name: unused
aws-role-arn: ${{ secrets.AWS_ROLE_ARN }}
lambda-alias-updates-json: |
[
{"function_name": "fn-a", "version": "3", "alias_name": "live"},
{"function_name": "fn-b", "version": "7", "alias_name": "live"}
]
```
## Notes
- When `lambda-alias-updates-json` is set, the single-alias inputs (`function-name`, `function-version`, `alias-name`) are ignored.
- Provisioned concurrency polling checks every 5 seconds and fails the step if status becomes `FAILED`.
- Uses `aws-configure` internally for OIDC authentication.

View file

@ -0,0 +1,95 @@
name: aws-lambda-update
description: Update Lambda function alias to a new version, optionally wait for provisioned concurrency
inputs:
function-name:
description: Lambda function name
required: true
function-version:
description: Lambda version number
required: true
alias-name:
description: Alias name
required: true
wait-provisioned-concurrency:
description: Poll until provisioned concurrency is READY
required: false
default: "false"
aws-role-arn:
description: IAM role via OIDC
required: true
aws-profile:
description: AWS CLI profile name
required: false
default: default
region:
description: AWS region
required: false
default: eu-central-1
lambda-alias-updates-json:
description: JSON array of {function_name, version, alias_name} objects; when set, applies all entries instead of single alias
required: false
default: ""
runs:
using: composite
steps:
- uses: schmalz/shared-actions/.github/actions/aws-configure@v1
with:
role-arn: ${{ inputs.aws-role-arn }}
aws-profile: ${{ inputs.aws-profile }}
region: ${{ inputs.region }}
- run: |
AWS_PROFILE="${{ inputs.aws-profile }}"
WAIT_PC="${{ inputs.wait-provisioned-concurrency }}"
update_alias() {
local fn="$1" ver="$2" alias="$3"
echo "Updating alias '$alias' on '$fn' to version $ver"
aws lambda update-alias \
--function-name "$fn" \
--name "$alias" \
--function-version "$ver" \
--profile "$AWS_PROFILE"
}
wait_provisioned_concurrency() {
local fn="$1" alias="$2"
echo "Waiting for provisioned concurrency on '$fn' alias '$alias'..."
while true; do
STATUS=$(aws lambda get-provisioned-concurrency-config \
--function-name "$fn" \
--qualifier "$alias" \
--profile "$AWS_PROFILE" \
--query 'Status' --output text 2>/dev/null || echo "NOT_FOUND")
echo " Status: $STATUS"
if [ "$STATUS" = "READY" ]; then
break
elif [ "$STATUS" = "FAILED" ]; then
echo "ERROR: Provisioned concurrency failed for '$fn' alias '$alias'"
exit 1
fi
sleep 5
done
}
UPDATES_JSON='${{ inputs.lambda-alias-updates-json }}'
if [ -n "$UPDATES_JSON" ]; then
echo "$UPDATES_JSON" | jq -c '.[]' | while read -r entry; do
FN=$(echo "$entry" | jq -r '.function_name')
VER=$(echo "$entry" | jq -r '.version')
ALIAS=$(echo "$entry" | jq -r '.alias_name')
update_alias "$FN" "$VER" "$ALIAS"
if [ "$WAIT_PC" = "true" ]; then
wait_provisioned_concurrency "$FN" "$ALIAS"
fi
done
else
update_alias "${{ inputs.function-name }}" "${{ inputs.function-version }}" "${{ inputs.alias-name }}"
if [ "$WAIT_PC" = "true" ]; then
wait_provisioned_concurrency "${{ inputs.function-name }}" "${{ inputs.alias-name }}"
fi
fi
shell: bash

31
aws-s3-sync/README.md Normal file
View file

@ -0,0 +1,31 @@
# aws-s3-sync
Sync build artifacts to S3, clean up old versioned assets, and optionally invalidate CloudFront.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `source-dir` | Yes | | Local path to sync |
| `s3-bucket` | Yes | | Target S3 bucket name |
| `aws-role-arn` | Yes | | IAM role ARN for OIDC authentication |
| `cache-control` | No | `public, max-age=31536000, immutable` | HTTP `Cache-Control` header |
| `aws-profile` | No | `default` | AWS CLI profile name |
| `cloudfront-distribution-ids-file` | No | `""` | Path to file with space-separated CloudFront distribution IDs |
| `retention-days` | No | `7` | Delete `_static/<timestamp>` prefixes older than this many days (`0` to skip) |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/aws-s3-sync@v1
with:
source-dir: dist/
s3-bucket: my-app-bucket
aws-role-arn: arn:aws:iam::123456789012:role/my-role
```
## Notes
- Requires `enable-openid-connect: true` on the job.
- Old assets under `_static/` are cleaned up based on `retention-days` by parsing timestamp-named prefixes.
- CloudFront invalidation uses `/*` path and is triggered only when `cloudfront-distribution-ids-file` points to an existing file.

71
aws-s3-sync/action.yml Normal file
View file

@ -0,0 +1,71 @@
name: aws-s3-sync
description: Sync build artifacts to S3, clean up old versioned assets, optionally invalidate CloudFront
inputs:
source-dir:
description: Local path to sync
required: true
s3-bucket:
description: Target S3 bucket name
required: true
cache-control:
description: HTTP Cache-Control header
required: false
default: "public, max-age=31536000, immutable"
aws-role-arn:
description: IAM role via OIDC
required: true
aws-profile:
description: AWS CLI profile name
required: false
default: default
cloudfront-distribution-ids-file:
description: Path to file with space-separated CF distribution IDs. If set, creates invalidations.
required: false
default: ""
retention-days:
description: Delete versioned _static/<timestamp> prefixes older than this many days (0 = skip)
required: false
default: "7"
runs:
using: composite
steps:
- uses: schmalz/shared-actions/.github/actions/aws-configure@v1
with:
role-arn: ${{ inputs.aws-role-arn }}
aws-profile: ${{ inputs.aws-profile }}
- run: |
SOURCE_DIR="${{ inputs.source-dir }}"
S3_BUCKET="${{ inputs.s3-bucket }}"
CACHE_CONTROL="${{ inputs.cache-control }}"
AWS_PROFILE="${{ inputs.aws-profile }}"
RETENTION_DAYS="${{ inputs.retention-days }}"
CF_IDS_FILE="${{ inputs.cloudfront-distribution-ids-file }}"
# Sync
aws s3 sync "$SOURCE_DIR" "s3://$S3_BUCKET" \
--cache-control "$CACHE_CONTROL" \
--profile "$AWS_PROFILE"
# Cleanup old _static/ prefixes if retention-days > 0
if [ "$RETENTION_DAYS" -gt 0 ]; then
CUTOFF=$(date -d "-${RETENTION_DAYS} days" +%s)
aws s3 ls "s3://$S3_BUCKET/_static/" --profile "$AWS_PROFILE" | while read -r line; do
PREFIX=$(echo "$line" | awk '{print $2}')
# Extract timestamp from prefix name, compare to cutoff, delete if old
TIMESTAMP=$(echo "$PREFIX" | tr -d '/')
if [ $(date -d "$TIMESTAMP" +%s 2>/dev/null || echo 0) -lt "$CUTOFF" ]; then
aws s3 rm "s3://$S3_BUCKET/_static/$PREFIX" --recursive --profile "$AWS_PROFILE"
fi
done
fi
# CloudFront invalidation
if [ -n "$CF_IDS_FILE" ] && [ -f "$CF_IDS_FILE" ]; then
for DIST_ID in $(cat "$CF_IDS_FILE"); do
aws cloudfront create-invalidation --distribution-id "$DIST_ID" --paths "/*" --profile "$AWS_PROFILE"
done
fi
shell: bash

View file

@ -0,0 +1,27 @@
# cloudfront-invalidate
Invalidate one or more CloudFront distributions.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `distribution-ids` | Yes | | Space-separated CloudFront distribution IDs, or path to a file containing them |
| `aws-role-arn` | Yes | | IAM role via OIDC |
| `paths` | No | `/*` | Invalidation paths |
| `aws-profile` | No | `default` | AWS CLI profile name |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/cloudfront-invalidate@v1
with:
distribution-ids: E1234567890ABC E0987654321XYZ
aws-role-arn: ${{ secrets.AWS_ROLE_ARN }}
```
## Notes
- `distribution-ids` can be literal IDs or a path to a file containing them (one per line or space-separated).
- Each distribution is invalidated separately in a loop.
- Uses `aws-configure` internally for OIDC authentication.

View file

@ -0,0 +1,46 @@
name: cloudfront-invalidate
description: Invalidate one or more CloudFront distributions
inputs:
distribution-ids:
description: Space-separated CloudFront distribution IDs, or path to file containing them
required: true
paths:
description: Invalidation paths
required: false
default: "/*"
aws-role-arn:
description: IAM role via OIDC
required: true
aws-profile:
description: AWS CLI profile name
required: false
default: default
runs:
using: composite
steps:
- uses: schmalz/shared-actions/.github/actions/aws-configure@v1
with:
role-arn: ${{ inputs.aws-role-arn }}
aws-profile: ${{ inputs.aws-profile }}
- run: |
DISTRIBUTION_IDS="${{ inputs.distribution-ids }}"
PATHS="${{ inputs.paths }}"
AWS_PROFILE="${{ inputs.aws-profile }}"
# Check if distribution-ids is a file path or literal IDs
if [ -f "$DISTRIBUTION_IDS" ]; then
IDS=$(cat "$DISTRIBUTION_IDS")
else
IDS="$DISTRIBUTION_IDS"
fi
for DIST_ID in $IDS; do
aws cloudfront create-invalidation \
--distribution-id "$DIST_ID" \
--paths "$PATHS" \
--profile "$AWS_PROFILE"
done
shell: bash

View file

@ -0,0 +1,30 @@
# docker-build-push
Build Docker image and push to JFrog with semver tags.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `image-name` | Yes | | Image name (e.g., `default-docker/db-devcontainer-base`) |
| `version-tag` | Yes | | Full semver tag (e.g., `1.2.3`) |
| `jfrog-token` | Yes | | JFrog registry password/token |
| `registry` | No | `schmalz.jfrog.io` | Docker registry |
| `dockerfile` | No | `Dockerfile` | Path to Dockerfile |
| `context` | No | `.` | Docker build context |
| `jfrog-user` | No | `jfrog-cicd-user` | JFrog registry user |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/docker-build-push@v1
with:
image-name: default-docker/my-service
version-tag: 1.2.3
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- Pushes three tags per build: full (`1.2.3`), minor (`1.2`), and major (`1`).
- Consumers using the major or minor tag always get the latest patch release.

View file

@ -0,0 +1,46 @@
name: docker-build-push
description: Build Docker image and push to JFrog with semver tags (major, minor, patch)
inputs:
image-name:
description: 'Image name (e.g., default-docker/db-devcontainer-base)'
required: true
registry:
description: 'Docker registry'
required: false
default: 'schmalz.jfrog.io'
dockerfile:
description: 'Path to Dockerfile'
required: false
default: 'Dockerfile'
context:
description: 'Docker build context'
required: false
default: '.'
version-tag:
description: 'Full semver tag (e.g., 1.2.3)'
required: true
jfrog-token:
description: 'JFrog registry password/token'
required: true
jfrog-user:
description: 'JFrog registry user'
required: false
default: 'jfrog-cicd-user'
runs:
using: composite
steps:
- name: Build and push Docker image
shell: bash
run: |
echo "${{ inputs.jfrog-token }}" | docker login "${{ inputs.registry }}" -u "${{ inputs.jfrog-user }}" --password-stdin
FULL="${{ inputs.registry }}/${{ inputs.image-name }}:${{ inputs.version-tag }}"
MINOR="${{ inputs.registry }}/${{ inputs.image-name }}:$(echo "${{ inputs.version-tag }}" | cut -d. -f1,2)"
MAJOR="${{ inputs.registry }}/${{ inputs.image-name }}:$(echo "${{ inputs.version-tag }}" | cut -d. -f1)"
docker build -f "${{ inputs.dockerfile }}" -t "$FULL" -t "$MINOR" -t "$MAJOR" "${{ inputs.context }}"
docker push "$FULL"
docker push "$MINOR"
docker push "$MAJOR"

33
helm-deploy/README.md Normal file
View file

@ -0,0 +1,33 @@
# 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 (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-su.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 repo |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/helm-deploy@v1
with:
service-name: my-service
helm-host: dsp1-stage.schmalzgroup.net
image-tag: ${{ github.sha }}
ssh-key: ${{ secrets.SSH_KEY }}
```
## Notes
- The override file is `scp`-ed to the remote host, then `helm upgrade --install` is run via SSH.
- Uses `--atomic` flag, so a failed deploy is automatically rolled back.
- `StrictHostKeyChecking` is disabled.

55
helm-deploy/action.yml Normal file
View file

@ -0,0 +1,55 @@
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-su.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:
- shell: bash
run: |
SSH_KEY_FILE=$(mktemp)
trap "rm -f '$SSH_KEY_FILE'" EXIT
echo "${{ inputs.ssh-key }}" > "$SSH_KEY_FILE"
chmod 600 "$SSH_KEY_FILE"
SSH_OPTS="-i $SSH_KEY_FILE -o StrictHostKeyChecking=no"
REMOTE="root@${{ inputs.helm-host }}"
SERVICE="${{ inputs.service-name }}"
scp $SSH_OPTS "${{ inputs.overrides-file }}" "$REMOTE:/tmp/${SERVICE}-overrides.yaml"
ssh $SSH_OPTS "$REMOTE" \
"helm repo update && \
helm upgrade --install --create-namespace -n ${{ inputs.namespace }} $SERVICE \
${{ inputs.helm-repo }}/${{ inputs.helm-chart }} -f /tmp/${SERVICE}-overrides.yaml \
--set image.tag=${{ inputs.image-tag }} --atomic --debug"

32
maven-build/README.md Normal file
View file

@ -0,0 +1,32 @@
# maven-build
Run a Maven build -- verify-only for PRs or package+jib push for deploys.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `maven-settings` | Yes | | Secret containing `settings.xml` content |
| `java-version` | No | `8` | Java version |
| `maven-image` | No | `maven:3.8.5-openjdk-8` | Maven Docker image |
| `phase` | No | `verify` | Build phase: `verify` or `package-and-push` |
| `verify-goals` | No | `spotless:check checkstyle:check compile` | Goals for the verify phase |
| `maven-profile` | No | `test` | Maven profile used during `package-and-push` |
| `service-dir` | No | `.` | Working directory containing the POM |
| `skip-tests` | No | `false` | Pass `-DskipTests` |
| `image-tag` | No | `${{ github.sha }}-${{ github.run_id }}` | Docker image tag for jib |
| `extra-args` | No | | Additional Maven CLI arguments |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/maven-build@v1
with:
maven-settings: ${{ secrets.MAVEN_SETTINGS }}
phase: package-and-push
```
## Notes
- Maven repository cache is restored/saved automatically using `pom.xml` hash.
- The `settings.xml` is written to a temp file and cleaned up after the build.

74
maven-build/action.yml Normal file
View file

@ -0,0 +1,74 @@
name: maven-build
description: Run Maven build — verify-only (PRs) or package+jib push (deploy)
inputs:
phase:
required: false
default: 'verify'
verify-goals:
required: false
default: 'spotless:check checkstyle:check compile'
maven-profile:
required: false
default: 'test'
service-dir:
required: false
default: '.'
skip-tests:
required: false
default: 'false'
image-tag:
required: false
default: '${{ github.sha }}-${{ github.run_id }}'
maven-settings:
required: true
description: Secret containing settings.xml content
extra-args:
required: false
default: ''
runs:
using: composite
steps:
- name: Restore Maven cache
uses: https://data.forgejo.org/actions/cache/restore@v4
with:
path: ~/.m2/repository
key: maven-${{ hashFiles('**/pom.xml') }}
- name: Maven build
shell: bash
working-directory: ${{ inputs.service-dir }}
run: |
SETTINGS_FILE=$(mktemp)
echo "${{ inputs.maven-settings }}" > "$SETTINGS_FILE"
trap 'rm -f "$SETTINGS_FILE"' EXIT
SKIP_TESTS_FLAG=""
if [ "${{ inputs.skip-tests }}" = "true" ]; then
SKIP_TESTS_FLAG="-DskipTests"
fi
if [ "${{ inputs.phase }}" = "verify" ]; then
mvn ${{ inputs.verify-goals }} \
-s "$SETTINGS_FILE" \
$SKIP_TESTS_FLAG \
${{ inputs.extra-args }}
elif [ "${{ inputs.phase }}" = "package-and-push" ]; then
mvn clean package jib:build \
-DsendCredentialsOverHttp=true \
-Djib.to.tags=${{ inputs.image-tag }} \
-P ${{ inputs.maven-profile }} \
-s "$SETTINGS_FILE" \
$SKIP_TESTS_FLAG \
${{ inputs.extra-args }}
else
echo "Unknown phase: ${{ inputs.phase }}"
exit 1
fi
- name: Save Maven cache
uses: https://data.forgejo.org/actions/cache/save@v4
with:
path: ~/.m2/repository
key: maven-${{ hashFiles('**/pom.xml') }}

38
playwright-e2e/README.md Normal file
View file

@ -0,0 +1,38 @@
# playwright-e2e
Run Playwright E2E tests with optional sharding, upload results to S3.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `jfrog-token` | Yes | | JFrog npm auth token |
| `s3-reports-bucket` | Yes | | S3 bucket for report upload |
| `s3-reports-prefix` | Yes | | S3 path prefix |
| `aws-role-arn` | Yes | | IAM role ARN for OIDC authentication |
| `working-directory` | No | `e2e` | Directory containing Playwright config |
| `playwright-version` | No | `v1.58.2` | Playwright version tag for browser cache key |
| `pnpm-version` | No | `10.11` | pnpm version |
| `shard-index` | No | `1` | Current shard (1-based) |
| `shard-total` | No | `1` | Total shards. 1 = no sharding |
| `aws-profile` | No | `stage` | AWS CLI profile name |
| `extra-deps` | No | `""` | Space-separated apt packages to install |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/playwright-e2e@v1
with:
jfrog-token: ${{ secrets.JFROG_TOKEN }}
s3-reports-bucket: my-reports-bucket
s3-reports-prefix: pr-${{ github.event.number }}
aws-role-arn: ${{ secrets.AWS_ROLE_ARN }}
shard-index: "1"
shard-total: "4"
```
## Notes
- Playwright browsers are cached between runs using the `playwright-version` input as cache key.
- Reports are uploaded to `s3://<bucket>/<prefix>/shard-<index>/`.
- Uses JFrog Artifactory as the npm registry for dependency installation.

109
playwright-e2e/action.yml Normal file
View file

@ -0,0 +1,109 @@
name: playwright-e2e
description: Run Playwright E2E tests with optional sharding, upload results to S3.
inputs:
working-directory:
description: Directory containing Playwright config
required: false
default: e2e
playwright-version:
description: Playwright version tag (browser cache key only — actual version comes from pnpm-lock.yaml)
required: false
default: v1.58.2
jfrog-token:
description: JFrog npm auth token
required: true
pnpm-version:
description: pnpm version
required: false
default: "10.11"
node-version:
description: Node.js version
required: false
default: "22"
shard-index:
description: Current shard (1-based)
required: false
default: "1"
shard-total:
description: Total shards. 1 = no sharding.
required: false
default: "1"
s3-reports-bucket:
description: S3 bucket for report upload
required: true
s3-reports-prefix:
description: S3 path prefix
required: true
aws-role-arn:
description: IAM role ARN for OIDC authentication
required: true
aws-profile:
description: AWS CLI profile name
required: false
default: stage
extra-deps:
description: Space-separated apt packages to install
required: false
default: ""
runs:
using: composite
steps:
- name: Configure AWS
uses: schmalz/shared-actions/.github/actions/aws-configure@v1
with:
role-arn: ${{ inputs.aws-role-arn }}
aws-profile: ${{ inputs.aws-profile }}
- name: Restore Playwright browser cache
uses: https://data.forgejo.org/actions/cache/restore@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ inputs.playwright-version }}
- name: Setup Node.js
uses: https://code.forgejo.org/actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Run Playwright tests and upload results
shell: bash
env:
EXTRA_DEPS: ${{ inputs.extra-deps }}
PNPM_VERSION: ${{ inputs.pnpm-version }}
JFROG_TOKEN: ${{ inputs.jfrog-token }}
WORKING_DIR: ${{ inputs.working-directory }}
SHARD_INDEX: ${{ inputs.shard-index }}
SHARD_TOTAL: ${{ inputs.shard-total }}
BUCKET: ${{ inputs.s3-reports-bucket }}
PREFIX: ${{ inputs.s3-reports-prefix }}
AWS_PROFILE: ${{ inputs.aws-profile }}
run: |
if [ -n "${EXTRA_DEPS}" ]; then
sudo apt-get update -qq
sudo apt-get install -y ${EXTRA_DEPS}
fi
npm i -g "pnpm@${PNPM_VERSION}"
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}"
pnpm --prefix="${WORKING_DIR}" install --frozen-lockfile
pnpm --prefix="${WORKING_DIR}" exec playwright install --with-deps
SHARD_ARG=""
if [ "${SHARD_TOTAL}" != "1" ]; then
SHARD_ARG="--shard=${SHARD_INDEX}/${SHARD_TOTAL}"
fi
pnpm --prefix="${WORKING_DIR}" exec playwright test ${SHARD_ARG}
aws s3 sync "${WORKING_DIR}/playwright-report/" \
"s3://${BUCKET}/${PREFIX}/shard-${SHARD_INDEX}/" \
--profile "${AWS_PROFILE}"
- name: Save Playwright browser cache
uses: https://data.forgejo.org/actions/cache/save@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ inputs.playwright-version }}

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

@ -0,0 +1,28 @@
# pnpm-build
Set up pnpm, authenticate with JFrog npm registry, install dependencies, and run build scripts.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `jfrog-token` | Yes | | JFrog npm auth token |
| `working-directory` | No | `.` | Directory containing `package.json` |
| `pnpm-version` | No | `10.14` | pnpm version |
| `node-version` | No | `22` | Node.js version |
| `run-scripts` | No | `ci,typecheck,build` | Comma-separated list of `pnpm run` scripts |
| `frozen-lockfile` | No | `true` | Pass `--frozen-lockfile` to `pnpm install` |
| `check-dedupe` | No | `true` | Run `pnpm dedupe --check` |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/pnpm-build@v1
with:
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- pnpm store cache is restored/saved automatically using `pnpm-lock.yaml` hash.
- Registry is set to `schmalz.jfrog.io`.

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

@ -0,0 +1,80 @@
name: pnpm-build
description: Set up pnpm, authenticate JFrog npm registry, install deps, run scripts.
inputs:
working-directory:
description: Directory containing package.json
required: false
default: "."
pnpm-version:
description: pnpm version
required: false
default: "10.14"
jfrog-token:
description: JFrog npm auth token
required: true
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"
node-version:
description: Node.js version
required: false
default: "22"
runs:
using: composite
steps:
- name: Restore pnpm cache
uses: https://data.forgejo.org/actions/cache/restore@v4
with:
path: ~/.local/share/pnpm/store/v3
key: pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Setup Node.js
uses: https://code.forgejo.org/actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Setup and build
shell: bash
env:
PNPM_VERSION: ${{ inputs.pnpm-version }}
JFROG_TOKEN: ${{ inputs.jfrog-token }}
WORKING_DIR: ${{ inputs.working-directory }}
RUN_SCRIPTS: ${{ inputs.run-scripts }}
FROZEN_LOCKFILE: ${{ inputs.frozen-lockfile }}
CHECK_DEDUPE: ${{ inputs.check-dedupe }}
run: |
npm i -g "pnpm@${PNPM_VERSION}"
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}"
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
- name: Save pnpm cache
uses: https://data.forgejo.org/actions/cache/save@v4
with:
path: ~/.local/share/pnpm/store/v3
key: pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}

View file

@ -0,0 +1,26 @@
# publish-npm-package
Build and publish npm package to JFrog Artifactory.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `jfrog-token` | Yes | | JFrog npm auth token |
| `working-directory` | No | `.` | Directory containing package.json |
| `pnpm-version` | No | `10.14` | pnpm version |
| `registry-url` | No | `https://schmalz.jfrog.io/artifactory/api/npm/default-npm/` | JFrog npm registry URL |
| `build-script` | No | `build` | pnpm build script name |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/publish-npm-package@v1
with:
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- Runs `pnpm install --frozen-lockfile`, then the build script, then `pnpm publish --no-git-checks`.
- The version published is whatever is in `package.json` -- bump it before calling this action.

View file

@ -0,0 +1,51 @@
name: publish-npm-package
description: Build and publish npm package to JFrog Artifactory.
inputs:
working-directory:
description: Directory containing package.json
required: false
default: "."
pnpm-version:
description: pnpm version
required: false
default: "10.14"
node-version:
description: Node.js version
required: false
default: "22"
jfrog-token:
description: JFrog npm auth token
required: true
registry-url:
description: JFrog npm registry URL
required: false
default: "https://schmalz.jfrog.io/artifactory/api/npm/default-npm/"
build-script:
description: pnpm build script name
required: false
default: "build"
runs:
using: composite
steps:
- name: Setup Node.js
uses: https://code.forgejo.org/actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Build and publish
shell: bash
env:
PNPM_VERSION: ${{ inputs.pnpm-version }}
JFROG_TOKEN: ${{ inputs.jfrog-token }}
REGISTRY_URL: ${{ inputs.registry-url }}
WORKING_DIR: ${{ inputs.working-directory }}
BUILD_SCRIPT: ${{ inputs.build-script }}
run: |
npm i -g "pnpm@${PNPM_VERSION}"
pnpm set registry "${REGISTRY_URL}"
pnpm set "//schmalz.jfrog.io/artifactory/api/npm/default-npm/:_authToken=${JFROG_TOKEN}"
pnpm --prefix="${WORKING_DIR}" install --frozen-lockfile
pnpm --prefix="${WORKING_DIR}" run "${BUILD_SCRIPT}"
pnpm --prefix="${WORKING_DIR}" publish --no-git-checks

View file

@ -0,0 +1,26 @@
# publish-rust-crate
Build, test, and publish Rust crate to JFrog Cargo registry.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `jfrog-token` | Yes | | JFrog access token for Cargo registry auth |
| `working-directory` | No | `.` | Directory containing Cargo.toml |
| `rust-version` | No | `1.87` | Rust toolchain version |
| `cargo-registry-name` | No | `jfrog` | Cargo registry name |
| `run-tests` | No | `true` | Run `cargo test` before publishing |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/publish-rust-crate@v1
with:
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- The registry token is set via `CARGO_REGISTRIES_<NAME>_TOKEN` environment variable (registry name is uppercased).
- The crate version published is whatever is in `Cargo.toml` -- bump it before calling this action.

View file

@ -0,0 +1,42 @@
name: publish-rust-crate
description: Build, test, and publish Rust crate to JFrog Cargo registry.
inputs:
working-directory:
description: Directory containing Cargo.toml
required: false
default: "."
rust-version:
description: Rust toolchain version
required: false
default: "1.87"
cargo-registry-name:
description: Cargo registry name
required: false
default: "jfrog"
jfrog-token:
description: JFrog access token for Cargo registry auth
required: true
run-tests:
description: Run cargo test before publishing
required: false
default: "true"
runs:
using: composite
steps:
- name: Test and publish
shell: bash
env:
RUST_VERSION: ${{ inputs.rust-version }}
REGISTRY_NAME: ${{ inputs.cargo-registry-name }}
JFROG_TOKEN: ${{ inputs.jfrog-token }}
WORKING_DIR: ${{ inputs.working-directory }}
RUN_TESTS: ${{ inputs.run-tests }}
run: |
rustup default "${RUST_VERSION}"
UPPER_NAME="$(echo "${REGISTRY_NAME}" | tr '[:lower:]' '[:upper:]')"
export "CARGO_REGISTRIES_${UPPER_NAME}_TOKEN=Bearer ${JFROG_TOKEN}"
cd "${WORKING_DIR}"
if [ "${RUN_TESTS}" = "true" ]; then cargo test; fi
cargo publish --registry "${REGISTRY_NAME}"

42
rust-build/README.md Normal file
View file

@ -0,0 +1,42 @@
# rust-build
Run Rust CI -- fmt, clippy, tests, optional cross-compilation.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `toolchain` | No | `stable` | Rust toolchain |
| `features` | No | `""` | Cargo features to enable |
| `cross-target` | No | `""` | Cross-compilation target (installs `cross` when set) |
| `run-fmt` | No | `true` | Run `cargo fmt --check` |
| `run-clippy` | No | `true` | Run clippy with `-D warnings` |
| `run-tests` | No | `true` | Run `cargo test` |
| `run-deny` | No | `false` | Run `cargo deny check` |
| `run-semver-checks` | No | `false` | Run `cargo semver-checks` |
| `sccache-enabled` | No | `true` | Enable sccache (requires `sccache_ci_setup.sh` in working directory) |
| `working-directory` | No | `.` | Working directory |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/rust-build@v1
with:
features: serde,tokio
run-deny: "true"
```
### Cross-compilation
```yaml
- uses: schmalz/shared-actions/.github/actions/rust-build@v1
with:
cross-target: aarch64-unknown-linux-gnu
run-fmt: "false"
```
## Notes
- Cargo registry and `target/` directory are cached based on `Cargo.lock` hash.
- When `cross-target` is set, `cross` is installed and used instead of `cargo` for clippy, test, and build steps. `cargo fmt` always uses `cargo` directly.
- sccache integration requires a `sccache_ci_setup.sh` script in the working directory.

101
rust-build/action.yml Normal file
View file

@ -0,0 +1,101 @@
name: rust-build
description: Run Rust CI — fmt, clippy, tests, optional cross-compilation.
inputs:
toolchain:
required: false
default: "stable"
features:
required: false
default: ""
cross-target:
required: false
default: ""
run-fmt:
required: false
default: "true"
run-clippy:
required: false
default: "true"
run-tests:
required: false
default: "true"
run-deny:
required: false
default: "false"
run-semver-checks:
required: false
default: "false"
sccache-enabled:
required: false
default: "true"
working-directory:
required: false
default: "."
runs:
using: composite
steps:
- name: Restore cargo cache
uses: https://data.forgejo.org/actions/cache/restore@v4
with:
path: |
~/.cargo/registry
target/
key: cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Rust CI
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
set -euo pipefail
rustup default ${{ inputs.toolchain }}
if [[ "${{ inputs.sccache-enabled }}" == "true" && -f sccache_ci_setup.sh ]]; then
source sccache_ci_setup.sh
fi
CMD=cargo
if [[ -n "${{ inputs.cross-target }}" ]]; then
cargo install cross
CMD=cross
fi
FEATURES_ARG=""
if [[ -n "${{ inputs.features }}" ]]; then
FEATURES_ARG="--features ${{ inputs.features }}"
fi
TARGET_ARG=""
if [[ -n "${{ inputs.cross-target }}" ]]; then
TARGET_ARG="--target ${{ inputs.cross-target }}"
fi
if [[ "${{ inputs.run-fmt }}" == "true" ]]; then
cargo fmt --check
fi
if [[ "${{ inputs.run-clippy }}" == "true" ]]; then
$CMD clippy $FEATURES_ARG $TARGET_ARG -- -D warnings
fi
if [[ "${{ inputs.run-tests }}" == "true" ]]; then
$CMD test $FEATURES_ARG $TARGET_ARG
fi
if [[ "${{ inputs.run-deny }}" == "true" ]]; then
cargo deny check
fi
if [[ "${{ inputs.run-semver-checks }}" == "true" ]]; then
cargo semver-checks
fi
- name: Save cargo cache
uses: https://data.forgejo.org/actions/cache/save@v4
with:
path: |
~/.cargo/registry
target/
key: cargo-${{ hashFiles('**/Cargo.lock') }}

23
secrets-inject/README.md Normal file
View file

@ -0,0 +1,23 @@
# secrets-inject
Append secret properties content to a Java `.properties` file.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `secret-content` | Yes | | Properties content to append |
| `target-file` | Yes | | Path to the `.properties` file |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/secrets-inject@v1
with:
secret-content: ${{ secrets.APP_SECRETS }}
target-file: src/main/resources/application.properties
```
## Notes
- A blank line is inserted before the appended content to avoid merging with the last existing line.

18
secrets-inject/action.yml Normal file
View file

@ -0,0 +1,18 @@
name: secrets-inject
description: Append a secrets file to a Java .properties file
inputs:
secret-content:
description: Secret value (properties content to append)
required: true
target-file:
description: Path to the .properties file
required: true
runs:
using: composite
steps:
- run: |
echo "" >> ${{ inputs.target-file }}
echo "${{ inputs.secret-content }}" >> ${{ inputs.target-file }}
shell: bash

38
terraform-apply/README.md Normal file
View file

@ -0,0 +1,38 @@
# terraform-apply
Full Terraform init, workspace select, plan/apply, and output capture.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `var-file` | Yes | | Path to `.tfvars` file |
| `workspace` | Yes | | Terraform workspace (`stage` or `prod`) |
| `aws-role-arn` | Yes | | IAM role ARN for OIDC authentication |
| `jfrog-token` | Yes | | JFrog access token (sets `TF_TOKEN_schmalz_jfrog_io`) |
| `terraform-dir` | No | `terraform` | Directory containing Terraform configuration |
| `terraform-version` | No | `1.11` | Terraform version to install |
| `aws-profile` | No | `default` | AWS CLI profile name |
| `output-names` | No | `""` | Comma-separated Terraform output names to capture as raw values |
| `output-json-names` | No | `""` | Comma-separated output names to capture as JSON |
| `plan-only` | No | `false` | Run `plan -out` instead of `apply` |
| `plan-file` | No | `""` | Pre-existing plan file to apply |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/terraform-apply@v1
with:
var-file: envs/stage.tfvars
workspace: stage
aws-role-arn: arn:aws:iam::123456789012:role/my-role
jfrog-token: ${{ secrets.JFROG_TOKEN }}
output-names: api_url,db_host
```
## Notes
- Requires `enable-openid-connect: true` on the job.
- Captured outputs are written to `$FORGEJO_OUTPUT` and to files under `<terraform-dir>/.outputs/`.
- Provider cache is restored/saved automatically.
- Use `plan-only: true` for a plan-then-apply workflow across jobs.

118
terraform-apply/action.yml Normal file
View file

@ -0,0 +1,118 @@
name: terraform-apply
description: Full Terraform init + workspace + apply + output capture
inputs:
terraform-dir:
description: Directory containing Terraform configuration
required: false
default: terraform
terraform-version:
description: Terraform version to install
required: false
default: "1.11"
var-file:
description: Path to .tfvars file
required: true
workspace:
description: Terraform workspace (stage or prod)
required: true
aws-role-arn:
description: IAM role ARN for OIDC authentication
required: true
aws-profile:
description: AWS profile name
required: false
default: default
jfrog-token:
description: JFrog access token (sets TF_TOKEN_schmalz_jfrog_io)
required: true
output-names:
description: Comma-separated list of terraform output names to capture as raw
required: false
default: ""
plan-only:
description: Run plan -out instead of apply
required: false
default: "false"
plan-file:
description: Pre-existing plan file to apply
required: false
default: ""
output-json-names:
description: Comma-separated output names to capture as JSON
required: false
default: ""
runs:
using: composite
steps:
- name: Configure AWS credentials
uses: schmalz/shared-actions/.github/actions/aws-configure@v1
with:
role-arn: ${{ inputs.aws-role-arn }}
aws-profile: ${{ inputs.aws-profile }}
- name: Restore Terraform provider cache
uses: https://data.forgejo.org/actions/cache/restore@v4
with:
path: ${{ inputs.terraform-dir }}/.terraform/providers
key: terraform-providers-${{ hashFiles(format('{0}/.terraform.lock.hcl', inputs.terraform-dir)) }}
- name: Install Terraform
shell: bash
run: |
TF_VERSION="${{ inputs.terraform-version }}"
curl -fsSL "https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip" -o /tmp/terraform.zip
sudo unzip -o /tmp/terraform.zip -d /usr/local/bin
rm /tmp/terraform.zip
terraform version
- name: Terraform init, plan/apply, and capture outputs
shell: bash
env:
TF_TOKEN_schmalz_jfrog_io: ${{ inputs.jfrog-token }}
TF_WORKSPACE: ${{ inputs.workspace }}
AWS_PROFILE: ${{ inputs.aws-profile }}
TF_DIR: ${{ inputs.terraform-dir }}
VAR_FILE: ${{ inputs.var-file }}
PLAN_ONLY: ${{ inputs.plan-only }}
PLAN_FILE: ${{ inputs.plan-file }}
OUTPUT_NAMES: ${{ inputs.output-names }}
OUTPUT_JSON_NAMES: ${{ inputs.output-json-names }}
run: |
terraform -chdir="$TF_DIR" init -no-color
if [ "$PLAN_ONLY" = "true" ]; then
terraform -chdir="$TF_DIR" plan -var-file="$VAR_FILE" -out=terraform.plan -no-color
elif [ -n "$PLAN_FILE" ]; then
terraform -chdir="$TF_DIR" apply -auto-approve -no-color "$PLAN_FILE"
else
terraform -chdir="$TF_DIR" apply -var-file="$VAR_FILE" -auto-approve -no-color
fi
mkdir -p "$TF_DIR/.outputs"
if [ -n "$OUTPUT_NAMES" ]; then
IFS=',' read -ra NAMES <<< "$OUTPUT_NAMES"
for name in "${NAMES[@]}"; do
name=$(echo "$name" | xargs)
val=$(terraform -chdir="$TF_DIR" output --raw "$name")
echo "$val" > "$TF_DIR/.outputs/$name"
echo "$name=$val" >> "$FORGEJO_OUTPUT"
done
fi
if [ -n "$OUTPUT_JSON_NAMES" ]; then
IFS=',' read -ra JNAMES <<< "$OUTPUT_JSON_NAMES"
for name in "${JNAMES[@]}"; do
name=$(echo "$name" | xargs)
terraform -chdir="$TF_DIR" output --json "$name" > "$TF_DIR/.outputs/$name.json"
echo "$name=$(terraform -chdir="$TF_DIR" output --json "$name")" >> "$FORGEJO_OUTPUT"
done
fi
- name: Save Terraform provider cache
uses: https://data.forgejo.org/actions/cache/save@v4
with:
path: ${{ inputs.terraform-dir }}/.terraform/providers
key: terraform-providers-${{ hashFiles(format('{0}/.terraform.lock.hcl', inputs.terraform-dir)) }}

View file

@ -0,0 +1,29 @@
# terraform-module-publish
Zip a Terraform module and publish to JFrog Artifactory.
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `module-dir` | Yes | | Path to the module directory to zip |
| `module-name` | Yes | | Module name (used in zip filename and upload path) |
| `version` | Yes | | Version string (from git tag) |
| `jfrog-token` | Yes | | JFrog access token |
| `jfrog-url` | No | `https://schmalz.jfrog.io/artifactory/terraform/schmalz` | JFrog Artifactory base URL |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/terraform-module-publish@v1
with:
module-dir: modules/vpc
module-name: vpc
version: ${{ github.ref_name }}
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- The module directory contents are zipped and uploaded to `<jfrog-url>/<module-name>/<version>.zip`.
- Uses `curl` with bearer token authentication.

View file

@ -0,0 +1,32 @@
name: terraform-module-publish
description: Zip a Terraform module and publish to JFrog Artifactory
inputs:
module-dir:
description: Path to the module directory to zip
required: true
module-name:
description: Module name (used in zip filename and upload path)
required: true
version:
description: Version string (from git tag)
required: true
jfrog-url:
description: JFrog Artifactory base URL
required: false
default: https://schmalz.jfrog.io/artifactory/terraform/schmalz
jfrog-token:
description: JFrog access token
required: true
runs:
using: composite
steps:
- name: Zip and publish module
shell: bash
run: |
cd "${{ inputs.module-dir }}"
zip -r "/tmp/${{ inputs.module-name }}-${{ inputs.version }}.zip" .
curl -sf -H "Authorization: Bearer ${{ inputs.jfrog-token }}" \
-T "/tmp/${{ inputs.module-name }}-${{ inputs.version }}.zip" \
"${{ inputs.jfrog-url }}/${{ inputs.module-name }}/${{ inputs.version }}.zip"

View file

@ -0,0 +1,28 @@
# terraform-validate
Validate Terraform code without a backend (for PR checks).
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `jfrog-token` | Yes | | Sets `TF_TOKEN_schmalz_jfrog_io` for provider auth |
| `terraform-dir` | No | `terraform` | Directory containing `.tf` files |
| `terraform-version` | No | `1.11` | Terraform version to use |
| `aws-role-arn` | No | `""` | If provided, runs `aws-configure` before validate |
| `aws-profile` | No | `stage` | AWS profile when `aws-role-arn` is given |
## Usage
```yaml
- uses: schmalz/shared-actions/.github/actions/terraform-validate@v1
with:
jfrog-token: ${{ secrets.JFROG_TOKEN }}
```
## Notes
- Runs `terraform init -backend=false`, `terraform fmt -check -recursive`, and `terraform validate`.
- Sets `TF_WORKSPACE=stage` during init.
- Provider cache is restored/saved automatically.
- Optionally configures AWS credentials if `aws-role-arn` is provided (requires `enable-openid-connect: true`).

View file

@ -0,0 +1,59 @@
name: terraform-validate
description: Validate Terraform code without backend (for PR checks)
inputs:
terraform-dir:
description: Directory containing .tf files
required: false
default: terraform
terraform-version:
description: Terraform version to use
required: false
default: "1.11"
jfrog-token:
description: Sets TF_TOKEN_schmalz_jfrog_io
required: true
aws-role-arn:
description: If provided, runs aws-configure before validate
required: false
default: ""
aws-profile:
description: Profile if aws-role-arn given
required: false
default: stage
runs:
using: composite
steps:
- uses: schmalz/shared-actions/.github/actions/aws-configure@v1
if: ${{ inputs.aws-role-arn != '' }}
with:
role-arn: ${{ inputs.aws-role-arn }}
aws-profile: ${{ inputs.aws-profile }}
- uses: https://data.forgejo.org/actions/cache/restore@v4
with:
key: terraform-${{ hashFiles('**/.terraform.lock.hcl') }}
path: ${{ inputs.terraform-dir }}/.terraform/providers
- name: Install Terraform
shell: bash
run: |
TF_VERSION="${{ inputs.terraform-version }}"
curl -fsSL "https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip" -o /tmp/terraform.zip
sudo unzip -o /tmp/terraform.zip -d /usr/local/bin
rm /tmp/terraform.zip
terraform version
- run: |
export TF_TOKEN_schmalz_jfrog_io="${INPUT_JFROG_TOKEN}"
export TF_WORKSPACE=stage
terraform -chdir="${INPUT_TERRAFORM_DIR}" init -backend=false -no-color
terraform -chdir="${INPUT_TERRAFORM_DIR}" fmt -check -recursive
terraform -chdir="${INPUT_TERRAFORM_DIR}" validate
shell: bash
- uses: https://data.forgejo.org/actions/cache/save@v4
with:
key: terraform-${{ hashFiles('**/.terraform.lock.hcl') }}
path: ${{ inputs.terraform-dir }}/.terraform/providers