From 248eb37110f02189c55710629d44ad4f0bf76179 Mon Sep 17 00:00:00 2001 From: Eric Liu Date: Wed, 17 Jun 2026 20:06:59 -0700 Subject: [PATCH] feat: support custom tag-keys for non-standard YAML layouts Adds tag-keys input (comma-separated) to update arbitrary YAML keys instead of just 'tag'. Handles unquoted values, quoted values, and YAML anchors (e.g. rustImageTag: &rustImageTag "value"). Makes image-repository optional when using custom tag-keys. --- .gitea/actions/flux-deploy/action.yml | 59 ++++++++++++++++--- README.md | 82 ++++++++++----------------- 2 files changed, 80 insertions(+), 61 deletions(-) diff --git a/.gitea/actions/flux-deploy/action.yml b/.gitea/actions/flux-deploy/action.yml index 791f029..6acbb58 100644 --- a/.gitea/actions/flux-deploy/action.yml +++ b/.gitea/actions/flux-deploy/action.yml @@ -4,11 +4,23 @@ author: eric inputs: image-repository: - description: Full image repository (e.g. git.ericxliu.me/eric/workout) - required: true + description: | + Full image repository (e.g. git.ericxliu.me/eric/workout). + Used to update 'repository:' fields in standard bjw-s app-template format. + Optional when using tag-keys for non-standard layouts. + required: false + default: "" image-tag: description: Image tag to deploy (e.g. 2026-06-17-abc1234) required: true + tag-keys: + description: | + Comma-separated YAML keys whose values should be set to image-tag. + Defaults to 'tag' which matches the standard 'tag: ' format. + For non-standard layouts, specify custom keys (e.g. 'rustImageTag,uiImageTag'). + Handles both unquoted values and quoted values with optional YAML anchors. + required: false + default: "tag" gitops-repo: description: SSH URL of the GitOps repo default: git@github.com:eric-x-liu/k8s-flux.git @@ -40,6 +52,7 @@ runs: env: IMAGE_REPOSITORY: ${{ inputs.image-repository }} IMAGE_TAG: ${{ inputs.image-tag }} + TAG_KEYS: ${{ inputs.tag-keys }} GITOPS_REPO: ${{ inputs.gitops-repo }} GITOPS_FILE: ${{ inputs.gitops-file }} DEPLOY_KEY: ${{ inputs.deploy-key }} @@ -61,23 +74,53 @@ runs: git clone "$GITOPS_REPO" "$workdir/gitops" cd "$workdir/gitops" - # ── Update image repository + tag via regex ──────────────── + # ── Update image fields via regex ────────────────────────── python3 -c ' import os, pathlib, re path = pathlib.Path(os.environ["GITOPS_FILE"]) - repo = os.environ["IMAGE_REPOSITORY"] + repo = os.environ.get("IMAGE_REPOSITORY", "") tag = os.environ["IMAGE_TAG"] + tag_keys = [k.strip() for k in os.environ["TAG_KEYS"].split(",") if k.strip()] text = path.read_text() - text = re.sub(r"(repository:\s*)\S+", lambda m: m.group(1) + repo, text, count=1) - text = re.sub(r"(tag:\s*)\S+", lambda m: m.group(1) + tag, text, count=1) + + # Update repository field (only if image-repository is provided) + if repo: + text = re.sub(r"(repository:\s*)\S+", lambda m: m.group(1) + repo, text, count=1) + + # Update each tag key + for key in tag_keys: + # Pattern matches both formats: + # tag: 2026-06-17-abc1234 (unquoted) + # tag: "2026-06-17-abc1234" (quoted) + # rustImageTag: &anchor "value" (YAML anchor with quotes) + # tag: *anchor (YAML alias — left alone by design) + # + # Group 1: everything up to and including the last quote or whitespace before the value + # Replacement: group(1) + new tag (preserving quoting style) + quoted = re.sub( + r"(" + re.escape(key) + r""":\s*(?:&\S+\s+)?)\"[^"]*\"""", + lambda m: m.group(1) + '"' + tag + '"', + text + ) + if quoted != text: + text = quoted + else: + # Unquoted value (e.g., tag: 2026-06-17-abc1234) + text = re.sub( + r"(" + re.escape(key) + r":\s*)\S+", + lambda m: m.group(1) + tag, + text, + count=1 + ) + path.write_text(text) ' # ── Commit + push (idempotent) ──────────────────────────── if git diff --quiet -- "$GITOPS_FILE"; then - echo "✅ GitOps already points to $IMAGE_REPOSITORY:$IMAGE_TAG — nothing to do" + echo "✅ GitOps already points to ${IMAGE_REPOSITORY:-(custom keys)}:$IMAGE_TAG — nothing to do" exit 0 fi @@ -97,4 +140,4 @@ runs: git commit -m "$COMMIT_MESSAGE" git push origin main - echo "🚀 Pushed: $IMAGE_REPOSITORY:$IMAGE_TAG → $GITOPS_FILE" + echo "🚀 Pushed: ${APP_NAME} → $IMAGE_TAG" diff --git a/README.md b/README.md index 8fbed9a..cdeb2a4 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,48 @@ # actions-k8s -Reusable Gitea Actions for Kubernetes deployments via Flux GitOps. +Reusable Gitea Actions for Kubernetes deployments. -## actions/flux-deploy +## `flux-deploy` -Composite action that updates a [Flux HelmRelease](https://fluxcd.io/flux/components/helm/) image tag in a GitOps repo to trigger a Kubernetes deployment. +Update a Flux HelmRelease image tag in a GitOps repo to trigger a Kubernetes deployment. -This action clones the GitOps repo, updates the `repository` and `tag` fields in the specified release YAML, commits, and pushes. If the release already points to the requested image, the action exits cleanly (idempotent). - -### Usage +### Usage (standard bjw-s app-template) ```yaml -jobs: - deploy: - runs-on: ubuntu-latest - needs: [ci] - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - steps: - - name: Add SSH_KEY - run: | - echo "${{ secrets.SSH_KEY }}" > $HOME/.ssh/id_rsa - chmod 400 $HOME/.ssh/id_rsa - ssh-keyscan -H git.ericxliu.me > $HOME/.ssh/known_hosts +- name: Deploy to K8s via Flux + uses: https://git.ericxliu.me/eric/actions-k8s/.gitea/actions/flux-deploy@main + with: + image-repository: git.ericxliu.me/eric/my-app + image-tag: ${{ steps.build.outputs.tag }} + gitops-file: apps/myapp/my-app/release.yaml + deploy-key: ${{ secrets.K8S_FLUX_DEPLOY_KEY }} +``` - - name: Checkout code - uses: https://gitea.com/actions/checkout@v4 - with: - ssh-key: ${{ secrets.SSH_KEY }} +### Usage (custom tag keys — e.g. YAML anchors) - - name: Build and push image - id: image - uses: https://git.ericxliu.me/eric/actions-docker/.gitea/actions/docker-build@main - with: - image-name: ${{ github.event.repository.name }} - push: true +For non-standard layouts where image tags are stored in custom keys +(e.g. `rustImageTag: &rustImageTag "2026-06-17-abc1234"`): - - name: Deploy to K8s via Flux - uses: https://git.ericxliu.me/eric/actions-k8s/.gitea/actions/flux-deploy@main - with: - image-repository: ${{ github.server_url }}/${{ github.repository }} - image-tag: ${{ steps.image.outputs.tag }} - gitops-file: apps/myapp/my-app/release.yaml - deploy-key: ${{ secrets.K8S_FLUX_DEPLOY_KEY }} +```yaml +- name: Deploy to K8s via Flux + uses: https://git.ericxliu.me/eric/actions-k8s/.gitea/actions/flux-deploy@main + with: + image-tag: ${{ env.TAG }} + tag-keys: rustImageTag,uiImageTag + gitops-file: apps/myapp/stock-analyzer/release.yaml + deploy-key: ${{ secrets.K8S_FLUX_DEPLOY_KEY }} ``` ### Inputs | Input | Required | Default | Description | -|-------|----------|---------|-------------| -| `image-repository` | **Yes** | — | Full image repository (e.g. `git.ericxliu.me/eric/workout`) | -| `image-tag` | **Yes** | — | Image tag to deploy (e.g. `2026-06-17-abc1234`) | +|---|---|---|---| +| `image-repository` | No | `""` | Full image repository to set in `repository:` fields. Optional when using `tag-keys`. | +| `image-tag` | **Yes** | — | Image tag to deploy | +| `tag-keys` | No | `tag` | Comma-separated YAML keys to update. Handles unquoted, quoted, and YAML anchor formats. | | `gitops-repo` | No | `git@github.com:eric-x-liu/k8s-flux.git` | SSH URL of the GitOps repo | -| `gitops-file` | **Yes** | — | Path to release YAML in the GitOps repo | +| `gitops-file` | **Yes** | — | Path to the release YAML in the GitOps repo | | `deploy-key` | **Yes** | — | SSH private key with push access to the GitOps repo | | `git-user-name` | No | `gitops-ci` | Git commit author name | | `git-user-email` | No | `gitops-ci@users.noreply.git.ericxliu.me` | Git commit author email | -| `commit-message` | No | `Update {app} image to {tag}` | Custom commit message (`{app}` and `{tag}` are replaced) | - -### How It Works - -1. Sets up SSH with the provided deploy key -2. Clones the GitOps repo (e.g. `k8s-flux`) -3. Updates `repository:` and `tag:` fields in the specified release YAML via regex -4. If no changes detected → exits cleanly (idempotent) -5. Commits and pushes to trigger Flux reconciliation - -### Requirements - -- The GitOps repo must be accessible via SSH with the provided deploy key -- The release YAML must contain `repository:` and `tag:` fields (standard bjw-s `app-template` chart format) -- `python3` must be available on the runner (standard on `ubuntu-latest`) +| `commit-message` | No | `Update {app} image to {tag}` | Custom commit message with `{app}` and `{tag}` placeholders |