diff --git a/.cursor/rules/reusable-workflows.mdc b/.cursor/rules/reusable-workflows.mdc index 3a3081f..ab460b8 100644 --- a/.cursor/rules/reusable-workflows.mdc +++ b/.cursor/rules/reusable-workflows.mdc @@ -127,8 +127,8 @@ runs-on: self-hosted Every reusable workflow must: - support `workflow_call` (for external callers) -- support `workflow_dispatch` (for manual testing) - expose explicit `inputs` — never rely on implicit context +- **must NOT** include a `workflow_dispatch` trigger — if manual/interactive dispatch is needed, create a separate self-workflow under `.github/workflows/self-*` - **always include a `dry_run` input** (`type: boolean`, `default: false`) so the workflow can be safely tested before applying real changes ```yaml @@ -146,15 +146,6 @@ on: secrets: DEPLOY_TOKEN: required: true - workflow_dispatch: - inputs: - environment: - required: true - type: string - dry_run: - description: Preview changes without applying them - type: boolean - default: false ``` The two modes have opposite goals — design them accordingly: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1f1ab84..7459ff0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -123,7 +123,7 @@ on: type: boolean default: false docker_build_args: - description: 'Newline-separated Docker build arguments to pass to docker build (e.g., "APP_NAME=spi\nCOMPONENT_NAME=api"). Forwarded to docker/build-push-action build-args.' + description: 'Newline-separated Docker build arguments (e.g., "APP_NAME=spi\nCOMPONENT_NAME=api"). For sensitive values (tokens, keys, passwords), use BuildKit secrets instead — build arguments are visible in image history.' type: string required: false default: '' @@ -139,7 +139,6 @@ on: permissions: contents: read packages: write - id-token: write jobs: prepare: @@ -208,6 +207,10 @@ jobs: if: needs.prepare.outputs.has_builds == 'true' runs-on: ${{ inputs.runner_type }} name: Build ${{ matrix.app.name }} + permissions: + contents: read + packages: write + id-token: write strategy: max-parallel: 2 fail-fast: false @@ -227,14 +230,14 @@ jobs: - name: Log in to DockerHub if: inputs.enable_dockerhub - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKERHUB_IMAGE_PUSH_TOKEN }} - name: Log in to GHCR if: inputs.enable_ghcr - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: registry: ghcr.io username: ${{ github.actor }} @@ -289,7 +292,6 @@ jobs: images: ${{ steps.image-names.outputs.images }} tags: | type=semver,pattern={{version}},value=${{ steps.version.outputs.version }} - type=semver,pattern={{major}}.{{minor}},value=${{ steps.version.outputs.version }} type=semver,pattern={{major}},value=${{ steps.version.outputs.version }},enable=${{ needs.prepare.outputs.is_release }} - name: Build and push Docker image @@ -320,12 +322,12 @@ jobs: ENABLE_GHCR: ${{ inputs.enable_ghcr }} DOCKERHUB_ORG: ${{ inputs.dockerhub_org }} APP_NAME: ${{ matrix.app.name }} - GHCR_ORG: ${{ steps.normalize.outputs.owner_lower }} + GHCR_ORG: ${{ inputs.ghcr_org || steps.normalize.outputs.owner_lower }} run: | REFS="" if [ "$ENABLE_DOCKERHUB" == "true" ]; then - REFS="${DOCKERHUB_ORG}/${APP_NAME}@${DIGEST}" + REFS="docker.io/${DOCKERHUB_ORG}/${APP_NAME}@${DIGEST}" fi if [ "$ENABLE_GHCR" == "true" ]; then @@ -341,7 +343,7 @@ jobs: - name: Sign container images with cosign if: inputs.enable_cosign_sign - uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign + uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@v1.23.0 with: image-refs: ${{ steps.cosign-refs.outputs.refs }} diff --git a/.github/workflows/gitops-update.yml b/.github/workflows/gitops-update.yml index cf80378..5f3acb0 100644 --- a/.github/workflows/gitops-update.yml +++ b/.github/workflows/gitops-update.yml @@ -75,7 +75,7 @@ jobs: steps: - name: Log in to Docker Hub if: ${{ inputs.enable_docker_login }} - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKERHUB_IMAGE_PULL_TOKEN }} @@ -444,7 +444,25 @@ jobs: fi git commit -am "ci(${{ steps.setup.outputs.commit_prefix }}): update image tags ($ENV_LABEL)" || echo "No changes to commit" - git push origin main + + # Retry push with rebase and exponential backoff to handle concurrent updates + MAX_RETRIES=5 + for i in $(seq 1 $MAX_RETRIES); do + if git push origin main; then + echo "Push succeeded on attempt $i" + break + fi + + if [ "$i" -eq "$MAX_RETRIES" ]; then + echo "ERROR: Failed to push after $MAX_RETRIES attempts" + exit 1 + fi + + BACKOFF=$((2 ** i)) + echo "Push failed (attempt $i/$MAX_RETRIES), rebasing and retrying in ${BACKOFF}s..." + sleep "$BACKOFF" + git pull --rebase origin main + done # ArgoCD Sync Job - runs in parallel for each server/env combination argocd_sync: diff --git a/.github/workflows/go-release.yml b/.github/workflows/go-release.yml index acd1ec8..23cbf9e 100644 --- a/.github/workflows/go-release.yml +++ b/.github/workflows/go-release.yml @@ -75,7 +75,6 @@ on: permissions: contents: write packages: write - id-token: write jobs: release: @@ -102,7 +101,7 @@ jobs: run: ${{ inputs.test_cmd }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@9a127d869fb706213d29cdf8eef3a4ea2b869415 # v7 + uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7 with: distribution: ${{ inputs.goreleaser_distribution }} version: ${{ inputs.goreleaser_version }} @@ -144,6 +143,10 @@ jobs: docker: name: Build and Push Docker Image runs-on: ${{ inputs.runner_type }} + permissions: + contents: read + packages: write + id-token: write needs: release if: inputs.enable_docker && startsWith(github.ref, 'refs/tags/v') @@ -155,7 +158,7 @@ jobs: uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 - name: Log in to Docker Registry - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: registry: ${{ inputs.docker_registry }} username: ${{ secrets.DOCKER_USERNAME || github.actor }} @@ -194,7 +197,7 @@ jobs: - name: Sign container images with cosign if: inputs.enable_cosign_sign - uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign + uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@v1.23.0 with: image-refs: ${{ steps.cosign-refs.outputs.refs }} diff --git a/.github/workflows/gptchangelog.yml b/.github/workflows/gptchangelog.yml index 180b8bf..88003b2 100644 --- a/.github/workflows/gptchangelog.yml +++ b/.github/workflows/gptchangelog.yml @@ -62,7 +62,7 @@ jobs: is_stable: ${{ steps.check-tag.outputs.is_stable }} steps: - name: Checkout for branch check - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 @@ -139,7 +139,7 @@ jobs: - name: Checkout repository if: steps.check-tag.outputs.is_stable == 'true' || inputs.stable_releases_only == false - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 @@ -252,14 +252,14 @@ jobs: steps: - name: Create GitHub App Token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 id: app-token with: app-id: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_APP_ID }} private-key: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_PRIVATE_KEY }} - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 token: ${{ steps.app-token.outputs.token }} @@ -279,7 +279,7 @@ jobs: fi - name: Import GPG key - uses: crazy-max/ghaction-import-gpg@v7 + uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7 id: import_gpg with: gpg_private_key: ${{ secrets.LERIAN_CI_CD_USER_GPG_KEY }} @@ -733,7 +733,7 @@ jobs: runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - name: Send Slack notification for sync PR - uses: slackapi/slack-github-action@v1.24.0 + uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 with: payload: | { diff --git a/.github/workflows/helm-update-chart.yml b/.github/workflows/helm-update-chart.yml index 20cf074..72f1f21 100644 --- a/.github/workflows/helm-update-chart.yml +++ b/.github/workflows/helm-update-chart.yml @@ -100,7 +100,7 @@ jobs: steps: - name: Generate GitHub App Token id: app-token - uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/.github/workflows/pr-security-scan.yml b/.github/workflows/pr-security-scan.yml index 3ead072..7405445 100644 --- a/.github/workflows/pr-security-scan.yml +++ b/.github/workflows/pr-security-scan.yml @@ -68,7 +68,7 @@ on: type: boolean default: true docker_build_args: - description: 'Newline-separated Docker build arguments to pass to docker build (e.g., "APP_NAME=spi\nCOMPONENT_NAME=api"). Forwarded to docker/build-push-action build-args.' + description: 'Newline-separated Docker build arguments (e.g., "APP_NAME=spi\nCOMPONENT_NAME=api"). For sensitive values (tokens, keys, passwords), use BuildKit secrets instead — build arguments are visible in image history.' type: string required: false default: '' @@ -91,7 +91,7 @@ jobs: steps: # ----------------- Setup ----------------- - name: Login to Docker Registry - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: registry: ${{ inputs.docker_registry }} username: ${{ secrets.DOCKER_USERNAME }} @@ -133,7 +133,7 @@ jobs: steps: # ----------------- Setup ----------------- - name: Login to Docker Registry - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 with: registry: ${{ inputs.docker_registry }} username: ${{ secrets.DOCKER_USERNAME }} diff --git a/.github/workflows/release-notification.yml b/.github/workflows/release-notification.yml index d025a58..a2444ce 100644 --- a/.github/workflows/release-notification.yml +++ b/.github/workflows/release-notification.yml @@ -114,14 +114,14 @@ jobs: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} steps: - name: Create GitHub App token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 id: app-token with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Fetch latest release tag id: release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a26472c..4b6e573 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -103,7 +103,7 @@ jobs: gpg_fingerprint: ${{ steps.import_gpg.outputs.fingerprint }} steps: - - uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 + - uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: app-token with: app-id: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_APP_ID }} diff --git a/.github/workflows/typescript-build.yml b/.github/workflows/typescript-build.yml index 91082e5..31d8533 100644 --- a/.github/workflows/typescript-build.yml +++ b/.github/workflows/typescript-build.yml @@ -148,7 +148,6 @@ on: permissions: contents: read packages: write - id-token: write jobs: prepare: @@ -250,6 +249,10 @@ jobs: if: needs.prepare.outputs.has_builds == 'true' runs-on: ${{ inputs.runner_type }} name: Build ${{ matrix.app.name }} + permissions: + contents: read + packages: write + id-token: write strategy: max-parallel: 2 fail-fast: false @@ -317,7 +320,7 @@ jobs: fi if [ "$ENABLE_DOCKERHUB" == "true" ]; then - REFS="${DOCKERHUB_ORG}/${APP_NAME}@${DIGEST}" + REFS="docker.io/${DOCKERHUB_ORG}/${APP_NAME}@${DIGEST}" fi if [ "$ENABLE_GHCR" == "true" ]; then @@ -333,7 +336,7 @@ jobs: - name: Sign container images with cosign if: inputs.enable_cosign_sign && !inputs.dry_run && steps.cosign-refs.outputs.refs != '' - uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@feat/cosign-sign + uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@v1.23.0 with: image-refs: ${{ steps.cosign-refs.outputs.refs }} diff --git a/.github/workflows/typescript-release.yml b/.github/workflows/typescript-release.yml index 92704d1..4cea8ea 100644 --- a/.github/workflows/typescript-release.yml +++ b/.github/workflows/typescript-release.yml @@ -114,7 +114,7 @@ jobs: gpg_fingerprint: ${{ steps.import_gpg.outputs.fingerprint }} steps: - - uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 + - uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 id: app-token with: app-id: ${{ secrets.LERIAN_STUDIO_MIDAZ_PUSH_BOT_APP_ID }} diff --git a/docs/build-workflow.md b/docs/build-workflow.md index 3054f54..b44b227 100644 --- a/docs/build-workflow.md +++ b/docs/build-workflow.md @@ -139,7 +139,6 @@ Generated tags based on semantic versioning: | Tag Pattern | Example | When Applied | |-------------|---------|--------------| | `{{version}}` | `1.0.0-beta.1` | Always | -| `{{major}}.{{minor}}` | `1.0` | Always | | `{{major}}` | `1` | Release tags only | ## Monorepo Change Detection @@ -225,7 +224,7 @@ jobs: ```bash cosign verify \ - --certificate-identity-regexp=".*" \ + --certificate-identity-regexp="^https://github\.com/LerianStudio/.+/.github/workflows/.+@refs/(heads|tags)/.+$" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ docker.io/lerianstudio/my-app@sha256:abc123... ``` diff --git a/docs/go-release-workflow.md b/docs/go-release-workflow.md index 86412dd..d8d1fc4 100644 --- a/docs/go-release-workflow.md +++ b/docs/go-release-workflow.md @@ -204,7 +204,7 @@ jobs: ```bash cosign verify \ - --certificate-identity-regexp=".*" \ + --certificate-identity-regexp="^https://github\.com/LerianStudio/.+/.github/workflows/.+@refs/(heads|tags)/.+$" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ ghcr.io/myorg/my-app@sha256:abc123... ``` diff --git a/docs/typescript-build.md b/docs/typescript-build.md index 6623214..aab3b88 100644 --- a/docs/typescript-build.md +++ b/docs/typescript-build.md @@ -37,6 +37,7 @@ on: permissions: contents: read packages: write + id-token: write # required for cosign keyless signing jobs: build: @@ -230,7 +231,7 @@ jobs: ```bash cosign verify \ - --certificate-identity-regexp=".*" \ + --certificate-identity-regexp="^https://github\.com/LerianStudio/.+/.github/workflows/.+@refs/(heads|tags)/.+$" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ ghcr.io/lerianstudio/my-app@sha256:abc123... ``` diff --git a/src/security/cosign-sign/README.md b/src/security/cosign-sign/README.md index 0151761..d107920 100644 --- a/src/security/cosign-sign/README.md +++ b/src/security/cosign-sign/README.md @@ -46,7 +46,7 @@ jobs: - name: Sign container image uses: LerianStudio/github-actions-shared-workflows/src/security/cosign-sign@v1.x.x with: - image-refs: myorg/myapp@${{ steps.build-push.outputs.digest }} + image-refs: docker.io/myorg/myapp@${{ steps.build-push.outputs.digest }} ``` ### Signing multiple registries @@ -64,7 +64,7 @@ jobs: ```bash cosign verify \ - --certificate-identity-regexp=".*" \ + --certificate-identity-regexp="^https://github\.com/LerianStudio/.+/.github/workflows/.+@refs/(heads|tags)/.+$" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ docker.io/myorg/myapp@sha256:abc123... ```