diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index 8fc37d0..dc2919a 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -12,12 +12,12 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 with: persist-credentials: false - name: Run shellcheck - uses: ludeeus/action-shellcheck@2.0.0 + uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0 with: scandir: dev severity: warning diff --git a/dev/README.md b/dev/README.md index bf91dc0..cffa093 100644 --- a/dev/README.md +++ b/dev/README.md @@ -78,7 +78,7 @@ sudo sysctl -w fs.inotify.max_user_watches=524288 ``` To make persistent, add to `/etc/sysctl.d/99-kind.conf`: -``` +```ini fs.inotify.max_user_instances=8192 fs.inotify.max_user_watches=524288 ``` @@ -129,7 +129,7 @@ endif ``` Add `.tools/` to your `.gitignore`: -``` +```bash echo '.tools/' >> .gitignore ``` diff --git a/dev/create-nhc.sh b/dev/create-nhc.sh index ff8384c..4519000 100755 --- a/dev/create-nhc.sh +++ b/dev/create-nhc.sh @@ -1,7 +1,7 @@ #!/bin/bash # Creates a NodeHealthCheck CR that references an available remediator. # Auto-detects deployed remediator templates (SNR, FAR, MDR) and uses the first found. -# Usage: create-nhc.sh [--duration ] +# Usage: create-nhc.sh [--duration ] (e.g. 300s, 5m, 1h) set -euo pipefail @@ -16,6 +16,10 @@ NHC_UNHEALTHY_DURATION="${NHC_UNHEALTHY_DURATION:-300s}" while [[ $# -gt 0 ]]; do case $1 in --duration) + if [[ $# -lt 2 ]]; then + echo "Error: --duration requires a value (e.g. 300s, 5m, 1h)" + exit 1 + fi NHC_UNHEALTHY_DURATION="$2" shift 2 ;; @@ -28,6 +32,12 @@ while [[ $# -gt 0 ]]; do esac done +# Validate duration format +if ! [[ "${NHC_UNHEALTHY_DURATION}" =~ ^[0-9]+(s|m|h)$ ]]; then + echo "Error: NHC_UNHEALTHY_DURATION must be a duration (e.g. 300s, 5m, 1h), got: '${NHC_UNHEALTHY_DURATION}'" + exit 1 +fi + # Check if the NHC CRD exists if ! ${KUBECTL} get crd nodehealthchecks.remediation.medik8s.io &>/dev/null; then echo "Error: NodeHealthCheck CRD not found. Deploy NHC first (make dev-deploy from the NHC directory)." diff --git a/dev/dev.mk b/dev/dev.mk index 3a7ae17..c3fb4db 100644 --- a/dev/dev.mk +++ b/dev/dev.mk @@ -52,7 +52,7 @@ TTL_SH_TTL ?= 2h ifeq ($(DEV_REGISTRY),local) DEV_IMG ?= localhost:5000/medik8s/$(OPERATOR_NAME):dev else - DEV_IMG ?= ttl.sh/medik8s-$(OPERATOR_NAME)-$(shell echo $$USER | head -c 8):$(TTL_SH_TTL) + DEV_IMG ?= ttl.sh/medik8s-$(OPERATOR_NAME)-$(shell head -c 32 /dev/urandom | base64 | tr -dc 'a-z0-9' | head -c 8):$(TTL_SH_TTL) endif # Detect kubectl or oc @@ -127,12 +127,13 @@ ifeq ($(DEV_REGISTRY),local) fi; \ done; \ restore() { for f in $$patched; do sed -i.bak 's/imagePullPolicy: IfNotPresent/imagePullPolicy: Always/' "$$f" && rm -f "$$f.bak"; done; }; \ - trap restore EXIT; \ + TMPTAR=$$(mktemp /tmp/dev-image-XXXXXX.tar); \ + cleanup() { rm -f "$$TMPTAR"; restore; }; \ + trap cleanup EXIT; \ $(CONTAINER_TOOL) build -t $(DEV_IMG) . && \ - $(CONTAINER_TOOL) save -o /tmp/dev-image-$(OPERATOR_NAME).tar $(DEV_IMG) && \ + $(CONTAINER_TOOL) save -o "$$TMPTAR" $(DEV_IMG) && \ KIND_EXPERIMENTAL_PROVIDER=$(if $(filter podman,$(CONTAINER_TOOL)),podman,docker) \ - kind load image-archive /tmp/dev-image-$(OPERATOR_NAME).tar --name $(MEDIK8S_CLUSTER_NAME) && \ - rm -f /tmp/dev-image-$(OPERATOR_NAME).tar + kind load image-archive "$$TMPTAR" --name $(MEDIK8S_CLUSTER_NAME) else $(CONTAINER_TOOL) build -t $(DEV_IMG) . $(CONTAINER_TOOL) push $(DEV_IMG) @@ -149,9 +150,9 @@ dev-deploy: dev-build install $(if $(ENVSUBST),envsubst) ## Build, load image, i cd config/manager && $(KUSTOMIZE) edit set image controller=$(DEV_IMG) && cd ../.. && \ ENVSUBST_BIN="$(ENVSUBST)"; \ if [ -n "$$ENVSUBST_BIN" ] && [ -x "$$ENVSUBST_BIN" ]; then \ - export IMG=$(DEV_IMG) && $(KUSTOMIZE) build config/default 2>&1 | grep -v "Warning: 'commonLabels'" | $$ENVSUBST_BIN | $(KUBECTL) apply -f -; \ + export IMG=$(DEV_IMG) && $(KUSTOMIZE) build config/default 2> >(grep -v "Warning: 'commonLabels'" >&2) | $$ENVSUBST_BIN | $(KUBECTL) apply -f -; \ else \ - $(KUSTOMIZE) build config/default 2>&1 | grep -v "Warning: 'commonLabels'" | $(KUBECTL) apply -f -; \ + $(KUSTOMIZE) build config/default 2> >(grep -v "Warning: 'commonLabels'" >&2) | $(KUBECTL) apply -f -; \ fi @# Detect the operator namespace from kustomization files (reliable, no cluster query needed). @# The namespace may be in config/default/ or in a component/patch kustomization.yaml. @@ -302,7 +303,7 @@ dev-describe: ## Full summary of all medik8s resources (nodes, pods, CRs, leases .PHONY: dev-shell dev-shell: ## Open a shell on a Kind node (use NODE=, default: first worker) - @NODES=$$(kind get nodes --name $(MEDIK8S_CLUSTER_NAME) 2>/dev/null); \ + @NODES=$$(KIND_EXPERIMENTAL_PROVIDER=$(CONTAINER_TOOL) kind get nodes --name $(MEDIK8S_CLUSTER_NAME) 2>/dev/null); \ if [ -z "$$NODES" ]; then \ NODES=$$($(KUBECTL) get nodes --no-headers -o custom-columns=NAME:.metadata.name 2>/dev/null); \ fi; \ @@ -317,6 +318,10 @@ dev-shell: ## Open a shell on a Kind node (use NODE=, default: first worke if [ -z "$$TARGET" ]; then \ TARGET=$$(echo "$$NODES" | head -1); \ fi; \ + if ! echo "$$NODES" | grep -qx "$$TARGET"; then \ + echo "Error: '$$TARGET' is not a node in the cluster. Available: $$(echo $$NODES | tr '\n' ' ')"; \ + exit 1; \ + fi; \ echo "Opening shell on $$TARGET..."; \ echo " (type 'exit' to return)"; \ $(CONTAINER_TOOL) exec -it "$$TARGET" bash diff --git a/dev/enable-certmanager.sh b/dev/enable-certmanager.sh index 7088672..f3eb506 100755 --- a/dev/enable-certmanager.sh +++ b/dev/enable-certmanager.sh @@ -75,8 +75,8 @@ ${KUBECTL} wait --for=condition=Ready certificate/serving-cert -n "${NAMESPACE}" # Annotate webhook configurations for CA injection for wh_type in mutatingwebhookconfigurations validatingwebhookconfigurations; do for wh in $(${KUBECTL} get "${wh_type}" -o name 2>/dev/null); do - # Only annotate webhooks that reference services in our namespace - if ${KUBECTL} get "${wh}" -o yaml 2>/dev/null | grep -q "namespace: ${NAMESPACE}"; then + # Only annotate webhooks whose clientConfig targets our namespace + if ${KUBECTL} get "${wh}" -o jsonpath='{.webhooks[*].clientConfig.service.namespace}' 2>/dev/null | grep -qw "${NAMESPACE}"; then ${KUBECTL} annotate "${wh}" cert-manager.io/inject-ca-from="${NAMESPACE}/serving-cert" --overwrite 2>/dev/null || true fi done @@ -86,7 +86,9 @@ done if ${KUBECTL} get deployment "${DEPLOY_NAME}" -n "${NAMESPACE}" -o jsonpath='{.spec.template.spec.volumes[*].name}' 2>/dev/null | grep -q cert; then echo " Deployment already has TLS volume mount — skipping patch." else - echo " Patching deployment to mount webhook TLS secret..." + CONTAINER_NAME=$(${KUBECTL} get deployment "${DEPLOY_NAME}" -n "${NAMESPACE}" \ + -o jsonpath='{.spec.template.spec.containers[0].name}') + echo " Patching deployment to mount webhook TLS secret (container: ${CONTAINER_NAME})..." ${KUBECTL} patch deployment "${DEPLOY_NAME}" -n "${NAMESPACE}" --type=strategic -p='{ "spec": { "template": { @@ -99,7 +101,7 @@ else } }], "containers": [{ - "name": "manager", + "name": "'"${CONTAINER_NAME}"'", "volumeMounts": [{ "name": "cert", "mountPath": "/tmp/k8s-webhook-server/serving-certs", diff --git a/dev/setup.sh b/dev/setup.sh index 4df89f2..bfd8f3a 100755 --- a/dev/setup.sh +++ b/dev/setup.sh @@ -41,6 +41,10 @@ while [[ $# -gt 0 ]]; do shift ;; --name) + if [[ $# -lt 2 ]]; then + echo "Error: --name requires a cluster name argument." + exit 1 + fi CLUSTER_NAME="$2" shift 2 ;; @@ -241,11 +245,16 @@ else echo " Namespace 'medik8s-leases' already exists." fi -echo "=== Installing cert-manager ===" +CERT_MANAGER_VERSION="${CERT_MANAGER_VERSION:-v1.17.2}" +if ! [[ "${CERT_MANAGER_VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: CERT_MANAGER_VERSION must be a semver tag (e.g. v1.17.2), got: '${CERT_MANAGER_VERSION}'" + exit 1 +fi +echo "=== Installing cert-manager ${CERT_MANAGER_VERSION} ===" if ${KUBECTL} get crd certificates.cert-manager.io &>/dev/null; then echo " cert-manager already installed (CRDs found)." else - ${KUBECTL} apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml + ${KUBECTL} apply -f "https://github.com/cert-manager/cert-manager/releases/download/${CERT_MANAGER_VERSION}/cert-manager.yaml" echo " Waiting for cert-manager to be ready..." ${KUBECTL} wait --for=condition=Available deployment --all -n cert-manager --timeout=120s fi diff --git a/dev/simulate-failure.sh b/dev/simulate-failure.sh index 8bf579a..ffb43b6 100755 --- a/dev/simulate-failure.sh +++ b/dev/simulate-failure.sh @@ -34,7 +34,7 @@ get_worker_nodes() { # Try kind first; if it can't see the cluster (e.g. created with sudo), # fall back to kubectl node names (which match Kind container names). local nodes - nodes=$(kind get nodes --name "${CLUSTER_NAME}" 2>/dev/null | grep worker | sort) + nodes=$(KIND_EXPERIMENTAL_PROVIDER="${CONTAINER_TOOL}" kind get nodes --name "${CLUSTER_NAME}" 2>/dev/null | grep worker | sort) if [ -z "$nodes" ]; then nodes=$(${KUBECTL} get nodes -l node-role.kubernetes.io/worker --no-headers -o custom-columns=NAME:.metadata.name 2>/dev/null | sort) fi diff --git a/dev/teardown.sh b/dev/teardown.sh index 357cc2d..6362294 100755 --- a/dev/teardown.sh +++ b/dev/teardown.sh @@ -29,6 +29,8 @@ if ! command -v kind &>/dev/null; then exit 1 fi +export KIND_EXPERIMENTAL_PROVIDER="${CONTAINER_TOOL}" + if kind get clusters 2>/dev/null | grep -q "^${CLUSTER_NAME}$"; then echo "=== Deleting Kind cluster '${CLUSTER_NAME}' ===" kind delete cluster --name "${CLUSTER_NAME}"