From a09a422251422e8fbe93115283181717dcd24cdf Mon Sep 17 00:00:00 2001 From: Michael Seele Date: Tue, 21 Apr 2026 12:20:39 +0200 Subject: [PATCH] wip: inital set of shared actions --- .github/actions/aikido-full-scan/README.md | 44 +++++++ .github/actions/aikido-full-scan/action.yml | 29 +++++ .github/actions/aikido-pr-scan/README.md | 26 ++++ .github/actions/aikido-pr-scan/action.yml | 37 ++++++ .github/actions/aws-configure/README.md | 25 ++++ .github/actions/aws-configure/action.yml | 45 +++++++ .github/actions/aws-lambda-update/README.md | 50 ++++++++ .github/actions/aws-lambda-update/action.yml | 95 ++++++++++++++ .github/actions/aws-s3-sync/README.md | 31 +++++ .github/actions/aws-s3-sync/action.yml | 71 +++++++++++ .../actions/cloudfront-invalidate/README.md | 27 ++++ .../actions/cloudfront-invalidate/action.yml | 46 +++++++ .github/actions/docker-build-push/README.md | 30 +++++ .github/actions/docker-build-push/action.yml | 46 +++++++ .github/actions/helm-deploy/README.md | 33 +++++ .github/actions/helm-deploy/action.yml | 55 ++++++++ .github/actions/maven-build/README.md | 32 +++++ .github/actions/maven-build/action.yml | 74 +++++++++++ .github/actions/playwright-e2e/README.md | 38 ++++++ .github/actions/playwright-e2e/action.yml | 109 ++++++++++++++++ .github/actions/pnpm-build/README.md | 28 +++++ .github/actions/pnpm-build/action.yml | 80 ++++++++++++ .github/actions/publish-npm-package/README.md | 26 ++++ .../actions/publish-npm-package/action.yml | 51 ++++++++ .github/actions/publish-rust-crate/README.md | 26 ++++ .github/actions/publish-rust-crate/action.yml | 42 +++++++ .github/actions/rust-build/README.md | 42 +++++++ .github/actions/rust-build/action.yml | 101 +++++++++++++++ .github/actions/secrets-inject/README.md | 23 ++++ .github/actions/secrets-inject/action.yml | 18 +++ .github/actions/terraform-apply/README.md | 38 ++++++ .github/actions/terraform-apply/action.yml | 118 ++++++++++++++++++ .../terraform-module-publish/README.md | 29 +++++ .../terraform-module-publish/action.yml | 32 +++++ .github/actions/terraform-validate/README.md | 28 +++++ .github/actions/terraform-validate/action.yml | 59 +++++++++ .github/workflows/validate-shared-actions.yml | 73 +++++++++++ .gitignore | 50 -------- README.md | 19 ++- 39 files changed, 1775 insertions(+), 51 deletions(-) create mode 100644 .github/actions/aikido-full-scan/README.md create mode 100644 .github/actions/aikido-full-scan/action.yml create mode 100644 .github/actions/aikido-pr-scan/README.md create mode 100644 .github/actions/aikido-pr-scan/action.yml create mode 100644 .github/actions/aws-configure/README.md create mode 100644 .github/actions/aws-configure/action.yml create mode 100644 .github/actions/aws-lambda-update/README.md create mode 100644 .github/actions/aws-lambda-update/action.yml create mode 100644 .github/actions/aws-s3-sync/README.md create mode 100644 .github/actions/aws-s3-sync/action.yml create mode 100644 .github/actions/cloudfront-invalidate/README.md create mode 100644 .github/actions/cloudfront-invalidate/action.yml create mode 100644 .github/actions/docker-build-push/README.md create mode 100644 .github/actions/docker-build-push/action.yml create mode 100644 .github/actions/helm-deploy/README.md create mode 100644 .github/actions/helm-deploy/action.yml create mode 100644 .github/actions/maven-build/README.md create mode 100644 .github/actions/maven-build/action.yml create mode 100644 .github/actions/playwright-e2e/README.md create mode 100644 .github/actions/playwright-e2e/action.yml create mode 100644 .github/actions/pnpm-build/README.md create mode 100644 .github/actions/pnpm-build/action.yml create mode 100644 .github/actions/publish-npm-package/README.md create mode 100644 .github/actions/publish-npm-package/action.yml create mode 100644 .github/actions/publish-rust-crate/README.md create mode 100644 .github/actions/publish-rust-crate/action.yml create mode 100644 .github/actions/rust-build/README.md create mode 100644 .github/actions/rust-build/action.yml create mode 100644 .github/actions/secrets-inject/README.md create mode 100644 .github/actions/secrets-inject/action.yml create mode 100644 .github/actions/terraform-apply/README.md create mode 100644 .github/actions/terraform-apply/action.yml create mode 100644 .github/actions/terraform-module-publish/README.md create mode 100644 .github/actions/terraform-module-publish/action.yml create mode 100644 .github/actions/terraform-validate/README.md create mode 100644 .github/actions/terraform-validate/action.yml create mode 100644 .github/workflows/validate-shared-actions.yml delete mode 100644 .gitignore diff --git a/.github/actions/aikido-full-scan/README.md b/.github/actions/aikido-full-scan/README.md new file mode 100644 index 0000000..61bd69d --- /dev/null +++ b/.github/actions/aikido-full-scan/README.md @@ -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. diff --git a/.github/actions/aikido-full-scan/action.yml b/.github/actions/aikido-full-scan/action.yml new file mode 100644 index 0000000..648b4af --- /dev/null +++ b/.github/actions/aikido-full-scan/action.yml @@ -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" diff --git a/.github/actions/aikido-pr-scan/README.md b/.github/actions/aikido-pr-scan/README.md new file mode 100644 index 0000000..0c3f34d --- /dev/null +++ b/.github/actions/aikido-pr-scan/README.md @@ -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`. diff --git a/.github/actions/aikido-pr-scan/action.yml b/.github/actions/aikido-pr-scan/action.yml new file mode 100644 index 0000000..66ca044 --- /dev/null +++ b/.github/actions/aikido-pr-scan/action.yml @@ -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" diff --git a/.github/actions/aws-configure/README.md b/.github/actions/aws-configure/README.md new file mode 100644 index 0000000..fa1c330 --- /dev/null +++ b/.github/actions/aws-configure/README.md @@ -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. diff --git a/.github/actions/aws-configure/action.yml b/.github/actions/aws-configure/action.yml new file mode 100644 index 0000000..b16219b --- /dev/null +++ b/.github/actions/aws-configure/action.yml @@ -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 diff --git a/.github/actions/aws-lambda-update/README.md b/.github/actions/aws-lambda-update/README.md new file mode 100644 index 0000000..1ec611c --- /dev/null +++ b/.github/actions/aws-lambda-update/README.md @@ -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. diff --git a/.github/actions/aws-lambda-update/action.yml b/.github/actions/aws-lambda-update/action.yml new file mode 100644 index 0000000..87fff8f --- /dev/null +++ b/.github/actions/aws-lambda-update/action.yml @@ -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 diff --git a/.github/actions/aws-s3-sync/README.md b/.github/actions/aws-s3-sync/README.md new file mode 100644 index 0000000..32c211f --- /dev/null +++ b/.github/actions/aws-s3-sync/README.md @@ -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/` 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. diff --git a/.github/actions/aws-s3-sync/action.yml b/.github/actions/aws-s3-sync/action.yml new file mode 100644 index 0000000..c7c0710 --- /dev/null +++ b/.github/actions/aws-s3-sync/action.yml @@ -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/ 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 diff --git a/.github/actions/cloudfront-invalidate/README.md b/.github/actions/cloudfront-invalidate/README.md new file mode 100644 index 0000000..48d4092 --- /dev/null +++ b/.github/actions/cloudfront-invalidate/README.md @@ -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. diff --git a/.github/actions/cloudfront-invalidate/action.yml b/.github/actions/cloudfront-invalidate/action.yml new file mode 100644 index 0000000..ad94fd3 --- /dev/null +++ b/.github/actions/cloudfront-invalidate/action.yml @@ -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 diff --git a/.github/actions/docker-build-push/README.md b/.github/actions/docker-build-push/README.md new file mode 100644 index 0000000..b401af5 --- /dev/null +++ b/.github/actions/docker-build-push/README.md @@ -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. diff --git a/.github/actions/docker-build-push/action.yml b/.github/actions/docker-build-push/action.yml new file mode 100644 index 0000000..401325f --- /dev/null +++ b/.github/actions/docker-build-push/action.yml @@ -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" diff --git a/.github/actions/helm-deploy/README.md b/.github/actions/helm-deploy/README.md new file mode 100644 index 0000000..b813a9c --- /dev/null +++ b/.github/actions/helm-deploy/README.md @@ -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. diff --git a/.github/actions/helm-deploy/action.yml b/.github/actions/helm-deploy/action.yml new file mode 100644 index 0000000..e10c00d --- /dev/null +++ b/.github/actions/helm-deploy/action.yml @@ -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" diff --git a/.github/actions/maven-build/README.md b/.github/actions/maven-build/README.md new file mode 100644 index 0000000..ebfc7db --- /dev/null +++ b/.github/actions/maven-build/README.md @@ -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. diff --git a/.github/actions/maven-build/action.yml b/.github/actions/maven-build/action.yml new file mode 100644 index 0000000..e7446f7 --- /dev/null +++ b/.github/actions/maven-build/action.yml @@ -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') }} diff --git a/.github/actions/playwright-e2e/README.md b/.github/actions/playwright-e2e/README.md new file mode 100644 index 0000000..b62dbc9 --- /dev/null +++ b/.github/actions/playwright-e2e/README.md @@ -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:////shard-/`. +- Uses JFrog Artifactory as the npm registry for dependency installation. diff --git a/.github/actions/playwright-e2e/action.yml b/.github/actions/playwright-e2e/action.yml new file mode 100644 index 0000000..4042f0e --- /dev/null +++ b/.github/actions/playwright-e2e/action.yml @@ -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 }} diff --git a/.github/actions/pnpm-build/README.md b/.github/actions/pnpm-build/README.md new file mode 100644 index 0000000..02a1218 --- /dev/null +++ b/.github/actions/pnpm-build/README.md @@ -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`. diff --git a/.github/actions/pnpm-build/action.yml b/.github/actions/pnpm-build/action.yml new file mode 100644 index 0000000..effc794 --- /dev/null +++ b/.github/actions/pnpm-build/action.yml @@ -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') }} diff --git a/.github/actions/publish-npm-package/README.md b/.github/actions/publish-npm-package/README.md new file mode 100644 index 0000000..10531b9 --- /dev/null +++ b/.github/actions/publish-npm-package/README.md @@ -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. diff --git a/.github/actions/publish-npm-package/action.yml b/.github/actions/publish-npm-package/action.yml new file mode 100644 index 0000000..0101668 --- /dev/null +++ b/.github/actions/publish-npm-package/action.yml @@ -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 diff --git a/.github/actions/publish-rust-crate/README.md b/.github/actions/publish-rust-crate/README.md new file mode 100644 index 0000000..2e884b7 --- /dev/null +++ b/.github/actions/publish-rust-crate/README.md @@ -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__TOKEN` environment variable (registry name is uppercased). +- The crate version published is whatever is in `Cargo.toml` -- bump it before calling this action. diff --git a/.github/actions/publish-rust-crate/action.yml b/.github/actions/publish-rust-crate/action.yml new file mode 100644 index 0000000..21736a1 --- /dev/null +++ b/.github/actions/publish-rust-crate/action.yml @@ -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}" diff --git a/.github/actions/rust-build/README.md b/.github/actions/rust-build/README.md new file mode 100644 index 0000000..a02d2a4 --- /dev/null +++ b/.github/actions/rust-build/README.md @@ -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. diff --git a/.github/actions/rust-build/action.yml b/.github/actions/rust-build/action.yml new file mode 100644 index 0000000..86eae0d --- /dev/null +++ b/.github/actions/rust-build/action.yml @@ -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') }} diff --git a/.github/actions/secrets-inject/README.md b/.github/actions/secrets-inject/README.md new file mode 100644 index 0000000..2acd738 --- /dev/null +++ b/.github/actions/secrets-inject/README.md @@ -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. diff --git a/.github/actions/secrets-inject/action.yml b/.github/actions/secrets-inject/action.yml new file mode 100644 index 0000000..ddb811f --- /dev/null +++ b/.github/actions/secrets-inject/action.yml @@ -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 diff --git a/.github/actions/terraform-apply/README.md b/.github/actions/terraform-apply/README.md new file mode 100644 index 0000000..144c4af --- /dev/null +++ b/.github/actions/terraform-apply/README.md @@ -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 `/.outputs/`. +- Provider cache is restored/saved automatically. +- Use `plan-only: true` for a plan-then-apply workflow across jobs. diff --git a/.github/actions/terraform-apply/action.yml b/.github/actions/terraform-apply/action.yml new file mode 100644 index 0000000..e33dc95 --- /dev/null +++ b/.github/actions/terraform-apply/action.yml @@ -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)) }} diff --git a/.github/actions/terraform-module-publish/README.md b/.github/actions/terraform-module-publish/README.md new file mode 100644 index 0000000..895b707 --- /dev/null +++ b/.github/actions/terraform-module-publish/README.md @@ -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 `//.zip`. +- Uses `curl` with bearer token authentication. diff --git a/.github/actions/terraform-module-publish/action.yml b/.github/actions/terraform-module-publish/action.yml new file mode 100644 index 0000000..3ef3141 --- /dev/null +++ b/.github/actions/terraform-module-publish/action.yml @@ -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" diff --git a/.github/actions/terraform-validate/README.md b/.github/actions/terraform-validate/README.md new file mode 100644 index 0000000..59a949c --- /dev/null +++ b/.github/actions/terraform-validate/README.md @@ -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`). diff --git a/.github/actions/terraform-validate/action.yml b/.github/actions/terraform-validate/action.yml new file mode 100644 index 0000000..c3f7bdb --- /dev/null +++ b/.github/actions/terraform-validate/action.yml @@ -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 diff --git a/.github/workflows/validate-shared-actions.yml b/.github/workflows/validate-shared-actions.yml new file mode 100644 index 0000000..d12f25d --- /dev/null +++ b/.github/workflows/validate-shared-actions.yml @@ -0,0 +1,73 @@ +name: validate-shared-actions + +on: + pull_request: + types: [opened, reopened, synchronize] + +permissions: + contents: read + +jobs: + validate-shared-actions: + runs-on: ubuntu-latest + env: + ACTIONLINT_VERSION: "1.7.8" + steps: + - name: Checkout + uses: https://code.forgejo.org/actions/checkout@v4 + + - name: Restore actionlint cache + id: cache-actionlint + uses: https://data.forgejo.org/actions/cache/restore@v4 + with: + path: .cache/tools/actionlint + key: actionlint-${{ runner.os }}-${{ env.ACTIONLINT_VERSION }} + + - name: Install actionlint (pinned + checksum) + if: ${{ steps.cache-actionlint.outputs.cache-hit != 'true' }} + shell: bash + run: | + set -euo pipefail + + VERSION="${ACTIONLINT_VERSION}" + OS="linux" + ARCH="amd64" + BASE_URL="https://github.com/rhysd/actionlint/releases/download/v${VERSION}" + TAR="actionlint_${VERSION}_${OS}_${ARCH}.tar.gz" + CHECKSUMS="checksums.txt" + + INSTALL_DIR=".cache/tools/actionlint/${VERSION}" + mkdir -p "${INSTALL_DIR}" + + curl -fsSL "${BASE_URL}/${TAR}" -o "/tmp/${TAR}" + curl -fsSL "${BASE_URL}/${CHECKSUMS}" -o "/tmp/${CHECKSUMS}" + + grep " ${TAR}$" "/tmp/${CHECKSUMS}" > "/tmp/actionlint-sha256.txt" + (cd /tmp && sha256sum -c actionlint-sha256.txt) + + tar -xzf "/tmp/${TAR}" -C "${INSTALL_DIR}" actionlint + chmod +x "${INSTALL_DIR}/actionlint" + + - name: Save actionlint cache + if: ${{ steps.cache-actionlint.outputs.cache-hit != 'true' }} + uses: https://data.forgejo.org/actions/cache/save@v4 + with: + path: .cache/tools/actionlint + key: actionlint-${{ runner.os }}-${{ env.ACTIONLINT_VERSION }} + + - name: Lint workflows with actionlint + shell: bash + run: | + set -euo pipefail + + ACTIONLINT_BIN=".cache/tools/actionlint/${ACTIONLINT_VERSION}/actionlint" + if [ ! -x "${ACTIONLINT_BIN}" ]; then + echo "actionlint binary missing: ${ACTIONLINT_BIN}" + exit 1 + fi + + if compgen -G ".github/workflows/*.yml" > /dev/null || compgen -G ".github/workflows/*.yaml" > /dev/null; then + "${ACTIONLINT_BIN}" -color + else + echo "No workflow files found in .github/workflows; skipping actionlint workflow lint" + fi diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b24d71e..0000000 --- a/.gitignore +++ /dev/null @@ -1,50 +0,0 @@ -# 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 - diff --git a/README.md b/README.md index db9329b..f59690e 100644 --- a/README.md +++ b/README.md @@ -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