Skip to content

Commit 9ff91db

Browse files
fix(session-manager): relocate vcluster ingress Contour/Envoy images for air-gap
The Contour ingress controller deployed inside a vcluster session used hardcoded upstream Contour/Envoy image refs that bypassed image relocation and were absent from the published image list, so vcluster ingress could not work in air-gapped or mirrored-registry environments. Make them first-class imageVersions entries (vcluster-internal-contour, vcluster-internal-envoy) and rewrite the loaded Contour manifests to the inventory refs at session creation, so they relocate via per-name overrides like the loft-sh images and are captured in the air-gap image list. Drop the dead contour-bundle inventory entry and its unused constant. Repackage the vendored session-manager subchart tarball. Also extend hack/generate-image-list.sh to render the session-manager chart so the full imageVersions inventory (workshop environments plus the vcluster application images) is included in the published list, and add publish-workshop-images to the image-list job's needs so the JDK/conda image digests resolve.
1 parent f341a73 commit 9ff91db

7 files changed

Lines changed: 123 additions & 38 deletions

File tree

.github/workflows/build-and-publish-images.yaml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -873,10 +873,13 @@ jobs:
873873
contents: read
874874
packages: read
875875

876-
# Digest resolution needs every listed image already pushed.
876+
# Digest resolution needs every listed image already pushed. The
877+
# list now includes the JDK/conda workshop environments, so the
878+
# workshop-images job has to finish first too.
877879
needs:
878880
- publish-generic-images
879881
- publish-workshop-base-image
882+
- publish-workshop-images
880883

881884
steps:
882885
- name: Check out the repository
@@ -889,10 +892,12 @@ jobs:
889892
echo "REPOSITORY_OWNER=${REPOSITORY_OWNER,,}" >>${GITHUB_ENV}
890893
echo "REPOSITORY_TAG=${GITHUB_REF##*/}" >>${GITHUB_ENV}
891894
892-
# Digest-pinned list of every image an air-gapped install needs
893-
# (platform images + base-environment + upstream cluster-service
894-
# images from the vendored charts). Attached to the GitHub
895-
# release for name-preserving mirroring with skopeo/crane.
895+
# Digest-pinned list of every image an air-gapped install can need
896+
# (platform images + the full session-manager image inventory:
897+
# base-environment, JDK/conda environments, and the vcluster
898+
# application images + upstream cluster-service images from the
899+
# vendored charts). Attached to the GitHub release for
900+
# name-preserving mirroring with skopeo/crane.
896901
- name: Generate image list
897902
shell: bash
898903
run: |

hack/generate-image-list.sh

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,30 @@
44
# hack/generate-image-list.sh <version> <registry-host> <registry-namespace> [--no-digests]
55
#
66
# Writes one fully qualified image reference per line to stdout
7-
# (`<repo>:<tag>@sha256:<digest>`), covering everything an air-gapped
8-
# install of the platform needs (see decisions.md "Image relocation is
9-
# a published digest-pinned list"):
7+
# (`<repo>:<tag>@sha256:<digest>`), covering everything an install of the
8+
# platform can need (see decisions.md "Image relocation is a published
9+
# digest-pinned list"):
1010
#
1111
# - the Educates platform images at the released version (operator,
12-
# runtime components, pause-container, docker-registry, ...) plus
13-
# the workshop base-environment image, composed from the given
14-
# registry host/namespace;
12+
# runtime components, pause-container, docker-registry, ...);
13+
# - the full session-manager image inventory (the `imageVersions`
14+
# helper): the workshop base, the JDK and conda workshop
15+
# environments, and the optional runtime images for the vcluster
16+
# workshop application — vcluster itself plus its loft-sh Kubernetes
17+
# distro images, docker-in-docker, and the debian base. Extracted by
18+
# rendering the session-manager chart, so version bumps in the chart
19+
# flow through without editing this script;
1520
# - the upstream cluster-service images (cert-manager, Contour,
16-
# external-dns, Kyverno), extracted by rendering the vendored
17-
# chart tarballs with default values. Defaults are a superset of
18-
# what the operator enables, so this over-collects slightly rather
19-
# than ever missing an image.
21+
# external-dns, Kyverno), extracted by rendering the vendored chart
22+
# tarballs with default values. Defaults are a superset of what the
23+
# operator enables, so this over-collects slightly rather than ever
24+
# missing an image.
2025
#
21-
# Workshop environment images beyond base-environment (jdk*, conda)
22-
# are deliberately excluded — they add many GB per release. Air-gap
23-
# users append them to the list as needed.
26+
# The list is intentionally COMPLETE — it includes the JDK/conda
27+
# environments and the vcluster application images even though most
28+
# installs use only a subset. When mirroring, delete the entries you do
29+
# not use rather than guessing which ones might be missing. The
30+
# JDK/conda environment images are multi-GB each.
2431
#
2532
# Digest resolution uses skopeo (preinstalled on GitHub runners) and
2633
# requires the images to already be published — the release workflow
@@ -47,24 +54,21 @@ fi
4754
cd "$(dirname "$0")/.."
4855

