Skip to content

Deliver Airflow cache wrappers safely

Purpose. Install, verify, update, and roll back the reviewed wrapper ConfigMap without losing predecessor evidence.

Audience. Platform engineers managing Kubernetes ConfigMaps and parser workload rollouts.

Back to Kubernetes cache deployment overview · Next likely task: deploy and verify the Helm release.

Deliver the wrappers

The paths referenced by Helm values must exist. Store the two reviewed scripts in a ConfigMap; do not rely on files that happen to be present in one image:

apiVersion: v1
kind: ConfigMap
metadata:
  name: dpone-airflow-cache-scripts
data:
  cache-init-fail-open.sh: |
    set +e
    timeout "${DPONE_PACK_SYNC_TIMEOUT_SECONDS:-20}" \
      dpone airflow desired-state reconcile \
      --connection-type airflow \
      --connection-id "${DPONE_AIRFLOW_PACK_READER_CONNECTION_ID}" \
      --artifact-connection-type airflow \
      --artifact-connection-id "${DPONE_AIRFLOW_PACK_READER_CONNECTION_ID}" \
      --cache-root "${DPONE_AIRFLOW_PACK_CACHE_DIR}" \
      --max-total-bytes "${DPONE_PACK_CACHE_MAX_TOTAL_BYTES:-536870912}"
    printf 'dpone exact-cache init exit=%s; startup remains fail-open\n' "$?"
    exit 0
  cache-watch.sh: |
    set +e
    trap 'exit 0' TERM INT
    publish_retention_status() {
      publication_tmp="$(mktemp "$(dirname "$2")/.retention-publication.XXXXXX" 2>/dev/null)" || return 4
      dpone airflow cache-status-publish \
        --status-root "$(dirname "$2")" \
        --source "$(basename "$1")" \
        --target "$(basename "$2")" \
        --failure-marker "$4" \
        --expected-schema "$3" \
        --format json >"${publication_tmp}"
      publication_rc=$?
      if [ -s "${publication_tmp}" ]; then
        chmod 0600 "${publication_tmp}" && mv -f "${publication_tmp}" "$5" || publication_rc=4
      else
        rm -f "${publication_tmp}"
      fi
      return "${publication_rc}"
    }
    retention_error_code() {
      python3 -c 'import json,sys; p=json.load(open(sys.argv[1])); errors=p.get("errors") or []; print((errors[0].get("code") if errors else None) or p.get("error_code") or ("ok" if p.get("passed") is True or p.get("status") in {"ok", "needs_cleanup", "committed"} else "unavailable"))' "$1" 2>/dev/null || printf 'unavailable\n'
    }
    cycle=0
    while :; do
      timeout "${DPONE_PACK_SYNC_TIMEOUT_SECONDS:-20}" \
        dpone airflow desired-state reconcile \
        --connection-type airflow \
        --connection-id "${DPONE_AIRFLOW_PACK_READER_CONNECTION_ID}" \
        --artifact-connection-type airflow \
        --artifact-connection-id "${DPONE_AIRFLOW_PACK_READER_CONNECTION_ID}" \
        --cache-root "${DPONE_AIRFLOW_PACK_CACHE_DIR}" \
        --max-total-bytes "${DPONE_PACK_CACHE_MAX_TOTAL_BYTES:-536870912}"
      rc=$?
      printf 'dpone exact-cache watch exit=%s\n' "${rc}"
      cycle=$((cycle + 1))
      if [ "${rc}" -eq 0 ] \
        && [ $((cycle % ${DPONE_PACK_RETENTION_INTERVAL_CYCLES:-60})) -eq 0 ] \
        && [ -f /opt/airflow/.dpone-ack/loader-ack.json ]; then
        retention_status_dir="${DPONE_AIRFLOW_PACK_CACHE_DIR}/status"
        umask 077
        mkdir -p "${retention_status_dir}"
        chmod 0700 "${retention_status_dir}"
        status_dir_rc=$?
        if [ "${status_dir_rc}" -ne 0 ]; then
          printf 'dpone cache retention status directory unavailable exit=%s\n' "${status_dir_rc}"
          sleep "${DPONE_PACK_SYNC_INTERVAL_SECONDS:-60}" & wait $!
          continue
        fi
        plan_tmp="$(mktemp "${retention_status_dir}/.retention-plan.XXXXXX" 2>/dev/null)"
        if [ -z "${plan_tmp}" ]; then
          printf 'dpone cache retention plan temp unavailable\n'
          sleep "${DPONE_PACK_SYNC_INTERVAL_SECONDS:-60}" & wait $!
          continue
        fi
        plan_status="${retention_status_dir}/last-retention-plan.json"
        dpone airflow cache-retention-plan \
          --cache-root "${DPONE_AIRFLOW_PACK_CACHE_DIR}" \
          --environment "${DPONE_ENVIRONMENT:-dev}" \
          --evidence-file /opt/airflow/.dpone-ack/loader-ack.json \
          --format json >"${plan_tmp}"
        plan_rc=$?
        plan_code="$(retention_error_code "${plan_tmp}")"
        plan_publication_status="${retention_status_dir}/last-retention-plan-publication.json"
        publish_retention_status \
          "${plan_tmp}" "${plan_status}" \
          dpone.deployment-cache-retention-plan.v1 \
          last-retention-plan-publication-failure.json \
          "${plan_publication_status}"
        publish_rc=$?
        publish_code="$(retention_error_code "${plan_publication_status}")"
        rm -f "${plan_tmp}"
        printf 'dpone cache retention plan exit=%s code=%s publish=%s publish_code=%s evidence=%s publication=%s\n' \
          "${plan_rc}" "${plan_code}" "${publish_rc}" "${publish_code}" \
          "${plan_status}" "${plan_publication_status}"
        if [ "${plan_rc}" -eq 0 ] && [ "${publish_rc}" -eq 0 ]; then
          plan_sha256="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["plan_sha256"])' "${plan_status}" 2>/dev/null)"
          approved_sha256="${DPONE_CACHE_RETENTION_APPROVED_PLAN_SHA256:-}"
          approved_review_id="${DPONE_CACHE_RETENTION_REVIEW_ID:-}"
          if [ -z "${approved_sha256}" ]; then
            printf 'dpone cache retention plan-only reason=approval_missing plan_sha256=%s\n' "${plan_sha256}"
          elif [ -z "${approved_review_id}" ]; then
            printf 'dpone cache retention plan-only reason=review_id_missing plan_sha256=%s\n' "${plan_sha256}"
          elif [ "${approved_sha256}" != "${plan_sha256}" ]; then
            printf 'dpone cache retention plan-only reason=approval_mismatch approved=%s actual=%s\n' \
              "${approved_sha256}" "${plan_sha256}"
          else
            apply_tmp="$(mktemp "${retention_status_dir}/.retention-apply.XXXXXX" 2>/dev/null)"
            apply_status="${retention_status_dir}/last-retention-apply.json"
            if [ -z "${apply_tmp}" ]; then
              printf 'dpone cache retention apply temp unavailable\n'
            else
              dpone airflow cache-retention-apply \
              --cache-root "${DPONE_AIRFLOW_PACK_CACHE_DIR}" \
              --environment "${DPONE_ENVIRONMENT:-dev}" \
              --evidence-file /opt/airflow/.dpone-ack/loader-ack.json \
              --expected-plan-sha256 "${approved_sha256}" \
              --review-id "${approved_review_id}" \
              --loader-ack-file /opt/airflow/.dpone-ack/loader-ack.json \
              --promoted-by "${DPONE_CACHE_RETENTION_IDENTITY}" \
              --allowed-promoter "${DPONE_CACHE_RETENTION_IDENTITY}" \
              --confirm-delete \
              --evidence-version v3 \
              --format json >"${apply_tmp}"
              apply_rc=$?
              apply_code="$(retention_error_code "${apply_tmp}")"
              apply_publication_status="${retention_status_dir}/last-retention-apply-publication.json"
              publish_retention_status \
                "${apply_tmp}" "${apply_status}" \
                dpone.deployment-cache-retention-apply.v3 \
                last-retention-apply-publication-failure.json \
                "${apply_publication_status}"
              apply_publish_rc=$?
              apply_publish_code="$(retention_error_code "${apply_publication_status}")"
              rm -f "${apply_tmp}"
              printf 'dpone cache retention apply exit=%s code=%s publish=%s publish_code=%s evidence=%s publication=%s\n' \
                "${apply_rc}" "${apply_code}" "${apply_publish_rc}" "${apply_publish_code}" \
                "${apply_status}" "${apply_publication_status}"
            fi
          fi
        else
          printf 'dpone cache retention skipped apply code=%s\n' \
            "${plan_code}"
        fi
      fi
      sleep "${DPONE_PACK_SYNC_INTERVAL_SECONDS:-60}" & wait $!
    done

