Skip to content

Airgap Capability - #46

Open
day0hero wants to merge 4 commits into
mainfrom
airgap_pr
Open

day0hero wants to merge 4 commits into
mainfrom
airgap_pr

Conversation

@day0hero

@day0hero day0hero commented Nov 3, 2026

Copy link
Copy Markdown

This PR adds the ability to discover the images necessary for running
in an airgap environment.

It will scrape the pattern repository and generate the imagesetconfig.yaml
for mirroring the proper images.

@dminnear-rh dminnear-rh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall but I'd like the Makefile-common logic moved to rhvp.cluster_utils if you're happy with that

}

// WriteChecklist writes CHECKLIST.md.
func WriteChecklist(inv *PatternInventory, opts *Options, outputDir string) error {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we using string.Builder here for making this file rather than a template like we do in WriteMirrorCommands above?

Comment on lines +81 to +91
func dashVersion(ocpVersion string) string {
out := make([]rune, 0, len(ocpVersion))
for _, r := range ocpVersion {
if r == '.' {
out = append(out, '-')
} else {
out = append(out, r)
}
}
return string(out)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use this helper rather than

dashVersion := strings.Replaceall(ocpVersion, '.', '-')

Comment on lines +66 to +270

##@ Disconnected / Airgap Tasks
# Optional overrides for airgap-init (passed through when set).
# AIRGAP_OCP_VERSION adds mirror.platform channel stable-<version> when set.
# AIRGAP_REGISTRY is optional for values-disconnected / mirror-commands helpers.
AIRGAP_OCP_VERSION ?=
AIRGAP_REGISTRY ?=
AIRGAP_OUTPUT_DIR ?= ./airgap-out
AIRGAP_PATTERNIZER_IMAGE ?= quay.io/validatedpatterns/patternizer
AIRGAP_DOWNLOAD_OC_MIRROR ?= false
AIRGAP_EXTRA_OPTS ?=
# install-airgap / airgap-bootstrap (values-secret airgap: section)
AIRGAP_BOOTSTRAP_DIR ?= ./airgap-bootstrap
AIRGAP_SECRETS_FILE ?=
AIRGAP_PATTERNIZER_BIN ?=
AIRGAP_WAIT_SECONDS ?= 1200
AIRGAP_WAIT_INTERVAL ?= 15
AIRGAP_ARGOCD_NS ?= vp-gitops
AIRGAP_REPO_SERVER ?= $(AIRGAP_ARGOCD_NS)-repo-server

.PHONY: airgap-init
airgap-init: ## Generate oc-mirror imagesetconfig.yaml and disconnected init artifacts
@$(ANSIBLE_RUN) rhvp.cluster_utils.airgap_init \
-e airgap_ocp_version="$(AIRGAP_OCP_VERSION)" \
-e airgap_registry="$(AIRGAP_REGISTRY)" \
-e airgap_output_dir=$(AIRGAP_OUTPUT_DIR) \
-e airgap_patternizer_image=$(AIRGAP_PATTERNIZER_IMAGE) \
-e airgap_download_oc_mirror=$(AIRGAP_DOWNLOAD_OC_MIRROR) \
-e airgap_extra_opts="$(AIRGAP_EXTRA_OPTS)"

# Resolve patternizer: explicit bin, PATH, ~/patternizer, or container image.
# Usage in a recipe: $(call AIRGAP_PATTERNIZER_EXEC,airgap bootstrap "$(CURDIR)" --output-dir "$(AIRGAP_BOOTSTRAP_DIR)")
define AIRGAP_PATTERNIZER_EXEC
set -euo pipefail; \
BIN="$(AIRGAP_PATTERNIZER_BIN)"; \
if [ -z "$$BIN" ] && command -v patternizer >/dev/null 2>&1; then BIN="$$(command -v patternizer)"; fi; \
if [ -z "$$BIN" ] && [ -x "$(HOME)/patternizer" ]; then BIN="$(HOME)/patternizer"; fi; \
if [ -z "$$BIN" ] && [ -x "$(HOME)/bin/patternizer" ]; then BIN="$(HOME)/bin/patternizer"; fi; \
if [ -n "$$BIN" ]; then \
echo "==> Using patternizer at $$BIN"; \
"$$BIN" $(1); \
elif command -v podman >/dev/null 2>&1; then \
echo "==> Using podman $(AIRGAP_PATTERNIZER_IMAGE)"; \
podman run --rm --network=host \
-v "$(CURDIR):$(CURDIR):z" -v "$(HOME):$(HOME):z" -w "$(CURDIR)" \
-e HOME="$(HOME)" \
$(AIRGAP_PATTERNIZER_IMAGE) $(1); \
elif command -v docker >/dev/null 2>&1; then \
echo "==> Using docker $(AIRGAP_PATTERNIZER_IMAGE)"; \
docker run --rm --network=host \
-v "$(CURDIR):$(CURDIR):z" -v "$(HOME):$(HOME):z" -w "$(CURDIR)" \
-e HOME="$(HOME)" \
$(AIRGAP_PATTERNIZER_IMAGE) $(1); \
else \
echo "patternizer not found. Install it, set AIRGAP_PATTERNIZER_BIN, or install podman/docker." >&2; \
exit 1; \
fi
endef

.PHONY: airgap-bootstrap
airgap-bootstrap: ## Generate airgap trust artifacts from values-secret airgap: section
@SECRETS_OPT=""; \
if [ -n "$(AIRGAP_SECRETS_FILE)" ]; then SECRETS_OPT="--secrets-file $(AIRGAP_SECRETS_FILE)"; fi; \
$(call AIRGAP_PATTERNIZER_EXEC,airgap bootstrap "$(CURDIR)" --output-dir "$(AIRGAP_BOOTSTRAP_DIR)" $$SECRETS_OPT)

.PHONY: airgap-trust-pre
airgap-trust-pre: airgap-bootstrap ## Apply cluster Proxy CA trust (before pattern install)
@test -x "$(AIRGAP_BOOTSTRAP_DIR)/apply-airgap.sh" || { echo "missing $(AIRGAP_BOOTSTRAP_DIR)/apply-airgap.sh; run make airgap-bootstrap" >&2; exit 1; }
@AIRGAP_WAIT_SECONDS="$(AIRGAP_WAIT_SECONDS)" AIRGAP_WAIT_INTERVAL="$(AIRGAP_WAIT_INTERVAL)" \
"$(AIRGAP_BOOTSTRAP_DIR)/apply-airgap.sh" pre

.PHONY: airgap-wait-gitops
airgap-wait-gitops: ## Wait for GitOps namespace / Argo CD (runs while install is in progress)
@set -euo pipefail; \
NS="$(AIRGAP_ARGOCD_NS)"; \
REPO="$(AIRGAP_REPO_SERVER)"; \
WAIT="$(AIRGAP_WAIT_SECONDS)"; \
INTERVAL="$(AIRGAP_WAIT_INTERVAL)"; \
if ! command -v oc >/dev/null 2>&1; then echo "oc is required" >&2; exit 1; fi; \
echo "==> Waiting up to $${WAIT}s for namespace/$${NS}"; \
elapsed=0; \
while [ "$$elapsed" -lt "$$WAIT" ]; do \
if oc get namespace "$$NS" >/dev/null 2>&1; then echo " namespace/$${NS} is present"; break; fi; \
sleep "$$INTERVAL"; \
elapsed=$$((elapsed + INTERVAL)); \
if [ "$$elapsed" -ge "$$WAIT" ]; then echo "timed out waiting for namespace/$${NS}" >&2; exit 1; fi; \
done; \
echo "==> Waiting up to $${WAIT}s for configmap/argocd-tls-certs-cm in $${NS} (optional if only repo-creds)"; \
elapsed=0; \
while [ "$$elapsed" -lt "$$WAIT" ]; do \
if oc get configmap argocd-tls-certs-cm -n "$$NS" >/dev/null 2>&1; then \
echo " configmap/argocd-tls-certs-cm is present"; \
break; \
fi; \
sleep "$$INTERVAL"; \
elapsed=$$((elapsed + INTERVAL)); \
if [ "$$elapsed" -ge "$$WAIT" ]; then \
echo " warning: argocd-tls-certs-cm not ready yet; airgap-trust-post will keep waiting" >&2; \
break; \
fi; \
done; \
echo "==> Waiting up to $${WAIT}s for deployment/$${REPO} in $${NS} (optional)"; \
elapsed=0; \
while [ "$$elapsed" -lt "$$WAIT" ]; do \
if oc get deployment "$$REPO" -n "$$NS" >/dev/null 2>&1; then \
echo " deployment/$${REPO} is present"; \
break; \
fi; \
sleep "$$INTERVAL"; \
elapsed=$$((elapsed + INTERVAL)); \
if [ "$$elapsed" -ge "$$WAIT" ]; then \
echo " warning: deployment/$${REPO} not ready yet; post will keep waiting" >&2; \
break; \
fi; \
done; \
echo "==> GitOps wait complete; running airgap-trust-post next"

.PHONY: airgap-trust-post
airgap-trust-post: ## Apply Argo CD TLS + repo-creds (waits for vp-gitops / Argo CD resources)
@test -x "$(AIRGAP_BOOTSTRAP_DIR)/apply-airgap.sh" || { echo "missing $(AIRGAP_BOOTSTRAP_DIR)/apply-airgap.sh; run make airgap-bootstrap first" >&2; exit 1; }
@AIRGAP_WAIT_SECONDS="$(AIRGAP_WAIT_SECONDS)" AIRGAP_WAIT_INTERVAL="$(AIRGAP_WAIT_INTERVAL)" \
"$(AIRGAP_BOOTSTRAP_DIR)/apply-airgap.sh" post

.PHONY: airgap-restore-proxy
airgap-restore-proxy: ## Restore proxy/cluster from airgap bootstrap backup
@test -x "$(AIRGAP_BOOTSTRAP_DIR)/restore-proxy-cluster.sh" || { echo "missing $(AIRGAP_BOOTSTRAP_DIR)/restore-proxy-cluster.sh" >&2; exit 1; }
@"$(AIRGAP_BOOTSTRAP_DIR)/restore-proxy-cluster.sh"

# Stop a background make install (and its descendants). Used by Ctrl-C cleanup and airgap-abort.
# Args: $1 = root pid. Sends TERM to the process group / tree, waits briefly, then KILL.
define AIRGAP_STOP_INSTALL
pid="$(1)"; \
if [ -n "$$pid" ] && kill -0 "$$pid" 2>/dev/null; then \
echo "==> Stopping background make install (pid $$pid)"; \
kill -TERM -"$$pid" 2>/dev/null || kill -TERM "$$pid" 2>/dev/null || true; \
if command -v pkill >/dev/null 2>&1; then pkill -TERM -P "$$pid" 2>/dev/null || true; fi; \
i=0; \
while [ "$$i" -lt 8 ] && kill -0 "$$pid" 2>/dev/null; do sleep 1; i=$$((i + 1)); done; \
if kill -0 "$$pid" 2>/dev/null; then \
echo "==> Force-killing background make install (pid $$pid)"; \
kill -KILL -"$$pid" 2>/dev/null || kill -KILL "$$pid" 2>/dev/null || true; \
if command -v pkill >/dev/null 2>&1; then pkill -KILL -P "$$pid" 2>/dev/null || true; fi; \
fi; \
wait "$$pid" 2>/dev/null || true; \
fi
endef

.PHONY: airgap-abort
airgap-abort: ## Stop a background install-airgap session (reads airgap-bootstrap/install.pid)
@set -euo pipefail; \
PIDFILE="$(AIRGAP_BOOTSTRAP_DIR)/install.pid"; \
if [ ! -f "$$PIDFILE" ]; then \
echo "No $$PIDFILE - nothing to abort (is install-airgap running?)" >&2; \
exit 1; \
fi; \
INSTALL_PID=$$(tr -d '[:space:]' < "$$PIDFILE"); \
$(call AIRGAP_STOP_INSTALL,$$INSTALL_PID); \
rm -f "$$PIDFILE"; \
echo "==> Aborted. Log: $(AIRGAP_BOOTSTRAP_DIR)/install.log"

.PHONY: install-airgap
install-airgap: ## Disconnected install: Proxy CA, background install, wait for GitOps, post TLS/creds, then await install
@$(MAKE) airgap-trust-pre
@set -euo pipefail; \
mkdir -p "$(AIRGAP_BOOTSTRAP_DIR)"; \
LOG="$(AIRGAP_BOOTSTRAP_DIR)/install.log"; \
PIDFILE="$(AIRGAP_BOOTSTRAP_DIR)/install.pid"; \
echo "==> Starting make install in background (log: $$LOG)"; \
echo " (post trust runs as soon as vp-gitops is ready — install need not finish first)"; \
echo " Ctrl-C or: make airgap-abort"; \
setsid $(MAKE) install >"$$LOG" 2>&1 < /dev/null & \
INSTALL_PID=$$!; \
echo "$$INSTALL_PID" > "$$PIDFILE"; \
cleanup() { \
trap - INT TERM; \
$(call AIRGAP_STOP_INSTALL,$$INSTALL_PID); \
rm -f "$$PIDFILE"; \
}; \
trap cleanup INT TERM; \
if ! $(MAKE) airgap-wait-gitops; then \
cleanup; \
echo "airgap-wait-gitops failed; see $$LOG" >&2; \
exit 1; \
fi; \
if ! $(MAKE) airgap-trust-post; then \
trap - INT TERM; \
echo "airgap-trust-post failed; leaving install running (pid $$INSTALL_PID). See $$LOG" >&2; \
echo " Re-run: make airgap-trust-post" >&2; \
echo " Or abort: make airgap-abort" >&2; \
exit 1; \
fi; \
echo "==> Argo CD trust applied; waiting for background make install (pid $$INSTALL_PID)..."; \
set +e; \
wait $$INSTALL_PID; \
INSTALL_RC=$$?; \
set -e; \
trap - INT TERM; \
rm -f "$$PIDFILE"; \
if [ "$$INSTALL_RC" -ne 0 ]; then \
echo "make install failed with exit $$INSTALL_RC; see $$LOG" >&2; \
tail -n 40 "$$LOG" >&2 || true; \
exit $$INSTALL_RC; \
fi; \
echo "install-airgap complete. Proxy backup: $(AIRGAP_BOOTSTRAP_DIR)/backup/proxy-cluster.yaml"; \
echo " install log: $$LOG"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer that the complexity of these Makefile-common targets be part of the rhvp.cluster_utils collection rather than in this makefile.

Makes it so that updating the airgap recipes across the board does not require updating each pattern separately and it's consistent with all the other targets.

You will probably also need to add those AIRGAP_* env vars as arguments to the invocation of the utility container at the end of pattern.sh as well since right now if someone had AIRGAP_REGISTRY=some-registry ./pattern.sh make airgap-init I don't believe that env var would be respected

Comment on lines +19 to +85

# ---------------------------------------------------------------------------
# Airgap / disconnected GitOps trust (patternizer airgap bootstrap ONLY).
# Not loaded into Vault by make load-secrets. Copy this file to
# ~/values-secret-<pattern>.yaml (or keep paths local) and fill in real paths.
#
# UX:
# Copy this template to ~/values-secret-<pattern>.yaml and fill in airgap: paths.
# ./pattern.sh make install-airgap
#
# Or manually:
# patternizer airgap bootstrap /path/to/pattern --output-dir ./airgap-bootstrap
# ./airgap-bootstrap/apply-airgap.sh pre # before install
# ./pattern.sh make install
# ./airgap-bootstrap/apply-airgap.sh post # after vp-gitops exists
#
# clusterProxy: CA for patterns-operator git clone (Proxy/user-ca-bundle)
# argocdTLS: CA for Argo CD (argocd-tls-certs-cm + trusted-ca-bundle)
# registries: OCI/Helm mirror credentials for Argo CD repo-creds
# ---------------------------------------------------------------------------
# airgap:
# argocdNamespace: vp-gitops
# patternsOperatorNamespace: patterns-operator
# certificates:
# - name: gitea
# fields:
# - name: host
# value: gitea.airgap.rhuk.lab
# - name: ca
# path: /home/fedora/gitea_container_certs/cert.pem
# - name: clusterProxy
# value: "true"
# - name: argocdTLS
# value: "true"
# - name: mirror-registry
# fields:
# - name: host
# value: mirror.airgap.rhuk.lab
# - name: ca
# path: /etc/pki/ca-trust/source/anchors/quay-rootCA.pem
# - name: clusterProxy
# value: "true"
# - name: argocdTLS
# value: "true"
# registries:
# - name: mirror
# fields:
# - name: url
# value: mirror.airgap.rhuk.lab:8443
# - name: dockerconfig
# path: ~/.docker/config.json
# # Or username + password file instead of dockerconfig:
# # - name: username
# # value: init
# # - name: password
# # path: /path/to/registry.password
#
# # Disconnected pattern values (rendered into values-global.yaml + variants)
# registry: mirror.airgap.rhuk.lab:8443
# ocpVersion: "4.22"
# # Or declare CatalogSources explicitly (overrides ocpVersion derivation):
# # catalogSources:
# # redhat: cs-redhat-operator-index-v4-22
# # community: cs-community-operator-index-v4-22
# # Or discover from the cluster (requires oc / kubeconfig):
# # discoverCatalogs: true
# # renderValues: true # default when registry/ocpVersion/catalogSources set

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will be added to every single repo created with patternizer with secrets. is that intended?

Comment thread README.md
@@ -1,6 +1,6 @@
# Patternizer

![Version: 2.3.1](https://img.shields.io/badge/Version-2.3.1-informational?style=flat-square)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's quite a leap in minor version

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants