commit c11d34400e498dba5756e7d9fa803afb0203da89 Author: Eric Liu Date: Tue Jun 16 23:46:32 2026 -0700 Initial: flux-deploy composite action for K8s GitOps deployments diff --git a/.gitea/actions/flux-deploy/action.yml b/.gitea/actions/flux-deploy/action.yml new file mode 100644 index 0000000..791f029 --- /dev/null +++ b/.gitea/actions/flux-deploy/action.yml @@ -0,0 +1,100 @@ +name: Flux GitOps Deploy +description: Update a Flux HelmRelease image tag in a GitOps repo to trigger a Kubernetes deployment +author: eric + +inputs: + image-repository: + description: Full image repository (e.g. git.ericxliu.me/eric/workout) + required: true + image-tag: + description: Image tag to deploy (e.g. 2026-06-17-abc1234) + required: true + gitops-repo: + description: SSH URL of the GitOps repo + default: git@github.com:eric-x-liu/k8s-flux.git + required: false + gitops-file: + description: Path to the release YAML in the GitOps repo (e.g. apps/myapp/workout/release.yaml) + required: true + deploy-key: + description: SSH private key with push access to the GitOps repo + required: true + git-user-name: + description: Git commit author name + default: gitops-ci + required: false + git-user-email: + description: Git commit author email + default: gitops-ci@users.noreply.git.ericxliu.me + required: false + commit-message: + description: "Custom commit message. Supports {app} and {tag} placeholders." + default: "" + required: false + +runs: + using: composite + steps: + - name: Update GitOps image tag + shell: bash + env: + IMAGE_REPOSITORY: ${{ inputs.image-repository }} + IMAGE_TAG: ${{ inputs.image-tag }} + GITOPS_REPO: ${{ inputs.gitops-repo }} + GITOPS_FILE: ${{ inputs.gitops-file }} + DEPLOY_KEY: ${{ inputs.deploy-key }} + GIT_USER_NAME: ${{ inputs.git-user-name }} + GIT_USER_EMAIL: ${{ inputs.git-user-email }} + COMMIT_MESSAGE: ${{ inputs.commit-message }} + run: | + set -euo pipefail + + # ── SSH setup ────────────────────────────────────────────── + mkdir -p "$HOME/.ssh" + printf '%s\n' "$DEPLOY_KEY" > "$HOME/.ssh/flux_deploy_key" + chmod 600 "$HOME/.ssh/flux_deploy_key" + ssh-keyscan -H github.com >> "$HOME/.ssh/known_hosts" 2>/dev/null + export GIT_SSH_COMMAND="ssh -i $HOME/.ssh/flux_deploy_key -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes" + + # ── Clone GitOps repo ────────────────────────────────────── + workdir="$(mktemp -d)" + git clone "$GITOPS_REPO" "$workdir/gitops" + cd "$workdir/gitops" + + # ── Update image repository + tag via regex ──────────────── + python3 -c ' + import os, pathlib, re + + path = pathlib.Path(os.environ["GITOPS_FILE"]) + repo = os.environ["IMAGE_REPOSITORY"] + tag = os.environ["IMAGE_TAG"] + + 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) + 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" + exit 0 + fi + + # Derive app name from the gitops file path for the default commit message + APP_NAME="$(basename "$(dirname "$GITOPS_FILE")")" + + if [ -z "$COMMIT_MESSAGE" ]; then + COMMIT_MESSAGE="Update ${APP_NAME} image to ${IMAGE_TAG}" + else + COMMIT_MESSAGE="${COMMIT_MESSAGE//\{app\}/$APP_NAME}" + COMMIT_MESSAGE="${COMMIT_MESSAGE//\{tag\}/$IMAGE_TAG}" + fi + + git config user.name "$GIT_USER_NAME" + git config user.email "$GIT_USER_EMAIL" + git add "$GITOPS_FILE" + git commit -m "$COMMIT_MESSAGE" + git push origin main + + echo "🚀 Pushed: $IMAGE_REPOSITORY:$IMAGE_TAG → $GITOPS_FILE" diff --git a/README.md b/README.md new file mode 100644 index 0000000..8fbed9a --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# actions-k8s + +Reusable Gitea Actions for Kubernetes deployments via Flux GitOps. + +## actions/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. + +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 + +```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: Checkout code + uses: https://gitea.com/actions/checkout@v4 + with: + ssh-key: ${{ secrets.SSH_KEY }} + + - 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 + + - 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 }} +``` + +### 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`) | +| `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 | +| `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`)