-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathapply-theme.sh
More file actions
executable file
·349 lines (298 loc) · 12.1 KB
/
Copy pathapply-theme.sh
File metadata and controls
executable file
·349 lines (298 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
# Registered-target defaults (overrideable via flags).
NAMESPACE="educates-config"
SECRET_NAME="tigera-labs-theme"
# Default-target is fixed by Educates' own naming; not flagged.
DEFAULT_THEME_NAMESPACE="educates"
DEFAULT_THEME_SECRET="default-website-theme"
THEME_DIR="${SCRIPT_DIR}"
KUBE_CONTEXT=""
TARGET="both"
RESTART=0
DRY_RUN=0
# Key educates injection points
THEME_KEYS=(
training-portal.html
training-portal.css
training-portal.js
workshop-dashboard.html
workshop-dashboard.css
workshop-dashboard.js
workshop-instructions.html
workshop-instructions.css
workshop-instructions.js
workshop-started.html
workshop-finished.html
)
# Where we stash extra suaces like logo and other stuff
EXTRA_ASSET_DIR="assets"
# Glob patterns matched inside ${THEME_DIR}/${EXTRA_ASSET_DIR}.
EXTRA_ASSET_GLOBS=(
"tigera-theme-core.js"
"tigera-theme-core.css"
"*.svg"
"*.png"
"*.jpg"
"*.jpeg"
"*.webp"
"*.ico"
"*.woff"
"*.woff2"
)
# ---------------------------------------------------------------------------
# helpers
# ---------------------------------------------------------------------------
log() { printf '\033[1;36m[tigera-labs-theme]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[tigera-labs-theme]\033[0m %s\n' "$*" >&2; }
fail() { printf '\033[1;31m[tigera-labs-theme]\033[0m %s\n' "$*" >&2; exit 1; }
usage() {
sed -n '3,55p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
}
kubectl_run() {
local args=(kubectl)
if [[ -n "${KUBE_CONTEXT}" ]]; then
args+=(--context "${KUBE_CONTEXT}")
fi
"${args[@]}" "$@"
}
# Ensure namespace exists; honors dry-run.
ensure_namespace() {
local ns="$1"
local manifest
manifest="$(kubectl_run create namespace "${ns}" --dry-run=client -o yaml)"
if [[ "${DRY_RUN}" -eq 1 ]]; then
log "Would ensure namespace ${ns} exists."
return
fi
if kubectl_run get namespace "${ns}" >/dev/null 2>&1; then
log "Namespace ${ns} already exists."
else
log "Creating namespace ${ns}..."
printf '%s\n' "${manifest}" | kubectl_run apply -f - >/dev/null
fi
}
# Apply (or simulate applying) a generic Secret from FROM_FILE_ARGS.
apply_secret() {
local ns="$1"
local name="$2"
local manifest
manifest="$(kubectl_run create secret generic "${name}" \
--namespace="${ns}" \
"${FROM_FILE_ARGS[@]}" \
--dry-run=client -o yaml)"
if [[ "${DRY_RUN}" -eq 1 ]]; then
log "Would apply Secret ${ns}/${name} (${#INCLUDED_KEYS[@]} keys)."
return
fi
log "Applying Secret ${ns}/${name}..."
# Server-side apply because the theme Secret routinely exceeds 256 KiB
# once fonts + SVGs + ICO are bundled — and client-side apply stashes
# the entire manifest in the `last-applied-configuration` annotation,
# which the apiserver caps at 256 KiB.
# --force-conflicts takes ownership from any prior client-side applier
# (including the educates installer's own field manager on the
# auto-created default-website-theme Secret).
printf '%s\n' "${manifest}" \
| kubectl_run apply --server-side --force-conflicts \
--field-manager=tigera-labs-theme \
-f - >/dev/null
log " ✓ ${ns}/${name}"
}
# ---------------------------------------------------------------------------
# argument parsing
# ---------------------------------------------------------------------------
while [[ $# -gt 0 ]]; do
case "$1" in
-n|--namespace) NAMESPACE="$2"; shift 2 ;;
-s|--secret) SECRET_NAME="$2"; shift 2 ;;
-d|--theme-dir) THEME_DIR="$2"; shift 2 ;;
-c|--context) KUBE_CONTEXT="$2"; shift 2 ;;
-t|--target) TARGET="$2"; shift 2 ;;
--restart) RESTART=1; shift ;;
--dry-run) DRY_RUN=1; shift ;;
-h|--help) usage; exit 0 ;;
*) fail "Unknown argument: $1 (try --help)" ;;
esac
done
case "${TARGET}" in
default|registered|both) ;;
*) fail "Invalid --target '${TARGET}' (expected: default | registered | both)" ;;
esac
# ---------------------------------------------------------------------------
# preflight
# ---------------------------------------------------------------------------
command -v kubectl >/dev/null 2>&1 || fail "kubectl not found in PATH"
[[ -d "${THEME_DIR}" ]] || fail "Theme directory not found: ${THEME_DIR}"
if ! kubectl_run cluster-info >/dev/null 2>&1; then
fail "Cannot reach Kubernetes API server. Check your kubeconfig${KUBE_CONTEXT:+ and --context $KUBE_CONTEXT}."
fi
CURRENT_CONTEXT=$(kubectl_run config current-context 2>/dev/null || echo "<none>")
log "Cluster context: ${CURRENT_CONTEXT}"
log "Theme directory: ${THEME_DIR}"
log "Target mode: ${TARGET}"
[[ "${DRY_RUN}" -eq 1 ]] && log "Dry run: NO changes will be applied."
# ---------------------------------------------------------------------------
# build per-surface JS bundles from src/
# ---------------------------------------------------------------------------
# Each surface's JS file is generated by concatenating the shared core
# (src/core.js) with the surface-specific delta (src/<surface>.src.js).
# Skipped if the src/ directory isn't present (e.g. someone is using the
# theme dir without the source layout).
SRC_DIR="${THEME_DIR}/src"
# JS bundles are NOT built — each surface JS file is canonical and
# dynamically <script>-loads tigera-theme-core.js at runtime. See
# assets/tigera-theme-core.js + the per-surface .js files at the
# theme root.
# Build per-surface CSS bundles from src/. Each generated file =
# _tokens.css + _dark.css + _fonts.css.tmpl(prefix=...) + <surface>.src.css
# The fonts template's __ASSET_PREFIX__ is substituted per-surface
# because each surface serves theme assets at a different URL path.
if [[ -d "${SRC_DIR}" && -f "${SRC_DIR}/_tokens.css" ]]; then
log "Building per-surface CSS bundles from src/..."
build_css_surface() {
local surface="$1"
local prefix="$2"
local src="${SRC_DIR}/${surface}.src.css"
local out="${THEME_DIR}/${surface}.css"
if [[ ! -f "${src}" ]]; then
warn "Skipping ${surface}.css — ${src} not found"
return
fi
{
printf '/* GENERATED by apply-theme.sh from src/_tokens.css + src/_dark.css + src/_fonts.css.tmpl + src/_slack-help.css + src/%s.src.css — do not edit. */\n' "${surface}"
cat "${SRC_DIR}/_tokens.css"
printf '\n'
cat "${SRC_DIR}/_dark.css"
printf '\n'
sed "s|__ASSET_PREFIX__|${prefix}|g" "${SRC_DIR}/_fonts.css.tmpl"
printf '\n'
if [[ -f "${SRC_DIR}/_slack-help.css" ]]; then
cat "${SRC_DIR}/_slack-help.css"
printf '\n'
fi
cat "${src}"
} > "${out}"
log " ✓ $(basename "${out}") ($(wc -c < "${out}") bytes)"
}
build_css_surface training-portal "/static/workshops/theme/"
build_css_surface workshop-dashboard "/static/theme/"
build_css_surface workshop-instructions "/workshop/static/theme/"
fi
# ---------------------------------------------------------------------------
# discover files to bundle
# ---------------------------------------------------------------------------
FROM_FILE_ARGS=()
INCLUDED_KEYS=()
for key in "${THEME_KEYS[@]}"; do
if [[ -f "${THEME_DIR}/${key}" ]]; then
FROM_FILE_ARGS+=(--from-file="${key}=${THEME_DIR}/${key}")
INCLUDED_KEYS+=("${key}")
fi
done
shopt -s nullglob
EXTRA_DIR="${THEME_DIR}/${EXTRA_ASSET_DIR}"
if [[ -d "${EXTRA_DIR}" ]]; then
for glob in "${EXTRA_ASSET_GLOBS[@]}"; do
for path in "${EXTRA_DIR}"/${glob}; do
name="$(basename "${path}")"
FROM_FILE_ARGS+=(--from-file="${name}=${path}")
INCLUDED_KEYS+=("${name} (extra)")
done
done
fi
shopt -u nullglob
if [[ ${#INCLUDED_KEYS[@]} -eq 0 ]]; then
fail "No theme files found in ${THEME_DIR}. Expected one or more of: ${THEME_KEYS[*]}"
fi
log "Files to bundle into the Secret (${#INCLUDED_KEYS[@]}):"
for k in "${INCLUDED_KEYS[@]}"; do printf ' - %s\n' "$k"; done
# ---------------------------------------------------------------------------
# detect whether the default-theme target is reachable
# ---------------------------------------------------------------------------
DEFAULT_THEME_PRESENT=0
if kubectl_run get secret -n "${DEFAULT_THEME_NAMESPACE}" "${DEFAULT_THEME_SECRET}" >/dev/null 2>&1; then
DEFAULT_THEME_PRESENT=1
fi
want_default=0
want_registered=0
case "${TARGET}" in
default) want_default=1 ;;
registered) want_registered=1 ;;
both) want_default=1; want_registered=1 ;;
esac
if [[ "${want_default}" -eq 1 && "${DEFAULT_THEME_PRESENT}" -eq 0 ]]; then
if [[ "${TARGET}" == "default" ]]; then
fail "Secret ${DEFAULT_THEME_NAMESPACE}/${DEFAULT_THEME_SECRET} not found — is Educates installed in this cluster?"
else
warn "Skipping default-theme overwrite: ${DEFAULT_THEME_NAMESPACE}/${DEFAULT_THEME_SECRET} not found (non-default install?)."
want_default=0
fi
fi
# ---------------------------------------------------------------------------
# registered target — durable Secret keyed by themeDataRefs
# ---------------------------------------------------------------------------
if [[ "${want_registered}" -eq 1 ]]; then
log "Registered target: ${NAMESPACE}/${SECRET_NAME}"
ensure_namespace "${NAMESPACE}"
apply_secret "${NAMESPACE}" "${SECRET_NAME}"
fi
# ---------------------------------------------------------------------------
# default target — overwrite the Secret Educates auto-mounts cluster-wide
# ---------------------------------------------------------------------------
if [[ "${want_default}" -eq 1 ]]; then
log "Default-theme target: ${DEFAULT_THEME_NAMESPACE}/${DEFAULT_THEME_SECRET}"
apply_secret "${DEFAULT_THEME_NAMESPACE}" "${DEFAULT_THEME_SECRET}"
warn "Note: educates/default-website-theme is auto-managed by the Educates"
warn "installer. Re-running the installer will REGENERATE this Secret from"
warn "installer values and revert this change. Bake your theme into the"
warn "installer values (see installer-config.yaml) to make it durable."
fi
# ---------------------------------------------------------------------------
# optional: restart consumer deployments
# ---------------------------------------------------------------------------
if [[ "${RESTART}" -eq 1 ]]; then
log "Restarting training-portal deployments cluster-wide so they reload the theme..."
# Educates names every portal deployment "training-portal" within its own
# namespace. We find them by name match across all namespaces — safer than
# guessing labels which can drift between Educates versions.
mapfile -t portal_targets < <(
kubectl_run get deployments --all-namespaces \
-o jsonpath='{range .items[?(@.metadata.name=="training-portal")]}{.metadata.namespace}{"\t"}{.metadata.name}{"\n"}{end}' \
2>/dev/null || true
)
if [[ ${#portal_targets[@]} -eq 0 || -z "${portal_targets[0]}" ]]; then
warn "No training-portal deployments found. (None deployed yet, or named differently.)"
else
for line in "${portal_targets[@]}"; do
[[ -z "${line}" ]] && continue
ns="${line%%$'\t'*}"
name="${line##*$'\t'}"
if [[ "${DRY_RUN}" -eq 1 ]]; then
log "Would: kubectl -n ${ns} rollout restart deployment/${name}"
else
log "kubectl -n ${ns} rollout restart deployment/${name}"
kubectl_run -n "${ns}" rollout restart deployment/"${name}" >/dev/null
fi
done
fi
warn "Active workshop sessions are NOT restarted — sessions are torn down and"
warn "rebuilt as users start/finish workshops. New sessions will pick up the theme."
fi
# ---------------------------------------------------------------------------
# next-step hints
# ---------------------------------------------------------------------------
cat <<EOF
Done.
Next steps:
- To make this durable across installer reruns, merge the snippet from
${THEME_DIR}/installer-config.yaml into your Educates installer values
and re-run the installer. Otherwise the next \`educates install\` will
regenerate educates/default-website-theme from defaults and revert the
visible theme.
- To opt in individual portals (instead of using a cluster default), apply:
kubectl apply -f ${THEME_DIR}/trainingportal-example.yaml
- To re-run after editing CSS/JS/HTML in this folder:
${BASH_SOURCE[0]} --restart
EOF