Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions .github/scripts/deploy-es.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#!/usr/bin/env bash
# Story 16.5 — deploy single-node Elasticsearch container(s) into a kind cluster, one per
# federation sidecar, reachable at the in-cluster Service name the install job overrides each
# sidecars[].elasticsearch.url to. Used by .github/workflows/federation-helm.yml.
#
# Usage: deploy-es.sh <topology> <es-version-list>
# topology : single-cluster | three-region | heterogeneous-ready | es-version | secrets
# es-version-list : comma-separated ES major versions, e.g. "8" or "8,8,9"
#
# Service names (must match the --set sidecars[N].elasticsearch.url=... in the install jobs):
# * 1-ES topologies (single-cluster / es-version / secrets) -> Service "es"
# * 3-ES topologies (three-region / heterogeneous-ready) -> "es-us-east-1", "es-eu-west-1",
# "es-ap-south-1" (order = list order)
#
# Each ES runs single-node, security disabled, small heap (fits the 7 GB GitHub runner).
# `vm.max_map_count=262144` MUST already be set on the runner (the workflow does this).
set -euo pipefail

TOPOLOGY="${1:?usage: deploy-es.sh <topology> <es-version-list>}"
ES_LIST="${2:?usage: deploy-es.sh <topology> <es-version-list>}"

# ES major version -> full image coordinate (CLAUDE.md ES Version Matrix).
es_image() {
case "$1" in
6) echo "docker.elastic.co/elasticsearch/elasticsearch:6.8.23" ;;
7) echo "docker.elastic.co/elasticsearch/elasticsearch:7.17.29" ;;
8) echo "docker.elastic.co/elasticsearch/elasticsearch:8.18.3" ;;
9) echo "docker.elastic.co/elasticsearch/elasticsearch:9.0.3" ;;
*) echo "unsupported ES version: $1" >&2; exit 1 ;;
esac
}

# Resolve the Service names for this topology, positionally aligned with the ES list.
case "$TOPOLOGY" in
three-region|heterogeneous-ready)
NAMES=(es-us-east-1 es-eu-west-1 es-ap-south-1)
;;
single-cluster|es-version|secrets)
NAMES=(es)
;;
*)
echo "unsupported topology: $TOPOLOGY" >&2; exit 1 ;;
esac

# Split the comma-separated version list into an array.
IFS=',' read -r -a VERSIONS <<< "$ES_LIST"

if [ "${#VERSIONS[@]}" -gt "${#NAMES[@]}" ]; then
echo "::error::deploy-es.sh: ${#VERSIONS[@]} ES versions but only ${#NAMES[@]} service name(s) for topology $TOPOLOGY" >&2
exit 1
fi

deploy_one() {
local name="$1" version="$2" image
image="$(es_image "$version")"
echo "Deploying Elasticsearch ${version} as Service '${name}' (${image})"
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${name}
labels:
app: ${name}
role: ci-elasticsearch
spec:
replicas: 1
selector:
matchLabels:
app: ${name}
template:
metadata:
labels:
app: ${name}
role: ci-elasticsearch
spec:
containers:
- name: elasticsearch
image: ${image}
env:
- name: discovery.type
value: single-node
- name: xpack.security.enabled
value: "false"
- name: ES_JAVA_OPTS
value: "-Xms512m -Xmx512m"
# ES 8/9 default to HTTPS + security ON; the env above disables it, but be explicit.
- name: xpack.security.http.ssl.enabled
value: "false"
ports:
- containerPort: 9200
readinessProbe:
httpGet:
path: /_cluster/health?wait_for_status=yellow&timeout=1s
port: 9200
initialDelaySeconds: 20
periodSeconds: 5
failureThreshold: 30
resources:
requests:
memory: 1Gi
limits:
memory: 1536Mi
---
apiVersion: v1
kind: Service
metadata:
name: ${name}
labels:
role: ci-elasticsearch
spec:
selector:
app: ${name}
ports:
- port: 9200
targetPort: 9200
EOF
}

i=0
for v in "${VERSIONS[@]}"; do
deploy_one "${NAMES[$i]}" "$v"
i=$((i + 1))
done

