From 0e97990615482424ae7e0cbcc63b04dc5ff65ac6 Mon Sep 17 00:00:00 2001 From: Jose Henrique Date: Sun, 8 Nov 2026 19:53:57 -0300 Subject: [PATCH] fix(kubectl): download kubectl for the runner's arch; wait for rollouts kubectl-apply and deploy-restart always downloaded the linux/amd64 kubectl binary, which only worked on arm64 runners because the runner images ship kubectl preinstalled. Detect the arch via uname -m, and use curl -f so a bad version fails instead of saving an error page. deploy-restart now waits for `kubectl rollout status` by default (new `wait` / `timeout` inputs, default true / 300s) and prints deployment, pod and event diagnostics when the rollout does not complete. The self-test pins kube_version to force the download path and sets a rollout timeout. README and AGENTS.md document the new inputs. Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/test-build.yaml | 4 +++ AGENTS.md | 4 ++- README.md | 6 +++- deploy-restart/action.yaml | 57 ++++++++++++++++++++++++++++++-- kubectl-apply/action.yaml | 15 +++++++-- 5 files changed, 78 insertions(+), 8 deletions(-) diff --git a/.gitea/workflows/test-build.yaml b/.gitea/workflows/test-build.yaml index f37586a..78cef92 100644 --- a/.gitea/workflows/test-build.yaml +++ b/.gitea/workflows/test-build.yaml @@ -164,6 +164,9 @@ jobs: kube_config: ${{ secrets.KUBE_CONFIG }} manifest: tests/manifests/test-deployment.yaml namespace: ${{ env.TEST_NAMESPACE }} + # Pin a version different from the runner image's preinstalled + # kubectl so the download path (arch detection) is exercised. + kube_version: v1.31.0 - name: Restart test deployment uses: https://git.ivanch.me/ivanch/pipeline-actions/deploy-restart@main @@ -171,6 +174,7 @@ jobs: kube_config: ${{ secrets.KUBE_CONFIG }} deployment_name: pipeline-actions-test namespace: ${{ env.TEST_NAMESPACE }} + timeout: 120s - name: Verify deployment rollout status shell: bash diff --git a/AGENTS.md b/AGENTS.md index f3c2976..5abfd15 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,13 +38,15 @@ Same remote SSH Buildx setup as `build-and-push`, but only **builds** the multi- ### `deploy-restart` (`deploy-restart/action.yaml`) -Validates `KUBE_CONFIG`, installs kubectl, and performs `rollout restart` of a Kubernetes Deployment. +Validates `KUBE_CONFIG`, installs kubectl (matching the runner's arch), performs `rollout restart` of a Kubernetes Deployment, and by default waits for `rollout status` — failing the step with pod/event diagnostics if the rollout doesn't complete. **Inputs:** - `kube_config` (required) — full kubeconfig YAML (use secrets) - `deployment_name` (required) — Deployment name to restart - `namespace` (default `default`) — Kubernetes namespace - `kube_version` (default `stable`) — kubectl version to download +- `wait` (default `true`) — wait for the rollout to complete; set `false` to fire-and-forget +- `timeout` (default `300s`) — max rollout wait (kubectl duration) ### `kubectl-apply` (`kubectl-apply/action.yaml`) diff --git a/README.md b/README.md index 0ab151c..af3f6e6 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ jobs: ### `deploy-restart` -Validates a kubeconfig, installs kubectl, and performs a `rollout restart` of a Kubernetes Deployment. +Validates a kubeconfig, installs kubectl, and performs a `rollout restart` of a Kubernetes Deployment. By default it then waits for the rollout to finish (`kubectl rollout status`) and fails the step — printing the deployment, pods and recent events — if the new pods don't become ready in time. | Input | Required | Default | Description | |---|---|---|---| @@ -100,6 +100,8 @@ Validates a kubeconfig, installs kubectl, and performs a `rollout restart` of a | `deployment_name` | ✅ | — | Deployment name to restart | | `namespace` | ❌ | `default` | Kubernetes namespace | | `kube_version` | ❌ | `stable` | kubectl version (`stable` or e.g. `v1.31.0`) | +| `wait` | ❌ | `true` | Wait for the rollout to complete and fail if it doesn't | +| `timeout` | ❌ | `300s` | Max time to wait for the rollout (kubectl duration, e.g. `5m`) | ### `kubectl-apply` @@ -147,6 +149,8 @@ jobs: # optional: # namespace: default # kube_version: stable + # wait: "true" # wait for rollout status + # timeout: 300s ``` --- diff --git a/deploy-restart/action.yaml b/deploy-restart/action.yaml index 4a528ea..bb79408 100644 --- a/deploy-restart/action.yaml +++ b/deploy-restart/action.yaml @@ -16,6 +16,14 @@ inputs: description: Kubernetes version to download (stable, or a specific version like v1.31.0) required: false default: stable + wait: + description: Wait for the rollout to finish and fail the step if it does not become ready ("true"/"false") + required: false + default: "true" + timeout: + description: How long to wait for the rollout when wait is enabled (kubectl duration, e.g. 300s, 5m) + required: false + default: 300s runs: using: composite @@ -80,9 +88,18 @@ runs: KUBE_VER="${KUBE_VERSION}" fi - # 4. Download kubectl - echo "Downloading kubectl version ${KUBE_VER}..." - curl -LO "https://dl.k8s.io/release/${KUBE_VER}/bin/linux/amd64/kubectl" + # 4. Detect runner architecture and download the matching kubectl + case "$(uname -m)" in + x86_64|amd64) KUBE_ARCH=amd64 ;; + aarch64|arm64) KUBE_ARCH=arm64 ;; + armv7l|armv7) KUBE_ARCH=arm ;; + *) + echo "::error::Unsupported runner architecture: $(uname -m)" + exit 1 + ;; + esac + echo "Downloading kubectl version ${KUBE_VER} (linux/${KUBE_ARCH})..." + curl -fLO "https://dl.k8s.io/release/${KUBE_VER}/bin/linux/${KUBE_ARCH}/kubectl" # 5. Install kubectl to a writable directory if [ -w "/usr/local/bin" ]; then @@ -117,3 +134,37 @@ runs: run: | kubectl --kubeconfig=kubeconfig.yaml rollout restart \ deployment "${DEPLOYMENT_NAME}" -n "${NAMESPACE}" + + - name: Wait for rollout to complete + shell: bash + env: + DEPLOYMENT_NAME: ${{ inputs.deployment_name }} + NAMESPACE: ${{ inputs.namespace }} + WAIT: ${{ inputs.wait }} + TIMEOUT: ${{ inputs.timeout }} + run: | + # strip stray CR (CRLF checkout via core.autocrlf) + WAIT=$(printf '%s' "${WAIT}" | tr -d '\r' | tr '[:upper:]' '[:lower:]') + TIMEOUT=$(printf '%s' "${TIMEOUT}" | tr -d '\r') + + if [ "${WAIT}" != "true" ]; then + echo "wait=${WAIT}; not waiting for rollout." + exit 0 + fi + + echo "Waiting up to ${TIMEOUT} for deployment/${DEPLOYMENT_NAME} in ${NAMESPACE}..." + if kubectl --kubeconfig=kubeconfig.yaml rollout status \ + deployment "${DEPLOYMENT_NAME}" -n "${NAMESPACE}" --timeout="${TIMEOUT}"; then + echo "Rollout of deployment/${DEPLOYMENT_NAME} completed." + exit 0 + fi + + echo "::error::Rollout of deployment/${DEPLOYMENT_NAME} did not complete within ${TIMEOUT}." + echo "== deployment ==" + kubectl --kubeconfig=kubeconfig.yaml describe deployment "${DEPLOYMENT_NAME}" -n "${NAMESPACE}" | tail -n 30 || true + echo "== pods ==" + kubectl --kubeconfig=kubeconfig.yaml get pods -n "${NAMESPACE}" -o wide || true + echo "== recent events ==" + kubectl --kubeconfig=kubeconfig.yaml get events -n "${NAMESPACE}" \ + --sort-by=.lastTimestamp | tail -n 20 || true + exit 1 diff --git a/kubectl-apply/action.yaml b/kubectl-apply/action.yaml index 4eb4a8e..16b23da 100644 --- a/kubectl-apply/action.yaml +++ b/kubectl-apply/action.yaml @@ -89,9 +89,18 @@ runs: KUBE_VER="${KUBE_VERSION}" fi - # 4. Download kubectl - echo "Downloading kubectl version ${KUBE_VER}..." - curl -LO "https://dl.k8s.io/release/${KUBE_VER}/bin/linux/amd64/kubectl" + # 4. Detect runner architecture and download the matching kubectl + case "$(uname -m)" in + x86_64|amd64) KUBE_ARCH=amd64 ;; + aarch64|arm64) KUBE_ARCH=arm64 ;; + armv7l|armv7) KUBE_ARCH=arm ;; + *) + echo "::error::Unsupported runner architecture: $(uname -m)" + exit 1 + ;; + esac + echo "Downloading kubectl version ${KUBE_VER} (linux/${KUBE_ARCH})..." + curl -fLO "https://dl.k8s.io/release/${KUBE_VER}/bin/linux/${KUBE_ARCH}/kubectl" # 5. Install kubectl to a writable directory if [ -w "/usr/local/bin" ]; then