4956
VENDORED_CHARTS_DIR=installer/operator/vendored-charts
57+
SESSION_MANAGER_CHART=installer/charts/educates-training-platform/charts/session-manager
58+
REGISTRY_PREFIX="$REGISTRY_HOST/$REGISTRY_NAMESPACE"
5059

51-
# Educates-built platform images: the publish-generic-images matrix
52-
# (with the operator's published name) plus the workshop
53-
# base-environment. educates-cli and the docker extension are client
54-
# tools, not platform images, and stay out.
60+
# Educates-built platform images that are NOT part of the session-manager
61+
# imageVersions inventory (the operator, the other components, and the
62+
# pause image). The inventory-resident Educates images — training-portal,
63+
# docker-registry, base-environment, jdk*, conda, ... — come from the
64+
# chart render below and are deduplicated against this list.
5565
PLATFORM_IMAGES="
56-
docker-registry
5766
pause-container
5867
session-manager
59-
training-portal
6068
secrets-manager
61-
tunnel-manager
62-
image-cache
63-
assets-server
6469
lookup-service
6570
node-ca-injector
6671
operator
67-
base-environment
6872
"
6973

7074
# Upstream cluster-service charts the operator installs in Managed
@@ -94,10 +98,6 @@ emit() {
9498
fi
9599
}
96100