# Wait for every ES Deployment to become Available before the install job starts the sidecars
# (FACT D: the federation only goes Ready once every downstream is reachable).
for ((j = 0; j < ${#VERSIONS[@]}; j++)); do
echo "Waiting for Elasticsearch Deployment '${NAMES[$j]}' to be Available..."
kubectl wait --for=condition=Available "deployment/${NAMES[$j]}" --timeout=300s
done

echo "All Elasticsearch instances ready for topology '${TOPOLOGY}'."
67 changes: 67 additions & 0 deletions .github/scripts/install-sealed-secrets.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
# Story 16.5 — install the Bitnami SealedSecrets controller into a kind cluster, then seal an
# Elasticsearch-auth Secret with the IN-CLUSTER controller certificate and apply it, so the
# controller materializes a real K8s Secret the chart can reference. Used by the
# `install-secrets` (sealed-secrets) job of .github/workflows/federation-helm.yml.
#
# Usage: install-sealed-secrets.sh <secret-name>
# secret-name : the name of the Secret the controller will materialize (chart references this
# via sidecars[0].elasticsearch.credentialsSecretName), e.g. "es-creds".
#
# Why re-seal in CI (project_sealed_secrets_review_gotchas):
# * The committed examples/sealed-secrets/*.yaml are NON-FUNCTIONAL placeholders — a SealedSecret
# is decryptable ONLY by the specific controller instance that issued the cert. We therefore
# create a fresh plaintext Secret, `kubeseal` it with the live cluster's cert, and apply.
# * The kubeseal CLI version is pinned to the controller chart appVersion (NOT the chart version).
set -euo pipefail

SECRET_NAME="${1:?usage: install-sealed-secrets.sh <secret-name>}"

# Pin the controller chart + kubeseal CLI so the CLI/controller versions match (gotcha:
# kubeseal CLI version must equal the controller appVersion, not the Helm chart version).
SEALED_SECRETS_CHART_VERSION="2.16.2" # helm chart version
KUBESEAL_VERSION="0.27.2" # == controller appVersion of chart 2.16.2

# 1. Install the controller (repo moved bitnami-labs -> bitnami.github.io).
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets >/dev/null 2>&1 || \
helm repo add sealed-secrets https://bitnami.github.io/sealed-secrets
helm repo update sealed-secrets
helm upgrade --install sealed-secrets sealed-secrets/sealed-secrets \
--namespace kube-system \
--version "${SEALED_SECRETS_CHART_VERSION}" \
--set fullnameOverride=sealed-secrets-controller \
--wait --timeout 180s

# 2. Install the matching kubeseal CLI.
curl -sSL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz" \
| tar -xz kubeseal
sudo install -m 0755 kubeseal /usr/local/bin/kubeseal
rm -f kubeseal

# 3. Wait for the controller to be Ready, then fetch its public cert.
kubectl rollout status deployment/sealed-secrets-controller -n kube-system --timeout=180s
kubeseal --controller-name=sealed-secrets-controller --controller-namespace=kube-system \
--fetch-cert > /tmp/sealed-secrets-cert.pem

# 4. Build a plaintext ES-auth Secret (matching the 16.3 es-* key contract), seal it with the
# live cert, and apply the SealedSecret (the plaintext Secret is NEVER applied to the cluster).
kubectl create secret generic "${SECRET_NAME}" \
--dry-run=client -o yaml \
--from-literal=es-auth-method=basic \
--from-literal=es-username=elastic \
--from-literal=es-password=changeme \
| kubeseal --cert /tmp/sealed-secrets-cert.pem --format yaml \
| kubectl apply -f -

# 5. Wait for the controller to materialize the real Secret from the SealedSecret.
for _ in $(seq 1 30); do
if kubectl get secret "${SECRET_NAME}" >/dev/null 2>&1; then
echo "SealedSecrets materialized Secret '${SECRET_NAME}'."
exit 0
fi
sleep 2
done

echo "::error::SealedSecrets controller did not materialize Secret '${SECRET_NAME}' in time" >&2
kubectl get sealedsecret,secret 2>/dev/null || true
exit 1
Loading
Loading