Skip to content

Commit 7fa8596

Browse files
Merge branch 'main' into production
2 parents b2f7d4d + 8556dd1 commit 7fa8596

13 files changed

Lines changed: 701 additions & 233 deletions

File tree

.github/workflows/deploy-to-aks.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ jobs:
5252
OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }}
5353
OAUTH_CALLBACK_URL: ${{ secrets.OAUTH_CALLBACK_URL }}
5454
K8S_NAMESPACE: ${{ vars.K8S_NAMESPACE }}
55+
PYTHON_VERSION: ${{ vars.PYTHON_VERSION }}
56+
CONTAINER_LIFETIME: ${{ vars.CONTAINER_LIFETIME }}
5557

5658
- name: Connect to AKS
5759
run: az aks get-credentials --resource-group ${{ vars.AZURE_RESOURCE_GROUP }} --name ${{ vars.AZURE_KUBERNETES_CLUSTER }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ values.yaml
99
*/policy/config-*.yaml
1010
**/templates/privacy-policy.html
1111
**/templates/terms-of-use.html
12+
13+
.env

Makefile

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
2+
# Make expects SHELL to be a path (no args). Use bash directly for portability.
3+
SHELL := /bin/bash
4+
.ONESHELL:
5+
.SHELLFLAGS := -euo pipefail -c
6+
.SILENT:
7+
8+
MAKEFLAGS += --no-print-directory
9+
10+
# Defaults (override via environment or `make VAR=value ...`)
11+
LOCAL ?= True
12+
CLUSTER_NAME ?= aiidalab-demo-server-local
13+
NAMESPACE ?= local
14+
RELEASE_NAME ?= aiidalab-demo-server
15+
VALUES_TEMPLATE ?= basehub/values.yaml.j2
16+
VALUES_FILE ?= ${VALUES_TEMPLATE:.j2=}
17+
CHART_LOCK_FILE ?= basehub/Chart.lock
18+
KIND_CONFIG_FILE ?= kind-config.yaml
19+
HELM_DEP_RETRIES ?= 5
20+
21+
help: ## Show available targets
22+
awk 'BEGIN {FS = ":.*##"} /^[a-zA-Z0-9_.-]+:.*##/ {printf "\033[36m%-22s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
23+
24+
generate-values: ## Render basehub/values.yaml from .env + Jinja2 template
25+
if [[ ! -f "$(VALUES_TEMPLATE)" ]]; then
26+
echo "Template not found: $(VALUES_TEMPLATE) (skipping)" >&2
27+
exit 0
28+
fi
29+
if ! command -v jinja2 >/dev/null 2>&1; then
30+
echo "Missing required command: jinja2" >&2
31+
exit 1
32+
fi
33+
if [[ -f .env ]]; then
34+
set -a
35+
source .env
36+
set +a
37+
fi
38+
LOCAL=${LOCAL} jinja2 "$(VALUES_TEMPLATE)" -o "$(VALUES_FILE)"
39+
echo "Wrote $(VALUES_FILE)"
40+
41+
up: _check-for-values ## Create kind cluster (if needed) and deploy helm release
42+
$(MAKE) _local_cmd CMD=up
43+
44+
refresh: _check-for-values ## Re-deploy helm release (fast; no kind create)
45+
$(MAKE) _local_cmd CMD=refresh
46+
47+
restart: ## Restart hub/proxy deployments (no helm upgrade)
48+
$(MAKE) _local_cmd CMD=restart
49+
50+
port-forward: ## Forward http://localhost:8000 -> service/proxy-public
51+
$(MAKE) _local_cmd CMD=port-forward
52+
53+
status: ## Show pods/services in the namespace
54+
$(MAKE) _local_cmd CMD=status
55+
56+
down: ## Uninstall release and delete kind cluster
57+
$(MAKE) _local_cmd CMD=down
58+
59+
clean: ## Remove generated files
60+
rm -f "$(VALUES_FILE)"
61+
rm -f "$(CHART_LOCK_FILE)"
62+
63+
_check-for-values:
64+
if [[ ! -f "$(VALUES_FILE)" ]]; then
65+
echo "Values file not found: $(VALUES_FILE). Run 'make generate-values' first." >&2
66+
exit 1
67+
fi
68+
69+
_local_cmd:
70+
require_cmd() { command -v "$$1" >/dev/null 2>&1 || { echo "Missing required command: $$1" >&2; exit 1; }; }
71+
has_cmd() { command -v "$$1" >/dev/null 2>&1; }
72+
73+
ensure_helm_repos() {
74+
if ! helm repo list 2>/dev/null | awk 'NR>1 {print $$1}' | grep -qx "jupyterhub"; then
75+
helm repo add jupyterhub https://jupyterhub.github.io/helm-chart/ >/dev/null
76+
fi
77+
helm repo update >/dev/null
78+
}
79+
80+
retry() {
81+
local -r attempts="$$1"; shift
82+
local attempt=1
83+
while true; do
84+
if "$$@"; then return 0; fi
85+
if [[ "$$attempt" -ge "$$attempts" ]]; then return 1; fi
86+
local sleep_s=$$((attempt * 5))
87+
echo "Retrying ($$attempt/$$attempts) after $${sleep_s}s: $$*" >&2
88+
sleep "$$sleep_s"
89+
attempt=$$((attempt + 1))
90+
done
91+
}
92+
93+
kind_cluster_exists() { kind get clusters 2>/dev/null | grep -qx "$(CLUSTER_NAME)"; }
94+
95+
deploy_release() {
96+
echo "Deploying helm release '$(RELEASE_NAME)' into namespace '$(NAMESPACE)'"
97+
ensure_helm_repos
98+
retry "$(HELM_DEP_RETRIES)" helm dependency build basehub >/dev/null || true
99+
helm upgrade \
100+
--install \
101+
--cleanup-on-fail \
102+
--create-namespace --namespace="$(NAMESPACE)" \
103+
-f "$(VALUES_FILE)" \
104+
"$(RELEASE_NAME)" basehub
105+
}
106+
107+
wait_ready() {
108+
echo "Waiting for hub and proxy to be ready..."
109+
kubectl -n "$(NAMESPACE)" rollout status deploy/hub --timeout=300s
110+
kubectl -n "$(NAMESPACE)" rollout status deploy/proxy --timeout=300s
111+
}
112+
113+
restart_pods() {
114+
echo "Restarting hub and proxy deployments..."
115+
kubectl -n "$(NAMESPACE)" rollout restart deploy/hub
116+
kubectl -n "$(NAMESPACE)" rollout restart deploy/proxy
117+
wait_ready
118+
}
119+
120+
case "$${CMD:-}" in
121+
up)
122+
require_cmd kubectl
123+
require_cmd helm
124+
if has_cmd kind; then
125+
if ! kind_cluster_exists; then
126+
echo "Creating kind cluster: $(CLUSTER_NAME)"
127+
if [[ -f "$(KIND_CONFIG_FILE)" ]]; then
128+
kind create cluster --name "$(CLUSTER_NAME)" --config "$(KIND_CONFIG_FILE)"
129+
else
130+
kind create cluster --name "$(CLUSTER_NAME)"
131+
fi
132+
else
133+
echo "Using existing kind cluster: $(CLUSTER_NAME)"
134+
fi
135+
else
136+
echo "kind not found; deploying to current kubectl context."
137+
fi
138+
deploy_release
139+
restart_pods
140+
echo
141+
echo "Next: open http://localhost:8000"
142+
echo "- If your kind cluster has port mappings, it should work directly."
143+
echo "- Otherwise, run: make port-forward"
144+
echo "Login with any username and password: demo"
145+
;;
146+
refresh)
147+
require_cmd kubectl
148+
require_cmd helm
149+
deploy_release
150+
restart_pods
151+
;;
152+
restart)
153+
require_cmd kubectl
154+
restart_pods
155+
;;
156+
port-forward)
157+
require_cmd kubectl
158+
echo "Forwarding service/proxy-public to http://localhost:8000 (Ctrl+C to stop)"
159+
kubectl -n "$(NAMESPACE)" port-forward svc/proxy-public 8000:80
160+
;;
161+
status)
162+
require_cmd kubectl
163+
kubectl -n "$(NAMESPACE)" get pods,svc
164+
;;
165+
down)
166+
require_cmd helm
167+
require_cmd kubectl
168+
echo "Uninstalling helm release '$(RELEASE_NAME)' from namespace '$(NAMESPACE)' (if present)"
169+
helm -n "$(NAMESPACE)" uninstall "$(RELEASE_NAME)" 2>/dev/null || true
170+
echo "Deleting namespace '$(NAMESPACE)' (if present)"
171+
kubectl delete namespace "$(NAMESPACE)" 2>/dev/null || true
172+
if has_cmd kind && kind_cluster_exists; then
173+
echo "Deleting kind cluster: $(CLUSTER_NAME)"
174+
kind delete cluster --name "$(CLUSTER_NAME)"
175+
fi
176+
;;
177+
*)
178+
echo "Unknown CMD='$${CMD:-}'. Try: make help" >&2
179+
exit 2 &2>/dev/null
180+
;;
181+
esac

