-
Notifications
You must be signed in to change notification settings - Fork 6
[HYPERSHELL-104] feat(kind): add optimized Keycloak image for faster startup #140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| apiVersion: kustomize.config.k8s.io/v1beta1 | ||
| kind: Kustomization | ||
| resources: | ||
| - ../kind | ||
| patches: | ||
| # The pre-built image (deploy/kind/keycloak/Dockerfile) runs `kc.sh build` | ||
| # at image build time so the provider registry, config parsing, and DB | ||
| # resource generation happen once, not on every pod start. `--optimized` | ||
| # skips the build phase entirely, cutting startup from ~60s to ~15s. | ||
| - patch: | | ||
| apiVersion: apps/v1 | ||
| kind: Deployment | ||
| metadata: | ||
| name: keycloak | ||
| namespace: keycloak | ||
| spec: | ||
| template: | ||
| spec: | ||
| containers: | ||
| - name: keycloak | ||
| args: | ||
| - start | ||
| - --optimized | ||
| - --import-realm | ||
| env: | ||
| - name: KC_HTTP_ENABLED | ||
| value: "true" | ||
| - name: KC_CACHE | ||
| value: "local" | ||
| images: | ||
| - name: quay.io/keycloak/keycloak | ||
| newName: localhost/hypershell-keycloak | ||
| newTag: dev-optimized | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| FROM quay.io/keycloak/keycloak:26.2@sha256:e1bea10dc9a144b0f70aba477ba2ef14a0fa752e7e95faa4b7903ffa1287187a AS builder | ||
|
|
||
| RUN /opt/keycloak/bin/kc.sh build \ | ||
| --db=dev-file \ | ||
| --health-enabled=true | ||
|
|
||
| FROM quay.io/keycloak/keycloak:26.2@sha256:e1bea10dc9a144b0f70aba477ba2ef14a0fa752e7e95faa4b7903ffa1287187a | ||
|
|
||
| COPY --from=builder /opt/keycloak/ /opt/keycloak/ | ||
|
|
||
| ENTRYPOINT ["/opt/keycloak/bin/kc.sh"] | ||
|
Comment on lines
+1
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
file="deploy/kind/keycloak/Dockerfile"
printf '%s\n' '--- Dockerfile ---'
cat -n "$file"
printf '%s\n' '--- Relevant Dockerfile instructions ---'
rg -n '^(FROM|USER|HEALTHCHECK|COPY|ENTRYPOINT|CMD|RUN)\b' "$file"
printf '%s\n' '--- Related container hardening patterns ---'
rg -n '^(FROM|USER|HEALTHCHECK)\b' --glob 'Dockerfile*' --glob 'Containerfile*' . | head -200Repository: openshift-online/hypershell Length of output: 3373 🏁 Script executed: #!/bin/bash
set -eu
file="deploy/kind/keycloak/Dockerfile"
python3 - <<'PY'
from pathlib import Path
import re
p = Path("deploy/kind/keycloak/Dockerfile")
lines = p.read_text().splitlines()
froms = [x for x in lines if re.match(r"^\s*FROM\b", x)]
users = [x for x in lines if re.match(r"^\s*USER\b", x)]
healthchecks = [x for x in lines if re.match(r"^\s*HEALTHCHECK\b", x)]
print("FROM instructions:")
for x in froms:
print(x)
print("USER instructions:", users or "<none>")
print("HEALTHCHECK instructions:", healthchecks or "<none>")
for i, x in enumerate(froms, 1):
image = x.split()[1]
print(f"stage {i} image={image}")
print(f" catalog.redhat.com={image.startswith('catalog.redhat.com/')}")
print(f" digest_pinned={'`@sha256`:' in image}")
PYRepository: openshift-online/hypershell Length of output: 800 🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
p = Path("deploy/kind/keycloak/Dockerfile")
lines = p.read_text().splitlines()
froms = [x for x in lines if re.match(r"^\s*FROM\b", x)]
users = [x for x in lines if re.match(r"^\s*USER\b", x)]
healthchecks = [x for x in lines if re.match(r"^\s*HEALTHCHECK\b", x)]
print("FROM instructions:")
for x in froms:
print(x)
print("USER instructions:", users or "<none>")
print("HEALTHCHECK instructions:", healthchecks or "<none>")
for i, x in enumerate(froms, 1):
image = x.split()[1]
print(f"stage {i} image={image}")
print(f" catalog.redhat.com={image.startswith('catalog.redhat.com/')}")
print(f" digest_pinned={'`@sha256`:' in image}")
PYRepository: openshift-online/hypershell Length of output: 800 Use approved bases and harden the runtime image. Both 🧰 Tools🪛 Trivy (0.73.0)[error] 1-1: Image user should not be 'root' Specify at least 1 USER command in Dockerfile with non-root user as argument Rule: DS-0002 (IaC/Dockerfile) 🤖 Prompt for AI AgentsSources: Path instructions, Linters/SAST tools |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| #!/usr/bin/env python3 | ||
| """Validate that kustomize overlays render valid YAML with expected resources.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import json | ||
| import re | ||
| import subprocess | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| REPOSITORY_ROOT = Path(__file__).resolve().parent.parent | ||
|
|
||
| OVERLAYS: dict[str, dict] = { | ||
| "deploy/kind": {}, | ||
| "deploy/kind-keycloak-optimized": { | ||
| "keycloak_image": "localhost/hypershell-keycloak:dev-optimized", | ||
| "keycloak_args": ["start", "--optimized", "--import-realm"], | ||
| "keycloak_env": {"KC_HTTP_ENABLED": "true", "KC_CACHE": "local"}, | ||
| }, | ||
| } | ||
|
|
||
|
|
||
| def _kustomize_build(overlay: str) -> str: | ||
| result = subprocess.run( | ||
| ("kustomize", "build", str(REPOSITORY_ROOT / overlay)), | ||
| check=False, | ||
| capture_output=True, | ||
| text=True, | ||
| ) | ||
| if result.returncode != 0: | ||
| return "" | ||
| return result.stdout | ||
|
|
||
|
|
||
| def _grep_keycloak_container(output: str) -> dict[str, str | list[str]]: | ||
| """Extract image, args, and env from the rendered keycloak container.""" | ||
| info: dict[str, str | list[str]] = {} | ||
| image_match = re.search( | ||
| r"image:\s*(\S+)", | ||
| output[output.find("name: keycloak\n namespace: keycloak"):], | ||
| ) | ||
| if image_match: | ||
| info["image"] = image_match.group(1) | ||
|
|
||
| args: list[str] = [] | ||
| in_args = False | ||
| for line in output.splitlines(): | ||
| stripped = line.strip() | ||
| if stripped == "- args:": | ||
| in_args = True | ||
| continue | ||
| if in_args: | ||
| if stripped.startswith("- "): | ||
| args.append(stripped[2:]) | ||
| else: | ||
| break | ||
| info["args"] = args | ||
|
|
||
| env: dict[str, str] = {} | ||
| lines = output.splitlines() | ||
| for i, line in enumerate(lines): | ||
| if line.strip() == "env:" and any( | ||
| "name: keycloak" in lines[j] for j in range(max(0, i - 20), i) | ||
| ): | ||
| j = i + 1 | ||
| while j < len(lines) and lines[j].strip().startswith("- name:"): | ||
| name = lines[j].strip().removeprefix("- name:").strip() | ||
| if j + 1 < len(lines) and "value:" in lines[j + 1]: | ||
| val = lines[j + 1].strip().removeprefix("value:").strip().strip('"') | ||
| env[name] = val | ||
| j += 2 | ||
| break | ||
| info["env"] = env | ||
| return info | ||
|
Comment on lines
+36
to
+75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- tracked files ---'
git ls-files | sed -n '/\(check_kustomize_overlays\|kustomization\|requirements\|pyproject\|README\|Makefile\|workflow\)/Ip' | head -200
printf '%s\n' '--- target file outline ---'
ast-grep outline scripts/check_kustomize_overlays.py 2>/dev/null || true
printf '%s\n' '--- target file ---'
cat -n scripts/check_kustomize_overlays.py
printf '%s\n' '--- kustomize references ---'
rg -n -i --glob '!node_modules' --glob '!build' --glob '!dist' 'kustomize|--output|output mode|kyaml' .Repository: openshift-online/hypershell Length of output: 16791 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- workload and container fields ---'
rg -n -C 4 '(^|[[:space:]])(kind:|name:|containers:|args:|env:|image:)' deploy/base deploy/kind deploy/kind-keycloak-optimized
printf '%s\n' '--- tool/version declarations ---'
rg -n -i -C 3 'kustomize|kubectl|kube[- ]?version|version.*kustomize|kustomize.*version' \
Makefile README.md scripts .github .devcontainer 2>/dev/null || true
printf '%s\n' '--- dependency and setup files ---'
git ls-files | rg -i '(^|/)(go\.mod|go\.sum|package\.json|pyproject\.toml|requirements[^/]*|tools?|versions?|\.tool-versions|Dockerfile|.*\.ya?ml)$' | head -200
printf '%s\n' '--- parser behavior probe ---'
python3 - <<'PY'
from pathlib import Path
import ast
source = Path("scripts/check_kustomize_overlays.py").read_text()
tree = ast.parse(source)
fn = next(
node for node in tree.body
if isinstance(node, ast.FunctionDef)
and node.name == "_grep_keycloak_container"
)
module = ast.Module(
body=[
node for node in tree.body
if isinstance(node, (ast.Import, ast.ImportFrom))
] + [fn],
type_ignores=[],
)
namespace = {}
exec(compile(module, "scripts/check_kustomize_overlays.py", "exec"), namespace)
parse = namespace["_grep_keycloak_container"]
samples = {
"keycloak-first": """apiVersion: v1
kind: Deployment
metadata:
name: keycloak
namespace: keycloak
spec:
template:
spec:
containers:
- name: keycloak
image: expected:keycloak
args:
- start
env:
- name: KC_HTTP_ENABLED
value: "true"
""",
"unrelated-args-first": """apiVersion: v1
kind: Deployment
metadata:
name: api
spec:
template:
spec:
containers:
- name: api
image: example:api
args:
- wrong
---
apiVersion: v1
kind: Deployment
metadata:
name: keycloak
namespace: keycloak
spec:
template:
spec:
containers:
- name: keycloak
image: expected:keycloak
args:
- start
env:
- name: KC_HTTP_ENABLED
value: "true"
""",
"unrelated-env-nearby": """apiVersion: v1
kind: Deployment
metadata:
name: keycloak
namespace: keycloak
spec:
template:
spec:
containers:
- name: sidecar
env:
- name: WRONG
value: "wrong"
- name: keycloak
image: expected:keycloak
args:
- start
env:
- name: KC_HTTP_ENABLED
value: "true"
""",
}
for name, output in samples.items():
print(name, parse(output))
PYRepository: openshift-online/hypershell Length of output: 50383 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- tool/version declarations ---'
rg -n -i -C 3 'kustomize|kubectl|kube[- ]?version|version.*kustomize|kustomize.*version' \
Makefile README.md scripts .github 2>/dev/null | head -250
printf '%s\n' '--- kustomization resource order ---'
for f in deploy/base/kustomization.yaml deploy/kind/kustomization.yaml \
deploy/kind-keycloak-optimized/kustomization.yaml; do
echo "--- $f"
cat -n "$f"
done
printf '%s\n' '--- deployment declarations and args/env locations ---'
rg -n '^(apiVersion: apps/v1|kind: Deployment| name: | - name: | args:| env:)' \
deploy/base deploy/kind deploy/kind-keycloak-optimized | head -250
printf '%s\n' '--- parser behavior probe ---'
python3 - <<'PY'
from pathlib import Path
import ast
source = Path("scripts/check_kustomize_overlays.py").read_text()
tree = ast.parse(source)
fn = next(
node for node in tree.body
if isinstance(node, ast.FunctionDef)
and node.name == "_grep_keycloak_container"
)
module = ast.Module(
body=[
node for node in tree.body
if isinstance(node, (ast.Import, ast.ImportFrom))
] + [fn],
type_ignores=[],
)
namespace = {}
exec(compile(module, "scripts/check_kustomize_overlays.py", "exec"), namespace)
parse = namespace["_grep_keycloak_container"]
samples = {
"keycloak-first": """apiVersion: v1
kind: Deployment
metadata:
name: keycloak
namespace: keycloak
spec:
template:
spec:
containers:
- name: keycloak
image: expected:keycloak
args:
- start
env:
- name: KC_HTTP_ENABLED
value: "true"
""",
"unrelated-args-first": """apiVersion: v1
kind: Deployment
metadata:
name: api
spec:
template:
spec:
containers:
- name: api
image: example:api
args:
- wrong
---
apiVersion: v1
kind: Deployment
metadata:
name: keycloak
namespace: keycloak
spec:
template:
spec:
containers:
- name: keycloak
image: expected:keycloak
args:
- start
env:
- name: KC_HTTP_ENABLED
value: "true"
""",
"unrelated-env-nearby": """apiVersion: v1
kind: Deployment
metadata:
name: keycloak
namespace: keycloak
spec:
template:
spec:
containers:
- name: sidecar
env:
- name: WRONG
value: "wrong"
- name: keycloak
image: expected:keycloak
args:
- start
env:
- name: KC_HTTP_ENABLED
value: "true"
""",
}
for name, output in samples.items():
print(name, parse(output))
PYRepository: openshift-online/hypershell Length of output: 31843 🌐 Web query:
💡 Result: The Kustomize CLI tool, specifically the Citations:
Parse the rendered YAML and bind fields to the Keycloak container. The 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| def main() -> int: | ||
| errors: list[str] = [] | ||
|
|
||
| for overlay, expectations in OVERLAYS.items(): | ||
| output = _kustomize_build(overlay) | ||
| if not output: | ||
| errors.append(f"{overlay}: kustomize build failed or produced no output") | ||
| continue | ||
|
|
||
| if "kind: Deployment" not in output or "name: keycloak" not in output: | ||
| errors.append(f"{overlay}: missing Keycloak Deployment") | ||
| continue | ||
|
|
||
| if not expectations: | ||
| continue | ||
|
|
||
| info = _grep_keycloak_container(output) | ||
|
|
||
| expected_image = expectations.get("keycloak_image") | ||
| if expected_image and info.get("image") != expected_image: | ||
| errors.append( | ||
| f"{overlay}: expected image '{expected_image}', " | ||
| f"got '{info.get('image', '<missing>')}'" | ||
| ) | ||
|
|
||
| expected_args = expectations.get("keycloak_args") | ||
| if expected_args and info.get("args") != expected_args: | ||
| errors.append( | ||
| f"{overlay}: expected args {expected_args}, " | ||
| f"got {info.get('args')}" | ||
| ) | ||
|
|
||
| expected_env = expectations.get("keycloak_env") | ||
| if expected_env: | ||
| actual_env = info.get("env", {}) | ||
| for key, value in expected_env.items(): | ||
| if actual_env.get(key) != value: | ||
| errors.append( | ||
| f"{overlay}: expected env {key}={value}, " | ||
| f"got {actual_env.get(key, '<missing>')}" | ||
| ) | ||
|
|
||
| if not errors: | ||
| return 0 | ||
|
|
||
| print("Kustomize overlay validation failures:", file=sys.stderr) | ||
| for error in errors: | ||
| print(f" {error}", file=sys.stderr) | ||
| return 1 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -238,10 +238,35 @@ kube create secret generic hypershell-oidc-session \ | |
| success "OIDC session secret created" | ||
| echo "" | ||
|
|
||
| # --- Build optimized Keycloak image (optional) --- | ||
| KUSTOMIZE_DIR="deploy/kind" | ||
| if [[ "${KIND_KEYCLOAK_OPTIMIZED:-false}" == "true" ]]; then | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Major] Add automated coverage for the enabled path. The successful E2E job executes the default
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 37a6da2. Added |
||
| header "Keycloak (optimized)" | ||
| KC_IMAGE="${keycloak_local:-localhost/hypershell-keycloak:dev-optimized}" | ||
| if ${CONTAINER_ENGINE} image inspect "${KC_IMAGE}" >/dev/null 2>&1; then | ||
| info "Image ${KC_IMAGE} already exists, reusing (run 'make kind-keycloak-build' to rebuild)" | ||
| else | ||
| info "Building optimized Keycloak image..." | ||
| ${CONTAINER_ENGINE} build -t "${KC_IMAGE}" "${REPO_ROOT}/deploy/kind/keycloak" | ||
| fi | ||
| info "Loading Keycloak image into Kind..." | ||
| KC_TAR="/tmp/hypershell-keycloak-dev.tar" | ||
| rm -f "${KC_TAR}" | ||
| ${CONTAINER_ENGINE} save -o "${KC_TAR}" "${KC_IMAGE}" | ||
| kind load image-archive "${KC_TAR}" --name "${KIND_CLUSTER_NAME}" | ||
| rm -f "${KC_TAR}" | ||
| success "Optimized Keycloak image loaded" | ||
| KUSTOMIZE_DIR="deploy/kind-keycloak-optimized" | ||
| echo "" | ||
| else | ||
| info "Keycloak optimization disabled (KIND_KEYCLOAK_OPTIMIZED=false), using stock image" | ||
| echo "" | ||
| fi | ||
|
|
||
| # --- Deploy all components via kustomize --- | ||
| header "Deploying Components" | ||
| info "Applying Kind manifests via kustomize..." | ||
| kustomize build deploy/kind | kube apply -f - | ||
| kustomize build "${KUSTOMIZE_DIR}" | kube apply -f - | ||
|
Comment on lines
+241
to
+269
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 6 '\bKIND_KEYCLOAK_URL\b|KUSTOMIZE_DIR|kustomize build' scripts/kind/up.shRepository: openshift-online/hypershell Length of output: 4692 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- scripts/kind/up.sh: setup through deployment ---'
sed -n '1,285p' scripts/kind/up.sh
printf '%s\n' '--- all external Keycloak URL references ---'
rg -n -C 4 'KIND_KEYCLOAK_URL|keycloak_local|KIND_KEYCLOAK_OPTIMIZED|kind-keycloak-optimized|keycloak' \
scripts/kind deploy/kind deploy/kind-keycloak-optimized 2>/dev/null || trueRepository: openshift-online/hypershell Length of output: 34848 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- repository-wide KIND_KEYCLOAK_URL references ---'
rg -n -C 3 'KIND_KEYCLOAK_URL' . --glob '!node_modules' --glob '!dist' --glob '!build'
printf '%s\n' '--- Kind kustomization resources and Keycloak objects ---'
sed -n '1,230p' deploy/kind/kustomization.yaml
printf '%s\n' '--- Kind resource files ---'
rg -n '^(resources:| - |kind:| name: keycloak|namespace: keycloak)' \
deploy/kind/kustomization.yaml deploy/kind --glob '*.yaml' --glob '*.yml'Repository: openshift-online/hypershell Length of output: 25720 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
script = Path("scripts/kind/up.sh").read_text()
kustomization = Path("deploy/kind/kustomization.yaml").read_text()
base = Path("deploy/base/kustomization.yaml").read_text()
checks = {
"optimized branch ignores external URL": (
'if [[ "${KIND_KEYCLOAK_OPTIMIZED:-false}" == "true" ]]' in script
and 'KIND_KEYCLOAK_URL' not in script[script.index("# --- Build optimized Keycloak image"):script.index("# --- Deploy all components")]
),
"deployment always builds selected Kustomize directory": (
'kustomize build "${KUSTOMIZE_DIR}" | kube apply -f -' in script
),
"Kind overlay includes base": "- ../base" in kustomization,
"base includes Keycloak resources": bool(re.search(r'keycloak', base, re.I)),
"external URL only guards readiness and restart": (
script.count('if [[ -z "${KIND_KEYCLOAK_URL:-}" ]]') >= 2
and 'KIND_KEYCLOAK_URL' not in script[script.index("# --- Build optimized Keycloak image"):script.index("# --- Deploy all components")]
),
}
for name, result in checks.items():
print(f"{'PASS' if result else 'FAIL'}: {name}")
PY
printf '%s\n' '--- base Kustomize resource declarations mentioning Keycloak ---'
rg -n -C 3 'keycloak' deploy/base deploy/kindRepository: openshift-online/hypershell Length of output: 18503 Honor 🤖 Prompt for AI Agents |
||
|
|
||
| info "Waiting for PostgreSQL..." | ||
| kube wait --for=condition=available deployment/hypershell-postgres -n "${KIND_NAMESPACE}" --timeout=300s | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Make the optimized Keycloak container filesystem read-only.
The final Keycloak container lacks
securityContext.readOnlyRootFilesystem: true. The Kubernetes manifest rule requires it. Ifdev-filemode needs writes, add scoped writable volumes for the required data and temporary paths before enabling the read-only root filesystem.🤖 Prompt for AI Agents
Source: Path instructions