97-
for name in $PLATFORM_IMAGES; do
98-
emit "$REGISTRY_HOST/$REGISTRY_NAMESPACE/educates-$name:$VERSION"
99-
done
100-
101101
# Image references appear in rendered manifests as `image:` fields and,
102102
# for cert-manager's acmesolver, as a `--*-image=` controller argument.
103103
extract_chart_images() {
@@ -107,18 +107,50 @@ extract_chart_images() {
107107
sed -E 's/^image: *"?//; s/"$//; s/^--[a-z0-9-]*image=//'
108108
}
109109

110-
upstream_refs=""
110+
# Render the session-manager chart and pull every image reference out of
111+
# the imageVersions inventory (and the chart's own pod images). The
112+
# registry prefix is forced to the release's host/namespace so the
113+
# Educates-built entries resolve to the published location; clusterIngress
114+
# .domain is a required value that does not affect image refs.
115+
render_session_manager_images() {
116+
helm template image-list-probe "$SESSION_MANAGER_CHART" \
117+
--set clusterIngress.domain=image-list-probe.invalid \
118+
--set development.imageRegistry.host="$REGISTRY_HOST" \
119+
--set development.imageRegistry.namespace="$REGISTRY_NAMESPACE" \
120+
2>/dev/null |
121+
grep -ohE 'image: *"?[^"[:space:]]+"?' |
122+
sed -E 's/^image: *"?//; s/"$//'
123+
}
124+
125+
all_refs=""
126+
127+
for name in $PLATFORM_IMAGES; do
128+
all_refs+="$REGISTRY_PREFIX/educates-$name:$VERSION"$'\n'
129+
done
130+
131+
# Educates-built inventory entries render at the chart's appVersion; pin
132+
# them to the release VERSION instead. External entries (vcluster,
133+
# loft-sh Kubernetes, the vcluster Contour/Envoy, docker-in-docker,
134+
# debian) pass through verbatim.
135+
while read -r ref; do
136+
[ -n "$ref" ] || continue
137+
case "$ref" in
138+
"$REGISTRY_PREFIX"/*) all_refs+="${ref%:*}:$VERSION"$'\n' ;;
139+
*) all_refs+="$ref"$'\n' ;;
140+
esac
141+
done < <(render_session_manager_images)
142+
111143
for glob in $UPSTREAM_CHART_GLOBS; do
112144
# shellcheck disable=SC2086 -- glob expansion is the point
113145
set -- $VENDORED_CHARTS_DIR/$glob
114146
[ -f "$1" ] || {
115147
echo "no vendored chart matching $glob in $VENDORED_CHARTS_DIR" >&2
116148
exit 1
117149
}
118-
upstream_refs+="$(extract_chart_images "$1")"$'\n'
150+
all_refs+="$(extract_chart_images "$1")"$'\n'
119151
done
120152

121153
while read -r ref; do
122154
[ -n "$ref" ] || continue
123155
emit "$ref"
124-
done < <(echo "$upstream_refs" | sort -u)
156+
done < <(echo "$all_refs" | sort -u)

installer/charts/educates-training-platform/charts/session-manager/templates/_helpers.tpl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,6 @@ Returns the merged list as a YAML array string (consume via fromYamlArray).
283283
(dict "name" "tunnel-manager" "image" (printf "%s/educates-tunnel-manager:%s" $repo $v))
284284
(dict "name" "image-cache" "image" (printf "%s/educates-image-cache:%s" $repo $v))
285285
(dict "name" "assets-server" "image" (printf "%s/educates-assets-server:%s" $repo $v))
286-
(dict "name" "contour-bundle" "image" (printf "%s/educates-contour-bundle:%s" $repo $v))
287286
(dict "name" "base-environment" "image" (printf "%s/educates-base-environment:%s" $repo $v))
288287
(dict "name" "jdk8-environment" "image" (printf "%s/educates-jdk8-environment:%s" $repo $v))
289288
(dict "name" "jdk11-environment" "image" (printf "%s/educates-jdk11-environment:%s" $repo $v))
@@ -297,6 +296,8 @@ Returns the merged list as a YAML array string (consume via fromYamlArray).
297296
(dict "name" "loftsh-kubernetes-v1.33" "image" "ghcr.io/loft-sh/kubernetes:v1.33.4")
298297
(dict "name" "loftsh-kubernetes-v1.34" "image" "ghcr.io/loft-sh/kubernetes:v1.34.0")
299298
(dict "name" "loftsh-vcluster" "image" "ghcr.io/loft-sh/vcluster-oss:0.30.2")
299+
(dict "name" "vcluster-internal-contour" "image" "ghcr.io/projectcontour/contour:v1.30.2")
300+
(dict "name" "vcluster-internal-envoy" "image" "docker.io/envoyproxy/envoy:v1.31.5")
300301
-}}
301302
{{- $overrides := dict -}}
302303
{{- range default list .Values.imageVersions -}}
Binary file not shown.

project-docs/release-notes/version-4.0.0.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,14 @@ New Features
8888
* The Helm charts are published as OCI artifacts to
8989
``ghcr.io/educates/charts`` with each release, and a digest-pinned list of
9090
all images used by a release is attached to the GitHub release to support
91-
mirroring images into air-gapped registries.
91+
mirroring images into air-gapped registries. The list is intentionally
92+
complete: in addition to the platform and bundled cluster-service images it
93+
includes every image in the session-manager image inventory — the
94+
workshop base, the JDK and conda workshop environments, and the images the
95+
vcluster workshop application uses (``vcluster`` itself, its loft-sh
96+
Kubernetes distribution images, the Contour and Envoy images for vcluster
97+
ingress, ``docker-in-docker`` and the debian base). When mirroring, delete
98+
the entries you do not use rather than guessing which ones might be missing.
9299

93100
* ``imageVersions`` entries in the CLI configuration now reach every
94101
platform image: entries named ``secrets-manager`` and ``lookup-service``
@@ -244,6 +251,15 @@ Deprecations
244251
Bugs Fixed
245252
----------
246253

254+
* The Contour ingress controller deployed inside a vcluster workshop session
255+
(when the ``vcluster`` application enables ingress) used hardcoded upstream
256+
Contour and Envoy image references that were not subject to image relocation
257+
and were absent from the published image list, so vcluster ingress could not
258+
work in air-gapped or registry-mirrored environments. These images are now
259+
first-class entries in the ``imageVersions`` inventory, so they are
260+
relocatable through the same per-name override mechanism as the other
261+
platform images and are included in the air-gap image list.
262+
247263
* In Inline mode the ``EducatesClusterConfig`` status could remain ``Ready``
248264
after a referenced ``ClusterIssuer`` was deleted, if the deletion event from
249265
the cert-manager watch was missed or observed against a momentarily stale

session-manager/handlers/application_vcluster.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,33 @@
1010
LOFTSH_KUBERNETES_V1_32_IMAGE,
1111
LOFTSH_KUBERNETES_V1_33_IMAGE,
1212
LOFTSH_KUBERNETES_V1_34_IMAGE,
13+
VCLUSTER_INTERNAL_CONTOUR_IMAGE,
14+
VCLUSTER_INTERNAL_ENVOY_IMAGE,
1315
CLUSTER_STORAGE_GROUP,
1416
)
1517

18+
# The Contour manifests in packages/contour/upstream pin specific upstream
19+
# image refs. Rewrite them to the refs from the imageVersions inventory so
20+
# the inventory entry — and any air-gap relocation override of it — is what
21+
# actually gets deployed into the vcluster, mirroring how the loft-sh
22+
# images are sourced.
23+
CONTOUR_IMAGE_REPOSITORIES = {
24+
"ghcr.io/projectcontour/contour": VCLUSTER_INTERNAL_CONTOUR_IMAGE,
25+
"docker.io/envoyproxy/envoy": VCLUSTER_INTERNAL_ENVOY_IMAGE,
26+
}
27+
28+
29+
def relocate_contour_images(objects):
30+
for obj in objects:
31+
pod_spec = xget(obj, "spec.template.spec", {})
32+
for key in ("initContainers", "containers"):
33+
for container in pod_spec.get(key, []):
34+
image = container.get("image")
35+
if image is None:
36+
continue
37+
repository = image.rsplit(":", 1)[0]
38+
container["image"] = CONTOUR_IMAGE_REPOSITORIES.get(repository, image)
39+
1640
K8S_DEFAULT_VERSION = "1.33"
1741

1842
K8S_VERSIONS = {
@@ -172,6 +196,8 @@ def relpath(*paths):
172196

173197
contour_objects.append(obj)
174198

199+
relocate_contour_images(contour_objects)
200+
175201
vcluster_objects.extend(contour_objects)
176202

177203
# Add the Contour service with a ClusterIP instead of a LoadBalancer

session-manager/handlers/operator_config.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,12 @@ def image_reference(name):
165165

166166
LOFTSH_VCLUSTER_IMAGE = image_reference("loftsh-vcluster")
167167

168-
CONTOUR_BUNDLE_IMAGE = image_reference("contour-bundle")
168+
# Contour ingress controller deployed inside a vcluster session when the
169+
# vcluster application enables ingress. Sourced from the imageVersions
170+
# inventory so the refs are relocatable and captured in the air-gap image
171+
# list, the same way the loft-sh images are.
172+
VCLUSTER_INTERNAL_CONTOUR_IMAGE = image_reference("vcluster-internal-contour")
173+
VCLUSTER_INTERNAL_ENVOY_IMAGE = image_reference("vcluster-internal-envoy")
169174

170175

171176
def resolve_workshop_image(name):

0 commit comments

Comments
 (0)