Treat this ConfigMap as reviewed infrastructure, not an ad-hoc prerequisite. Save the exact block above as dpone-airflow-cache-scripts.yaml, then use one reproducible lifecycle for first install, update and rollback:

set -euo pipefail

: "${KUBE_CONTEXT:?set the reviewed kubeconfig context}"
: "${AIRFLOW_NAMESPACE:?set the namespace used by the reviewed Airflow Helm release}"
: "${DPONE_CACHE_SCRIPTS_MANIFEST:?set the reviewed wrapper ConfigMap manifest}"
: "${DPONE_CACHE_SCRIPTS_APPLY_ACK:?set to review-and-apply-cache-scripts}"
: "${DPONE_CACHE_PARSER_WORKLOAD:?set exact deployment/name or statefulset/name}"
: "${DPONE_CACHE_WATCH_CONTAINER:?set the exact cache-watch container name}"
[ "${DPONE_CACHE_SCRIPTS_APPLY_ACK}" = review-and-apply-cache-scripts ] || exit 2
[ -f "${DPONE_CACHE_SCRIPTS_MANIFEST}" ]
case "${DPONE_CACHE_PARSER_WORKLOAD}" in deployment/*|statefulset/*) ;; *) exit 2 ;; esac

evidence_dir="$(mktemp -d dpone-airflow-cache-scripts.XXXXXX)"
candidate_list="${evidence_dir}/candidate-list.json"
candidate_data="${evidence_dir}/candidate-data.json"
observed="${evidence_dir}/observed.json"
observed_data="${evidence_dir}/observed-data.json"
workload_before="${evidence_dir}/workload-before.json"
workload_after="${evidence_dir}/workload-after.json"
kubectl --context "${KUBE_CONTEXT}" --namespace "${AIRFLOW_NAMESPACE}" \
  create --dry-run=client -f "${DPONE_CACHE_SCRIPTS_MANIFEST}" -o json \
  | jq -s '.' >"${candidate_list}"
jq -e --arg namespace "${AIRFLOW_NAMESPACE}" '
  length == 1 and
  .[0].apiVersion == "v1" and
  .[0].kind == "ConfigMap" and
  .[0].metadata.name == "dpone-airflow-cache-scripts" and
  ((.[0].metadata.namespace // $namespace) == $namespace) and
  ((.[0].data | keys | sort) == ["cache-init-fail-open.sh", "cache-watch.sh"])
' "${candidate_list}" >/dev/null
jq -S '.[0].data' "${candidate_list}" >"${candidate_data}"
kubectl --context "${KUBE_CONTEXT}" --namespace "${AIRFLOW_NAMESPACE}" \
  get "${DPONE_CACHE_PARSER_WORKLOAD}" -o json >"${workload_before}"
jq -e '.metadata.uid != null and (.spec.selector.matchLabels | type == "object" and length > 0)' \
  "${workload_before}" >/dev/null
pod_selector="$(jq -er '.spec.selector.matchLabels | to_entries | sort_by(.key) | map("\(.key)=\(.value)") | join(",")' \
  "${workload_before}")"

previous_tmp="${evidence_dir}/previous.tmp"
kubectl --context "${KUBE_CONTEXT}" --namespace "${AIRFLOW_NAMESPACE}" \
  get configmap dpone-airflow-cache-scripts --ignore-not-found -o json \
  >"${previous_tmp}"
if [ -s "${previous_tmp}" ]; then
  mv "${previous_tmp}" "${evidence_dir}/previous.json"
  jq -S '.data' "${evidence_dir}/previous.json" >"${evidence_dir}/previous-data.json"
else
  rm -f "${previous_tmp}"
  printf 'first_install=true\n' >"${evidence_dir}/previous.absent"
fi

kubectl --context "${KUBE_CONTEXT}" --namespace "${AIRFLOW_NAMESPACE}" \
  apply --dry-run=server -f "${DPONE_CACHE_SCRIPTS_MANIFEST}" >/dev/null
diff_rc=0
kubectl --context "${KUBE_CONTEXT}" --namespace "${AIRFLOW_NAMESPACE}" \
  diff -f "${DPONE_CACHE_SCRIPTS_MANIFEST}" \
  >"${evidence_dir}/kubectl.diff" || diff_rc=$?
[ "${diff_rc}" -le 1 ] || exit "${diff_rc}"
cat "${evidence_dir}/kubectl.diff"
kubectl --context "${KUBE_CONTEXT}" --namespace "${AIRFLOW_NAMESPACE}" \
  apply --server-side --field-manager=dpone-airflow-cache-scripts \
  -f "${DPONE_CACHE_SCRIPTS_MANIFEST}"
kubectl --context "${KUBE_CONTEXT}" --namespace "${AIRFLOW_NAMESPACE}" \
  get configmap dpone-airflow-cache-scripts -o json \
  >"${observed}"
jq -e --arg namespace "${AIRFLOW_NAMESPACE}" '
  .apiVersion == "v1" and
  .kind == "ConfigMap" and
  .metadata.name == "dpone-airflow-cache-scripts" and
  ((.metadata.namespace // $namespace) == $namespace) and
  ((.data | keys | sort) == ["cache-init-fail-open.sh", "cache-watch.sh"])
' "${observed}" >/dev/null
jq -S '.data' "${observed}" >"${observed_data}"
cmp -- "${candidate_data}" "${observed_data}"
kubectl --context "${KUBE_CONTEXT}" --namespace "${AIRFLOW_NAMESPACE}" \
  rollout restart "${DPONE_CACHE_PARSER_WORKLOAD}"
kubectl --context "${KUBE_CONTEXT}" --namespace "${AIRFLOW_NAMESPACE}" \
  rollout status "${DPONE_CACHE_PARSER_WORKLOAD}" --timeout=10m
kubectl --context "${KUBE_CONTEXT}" --namespace "${AIRFLOW_NAMESPACE}" \
  get "${DPONE_CACHE_PARSER_WORKLOAD}" -o json >"${workload_after}"
jq -e --arg uid "$(jq -r '.metadata.uid' "${workload_before}")" \
  '.metadata.uid == $uid' "${workload_after}" >/dev/null
kubectl --context "${KUBE_CONTEXT}" --namespace "${AIRFLOW_NAMESPACE}" \
  get pods -l "${pod_selector}" -o json >"${evidence_dir}/replacement-pods.json"
jq -e '
  (.items | length > 0) and
  all(.items[]; any(.status.conditions[]?; .type == "Ready" and .status == "True"))
' "${evidence_dir}/replacement-pods.json" >/dev/null
jq -r '.items[] | [.metadata.name, .metadata.uid] | @tsv' \
  "${evidence_dir}/replacement-pods.json" >"${evidence_dir}/replacement-pods.tsv"
python3 - "${candidate_data}" >"${evidence_dir}/expected-script-sha256.txt" <<'PY'
import hashlib
import json
import sys

with open(sys.argv[1], encoding="utf-8") as stream:
    scripts = json.load(stream)
for name, content in sorted(scripts.items()):
    digest = hashlib.sha256(content.encode()).hexdigest()
    print(f"{digest}  /opt/dpone/bin/{name}")
PY
while IFS=$'\t' read -r pod pod_uid; do
  [ -n "${pod}" ] && [ -n "${pod_uid}" ]
  mounted="${evidence_dir}/mounted-script-sha256-${pod}.txt"
  kubectl --context "${KUBE_CONTEXT}" --namespace "${AIRFLOW_NAMESPACE}" \
    exec "pod/${pod}" -c "${DPONE_CACHE_WATCH_CONTAINER}" -- \
    sha256sum -- /opt/dpone/bin/cache-init-fail-open.sh /opt/dpone/bin/cache-watch.sh \
    | sort >"${mounted}"
  cmp -- "${evidence_dir}/expected-script-sha256.txt" "${mounted}"
done <"${evidence_dir}/replacement-pods.tsv"
(cd "${evidence_dir}" && find . -type f ! -name SHA256SUMS -print0 \
  | sort -z | xargs -0 sha256sum >SHA256SUMS && sha256sum -c SHA256SUMS)
chmod 0400 "${evidence_dir}"/*
printf 'cache script ConfigMap evidence: %s\n' "${evidence_dir}"

The lifecycle records the exact previous live object, or an explicit previous.absent marker, before mutation. Applying a ConfigMap alone is not a runtime activation: the exact parser workload is rolled out, its stable UID and selector are checked, and every replacement Pod proves the mounted script hashes in the named watch container. Rollback reconstructs the fixed-name ConfigMap from the captured previous .data, repeats candidate validation, server dry-run, diff and apply, then performs the same workload rollout and mounted-byte verification. A first-install rollback deletes only configmap/dpone-airflow-cache-scripts after the same explicit acknowledgement and rolls the workload so stale projected bytes cannot remain in a running process. Never apply the captured multi-resource input or edit the live ConfigMap. Retain predecessor, candidate, diff, workload/Pod identities, mounted hashes and SHA256SUMS with the corresponding Helm revision evidence.