README.md

Lines changed: 98 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,10 @@ curl https://raw.githubusercontent.com/helm/helm/HEAD/scripts/get-helm-3 | bash
186186

187187
## Generating policy documents for an AiiDAlab deployment
188188

189-
In the `basehub/policy` folder:
190-
191-
- Fill out the required variables in `config.yaml`
192-
- Run `source generate.sh` to generate the policy documents
193-
194-
See `basehub/policy/README.md` for more information.
189+
Policy document templates are available at https://github.com/aiidalab/aiidalab-deployment-files.
190+
Follow the instructions there to generate the policy documents for your deployment.
191+
You can then deploy the generated documents in `basehub/files/etc/jupyterhub/templates`.
192+
Once deployed, set the `INCLUDE_POLICIES` environment variable to `true`. The deployment procedure will then introduce links to the documents in the JupyterHub UI.
195193

196194
## Install JupyterHub
197195

@@ -205,24 +203,25 @@ source k8s-deploy-venv/bin/activate
205203

206204
## Install the requirements
207205
python3 -m pip install -r requirements.txt
208-
```
209-
210-
Render the `values.yaml` file with the following command:
211206

212-
```bash
213-
jinja2 --format=env basehub/values.yaml.j2 > basehub/values.yaml
214207
```
215208

216-
The following environment variables are required to be set:
209+
The `values.yaml` file requires the following environment variables to be set:
217210

218211
- `K8S_NAMESPACE`: The namespace where the JupyterHub will be installed, e.g. `production`, `staging`.
219212
- `OAUTH_CLIENT_ID`: The client ID of the GitHub app.
220213
- `OAUTH_CLIENT_SECRET`: The client secret of the GitHub app.
221214
- `OAUTH_CALLBACK_URL`: The callback URL of the GitHub app.
222215

223-
We use GitHub oauthenticator, the users will be able to login with their GitHub account.
216+
We use GitHub oauthenticator to allow users to login with their GitHub account.
224217
The authentication is created using the `aiidalab` org with app name `aiidalab-demo-production` and `aiidalab-demo-staging` for the production and staging environments respectively.
225218

219+
Render the `values.yaml` file with the following command:
220+
221+
```bash
222+
make generate-values LOCAL=false
223+
```
224+
226225
To deploy the JupyterHub, run the following command:
227226

228227
```bash
@@ -267,3 +266,89 @@ proxy:
267266
letsencrypt:
268267
contactEmail: <your-email-address>
269268
```
269+
270+
## Local deployment for development
271+
272+
For quick iteration on the demo server UI (templates, static assets, chart wiring), you can deploy the Helm chart to a local Kubernetes cluster (recommended: [kind](https://kind.sigs.k8s.io/)).
273+
274+
### Prerequisites
275+
276+
- `kind`
277+
- `kubectl`
278+
- `helm`
279+
- `make`
280+
- `jinja2` (from `jinja2-cli`, installed via `requirements.txt`)
281+
282+
See [here](https://kind.sigs.k8s.io/docs/user/quick-start/#installing-from-release-binaries) for `kind` installation instructions.
283+
284+
### Configure and generate values
285+
286+
This repo uses a Makefile-based workflow for local deployment.
287+
288+
1. Create a python environment and install the templating dependencies, for example:
289+
290+
```bash
291+
python3 -m venv k8s-deploy-venv
292+
source k8s-deploy-venv/bin/activate
293+
python3 -m pip install -r requirements.txt
294+
```
295+
296+
2. Create a `.env` file (or export variables in your shell). For GitHub OAuth (see **Note** below) you typically need:
297+
298+
- `OAUTH_CLIENT_ID`
299+
- `OAUTH_CLIENT_SECRET`
300+
- `OAUTH_CALLBACK_URL` (for local: `http://localhost:8000/hub/oauth_callback`)
301+
302+
!!! note
303+
304+
For local development, you can [create a GitHub OAuth app](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) in your own GitHub account.
305+
Make sure to set the following:
306+
- **Homepage URL**: `http://localhost:8000`.
307+
- **Authorization callback URL**: `http://localhost:8000/hub/oauth_callback`.
308+
309+
3. Render the values.yaml file used by Helm:
310+
311+
```bash
312+
make generate-values
313+
```
314+
315+
By default this writes `basehub/values.yaml` from `basehub/values.yaml.j2` with `LOCAL=True`.
316+
317+
### Run
318+
319+
```bash
320+
make up
321+
```
322+
323+
Then open `http://localhost:8000`.
324+
325+
If `http://localhost:8000` is not reachable (common on kind without port mappings), run:
326+
327+
```bash
328+
make port-forward
329+
```
330+
331+
### Applying changes while developing
332+
333+
Edits to Helm values, templates, and the bundled static assets are not hot-reloaded automatically.
334+
Re-apply changes with:
335+
336+
```bash
337+
make refresh
338+
```
339+
340+
### Tear down
341+
342+
```bash
343+
make down
344+
```
345+
346+
### Useful overrides
347+
348+
You can override defaults at invocation time, e.g.:
349+
350+
```bash
351+
make NAMESPACE=local RELEASE_NAME=aiidalab-demo-server up
352+
```
353+
354+
Run `make help` to see available targets and defaults.

0 commit comments

Comments
 (0)