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.
This commit is contained in:
@@ -4,11 +4,23 @@ author: eric
|
|||||||
|
|
||||||
inputs:
|
inputs:
|
||||||
image-repository:
|
image-repository:
|
||||||
description: Full image repository (e.g. git.ericxliu.me/eric/workout)
|
description: |
|
||||||
required: true
|
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:
|
image-tag:
|
||||||
description: Image tag to deploy (e.g. 2026-06-17-abc1234)
|
description: Image tag to deploy (e.g. 2026-06-17-abc1234)
|
||||||
required: true
|
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: <value>' 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:
|
gitops-repo:
|
||||||
description: SSH URL of the GitOps repo
|
description: SSH URL of the GitOps repo
|
||||||
default: git@github.com:eric-x-liu/k8s-flux.git
|
default: git@github.com:eric-x-liu/k8s-flux.git
|
||||||
@@ -40,6 +52,7 @@ runs:
|
|||||||
env:
|
env:
|
||||||
IMAGE_REPOSITORY: ${{ inputs.image-repository }}
|
IMAGE_REPOSITORY: ${{ inputs.image-repository }}
|
||||||
IMAGE_TAG: ${{ inputs.image-tag }}
|
IMAGE_TAG: ${{ inputs.image-tag }}
|
||||||
|
TAG_KEYS: ${{ inputs.tag-keys }}
|
||||||
GITOPS_REPO: ${{ inputs.gitops-repo }}
|
GITOPS_REPO: ${{ inputs.gitops-repo }}
|
||||||
GITOPS_FILE: ${{ inputs.gitops-file }}
|
GITOPS_FILE: ${{ inputs.gitops-file }}
|
||||||
DEPLOY_KEY: ${{ inputs.deploy-key }}
|
DEPLOY_KEY: ${{ inputs.deploy-key }}
|
||||||
@@ -61,23 +74,53 @@ runs:
|
|||||||
git clone "$GITOPS_REPO" "$workdir/gitops"
|
git clone "$GITOPS_REPO" "$workdir/gitops"
|
||||||
cd "$workdir/gitops"
|
cd "$workdir/gitops"
|
||||||
|
|
||||||
# ── Update image repository + tag via regex ────────────────
|
# ── Update image fields via regex ──────────────────────────
|
||||||
python3 -c '
|
python3 -c '
|
||||||
import os, pathlib, re
|
import os, pathlib, re
|
||||||
|
|
||||||
path = pathlib.Path(os.environ["GITOPS_FILE"])
|
path = pathlib.Path(os.environ["GITOPS_FILE"])
|
||||||
repo = os.environ["IMAGE_REPOSITORY"]
|
repo = os.environ.get("IMAGE_REPOSITORY", "")
|
||||||
tag = os.environ["IMAGE_TAG"]
|
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 = 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)
|
path.write_text(text)
|
||||||
'
|
'
|
||||||
|
|
||||||
# ── Commit + push (idempotent) ────────────────────────────
|
# ── Commit + push (idempotent) ────────────────────────────
|
||||||
if git diff --quiet -- "$GITOPS_FILE"; then
|
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
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -97,4 +140,4 @@ runs:
|
|||||||
git commit -m "$COMMIT_MESSAGE"
|
git commit -m "$COMMIT_MESSAGE"
|
||||||
git push origin main
|
git push origin main
|
||||||
|
|
||||||
echo "🚀 Pushed: $IMAGE_REPOSITORY:$IMAGE_TAG → $GITOPS_FILE"
|
echo "🚀 Pushed: ${APP_NAME} → $IMAGE_TAG"
|
||||||
|
|||||||
@@ -1,72 +1,48 @@
|
|||||||
# actions-k8s
|
# 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 (standard bjw-s app-template)
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
jobs:
|
- name: Deploy to K8s via Flux
|
||||||
deploy:
|
uses: https://git.ericxliu.me/eric/actions-k8s/.gitea/actions/flux-deploy@main
|
||||||
runs-on: ubuntu-latest
|
with:
|
||||||
needs: [ci]
|
image-repository: git.ericxliu.me/eric/my-app
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
image-tag: ${{ steps.build.outputs.tag }}
|
||||||
steps:
|
gitops-file: apps/myapp/my-app/release.yaml
|
||||||
- name: Add SSH_KEY
|
deploy-key: ${{ secrets.K8S_FLUX_DEPLOY_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: Checkout code
|
### Usage (custom tag keys — e.g. YAML anchors)
|
||||||
uses: https://gitea.com/actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ssh-key: ${{ secrets.SSH_KEY }}
|
|
||||||
|
|
||||||
- name: Build and push image
|
For non-standard layouts where image tags are stored in custom keys
|
||||||
id: image
|
(e.g. `rustImageTag: &rustImageTag "2026-06-17-abc1234"`):
|
||||||
uses: https://git.ericxliu.me/eric/actions-docker/.gitea/actions/docker-build@main
|
|
||||||
with:
|
|
||||||
image-name: ${{ github.event.repository.name }}
|
|
||||||
push: true
|
|
||||||
|
|
||||||
- name: Deploy to K8s via Flux
|
```yaml
|
||||||
uses: https://git.ericxliu.me/eric/actions-k8s/.gitea/actions/flux-deploy@main
|
- name: Deploy to K8s via Flux
|
||||||
with:
|
uses: https://git.ericxliu.me/eric/actions-k8s/.gitea/actions/flux-deploy@main
|
||||||
image-repository: ${{ github.server_url }}/${{ github.repository }}
|
with:
|
||||||
image-tag: ${{ steps.image.outputs.tag }}
|
image-tag: ${{ env.TAG }}
|
||||||
gitops-file: apps/myapp/my-app/release.yaml
|
tag-keys: rustImageTag,uiImageTag
|
||||||
deploy-key: ${{ secrets.K8S_FLUX_DEPLOY_KEY }}
|
gitops-file: apps/myapp/stock-analyzer/release.yaml
|
||||||
|
deploy-key: ${{ secrets.K8S_FLUX_DEPLOY_KEY }}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Inputs
|
### Inputs
|
||||||
|
|
||||||
| Input | Required | Default | Description |
|
| Input | Required | Default | Description |
|
||||||
|-------|----------|---------|-------------|
|
|---|---|---|---|
|
||||||
| `image-repository` | **Yes** | — | Full image repository (e.g. `git.ericxliu.me/eric/workout`) |
|
| `image-repository` | No | `""` | Full image repository to set in `repository:` fields. Optional when using `tag-keys`. |
|
||||||
| `image-tag` | **Yes** | — | Image tag to deploy (e.g. `2026-06-17-abc1234`) |
|
| `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-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 |
|
| `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-name` | No | `gitops-ci` | Git commit author name |
|
||||||
| `git-user-email` | No | `gitops-ci@users.noreply.git.ericxliu.me` | Git commit author email |
|
| `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) |
|
| `commit-message` | No | `Update {app} image to {tag}` | Custom commit message with `{app}` and `{tag}` placeholders |
|
||||||
|
|
||||||
### 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`)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user