Skip to content

Commit 29056d9

Browse files
committed
feat: configurable template
1 parent c51580a commit 29056d9

File tree

6 files changed

+186
-53
lines changed

6 files changed

+186
-53
lines changed

example/jupyterhub/job.hcl.j2

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
job "{{ job_name }}" {
2+
3+
type = "service"
4+
datacenters = {{ datacenters | tojson}}
5+
6+
meta {
7+
jupyterhub_user = "{{ username }}"
8+
{% if notebook_name %}
9+
jupyterhub_notebook = "{{ notebook_name }}"
10+
{% endif %}
11+
}
12+
group "nb" {
13+
14+
{% if policies %}
15+
vault {
16+
policies = {{ policies | tojson }}
17+
}
18+
{% endif %}
19+
{% if volume_data %}
20+
volume "{{ volume_data.volume_name }}" {
21+
type = "{{ volume_data.type }}"
22+
read_only = false
23+
source = "{{ volume_data.source }}"
24+
25+
{% if volume_data.type == "csi" %}
26+
attachment_mode = "file-system"
27+
access_mode = "single-node-writer"
28+
{% endif %}
29+
}
30+
{% endif %}
31+
32+
network {
33+
port "notebook" {
34+
to = 8888
35+
}
36+
}
37+
38+
task "nb" {
39+
driver = "docker"
40+
41+
config {
42+
image = "{{ image }}"
43+
ports = [ "notebook" ]
44+
45+
args = {{ args | tojson }}
46+
}
47+
env {
48+
{% for key, value in env|dictsort %}
49+
{{ key }} = {{ value | tojson}}
50+
{% endfor %}
51+
JUPYTER_ENABLE_LAB="yes"
52+
# GRANT_SUDO="yes"
53+
}
54+
55+
resources {
56+
cpu = {{ cpu }}
57+
memory = {{ memory }}
58+
}
59+
60+
{% if volume_data %}
61+
volume_mount {
62+
volume = "{{ volume_data.volume_name }}"
63+
destination = "{{ volume_data.destination }}"
64+
read_only = false
65+
}
66+
{% endif %}
67+
68+
template {
69+
data = <<-EOH
70+
[[ with nomadVar "openai" ]]
71+
OPENAI_AI_KEY=[[ .openai_ai_key ]]
72+
[[ end ]]
73+
EOH
74+
destination = "secrets/openai.env"
75+
env = true
76+
left_delimiter = "[["
77+
right_delimiter = "]]"
78+
}
79+
}
80+
81+
service {
82+
name = "{{ service_name }}"
83+
provider = "{{ service_provider }}"
84+
port = "notebook"
85+
check {
86+
name = "alive"
87+
type = "tcp"
88+
interval = "10s"
89+
timeout = "2s"
90+
}
91+
}
92+
}
93+
}

example/jupyterhub/jupyterhub.nomad

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -40,46 +40,13 @@ NOMAD_ADDR=http://host.docker.internal:4646
4040
}
4141
template {
4242
destination = "/local/jupyterhub_config.py"
43-
44-
data = <<EOF
45-
import json
46-
import os
47-
import socket
48-
49-
from jupyterhub.auth import DummyAuthenticator
50-
import tarfile
51-
c.JupyterHub.spawner_class = "nomad-spawner"
52-
c.JupyterHub.bind_url = "http://0.0.0.0:8000"
53-
c.JupyterHub.hub_bind_url = "http://0.0.0.0:8081"
54-
55-
c.JupyterHub.hub_connect_url = f"http://{os.environ.get('NOMAD_IP_api')}:{os.environ.get('NOMAD_HOST_PORT_api')}"
56-
c.JupyterHub.log_level = "DEBUG"
57-
c.ConfigurableHTTPProxy.debug = True
58-
59-
60-
c.JupyterHub.allow_named_servers = True
61-
c.JupyterHub.named_server_limit_per_user = 5
62-
63-
c.JupyterHub.authenticator_class = DummyAuthenticator
64-
65-
c.NomadSpawner.datacenters = ["dc1", "dc2", "dc3"]
66-
c.NomadSpawner.csi_plugin_ids = ["nfs", "hostpath-plugin0"]
67-
c.NomadSpawner.mem_limit = "2G"
68-
c.NomadSpawner.common_images = ["jupyter/minimal-notebook:2023-06-26"]
69-
70-
def csi_volume_parameters(spawner):
71-
if spawner.user_options["volume_csi_plugin_id"] == "nfs":
72-
return {
73-
"gid" : "1000",
74-
"uid" : "1000"
75-
}
76-
else:
77-
return None
78-
c.NomadSpawner.csi_volume_parameters = csi_volume_parameters
79-
80-
EOF
81-
82-
43+
data = file("jupyterhub_config.py")
44+
}
45+
template {
46+
destination = "/local/job.hcl.j2"
47+
data = file("job.hcl.j2")
48+
left_delimiter = "<[{"
49+
right_delimiter = "}]>"
8350
}
8451

8552
resources {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# flake8: noqa
2+
import os
3+
4+
from jupyterhub.auth import DummyAuthenticator
5+
6+
c.JupyterHub.spawner_class = "nomad-spawner"
7+
c.JupyterHub.bind_url = "http://0.0.0.0:8000"
8+
c.JupyterHub.hub_bind_url = "http://0.0.0.0:8081"
9+
10+
c.JupyterHub.hub_connect_url = (
11+
f"http://{os.environ.get('NOMAD_IP_api')}:{os.environ.get('NOMAD_HOST_PORT_api')}"
12+
)
13+
c.JupyterHub.log_level = "DEBUG"
14+
c.ConfigurableHTTPProxy.debug = True
15+
16+
17+
c.JupyterHub.allow_named_servers = True
18+
c.JupyterHub.named_server_limit_per_user = 5
19+
20+
c.JupyterHub.authenticator_class = DummyAuthenticator
21+
22+
c.NomadSpawner.datacenters = ["dc1", "dc2", "dc3"]
23+
c.NomadSpawner.csi_plugin_ids = ["nfs", "hostpath-plugin0"]
24+
c.NomadSpawner.mem_limit = "2G"
25+
c.NomadSpawner.common_images = ["jupyter/minimal-notebook:2023-06-26"]
26+
27+
28+
def csi_volume_parameters(spawner):
29+
if spawner.user_options["volume_csi_plugin_id"] == "nfs":
30+
return {"gid": "1000", "uid": "1000"}
31+
else:
32+
return None
33+
34+
35+
c.NomadSpawner.csi_volume_parameters = csi_volume_parameters
36+
37+
c.NomadSpawner.job_template_path = "/local/job.hcl.j2"

example/jupyterhub/main.tf

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,22 @@ terraform {
1111
provider "nomad" {
1212
address = "http://localhost:4646"
1313
}
14-
14+
# https://github.com/hashicorp/terraform-provider-nomad/issues/288
15+
# resource "nomad_variable" "dummy_openai_key" {
16+
# path = "openai"
17+
# items = {
18+
# open_ai_key : "sk_dummy-key"
19+
# }
20+
# }
1521
resource "nomad_job" "jupyterhub" {
1622
jobspec = templatefile("jupyterhub.nomad", {
1723

1824
})
25+
26+
hcl2 {
27+
enabled = true
28+
allow_fs = true
29+
30+
}
31+
1932
}

jupyterhub_nomad_spawner/job_factory.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
import os
12
from enum import Enum
23
from typing import Dict, List, Optional
34

4-
from jinja2 import Environment, PackageLoader, select_autoescape
5+
from jinja2 import (
6+
BaseLoader,
7+
Environment,
8+
FileSystemLoader,
9+
PackageLoader,
10+
select_autoescape,
11+
)
512
from pydantic import BaseModel
613

714

@@ -49,12 +56,21 @@ class Config:
4956
use_enum_values = True
5057

5158

52-
def create_job(job_data: JobData) -> str:
53-
env = Environment(
54-
loader=PackageLoader("jupyterhub_nomad_spawner"), autoescape=select_autoescape()
55-
)
59+
def create_job(job_data: JobData, job_template_path: Optional[str] = None) -> str:
60+
# if present, split up and use head as filesystem loader and tail as template name
61+
loader: BaseLoader
5662

57-
template = env.get_template("job.hcl.j2")
63+
if job_template_path:
64+
head_tail = os.path.split(job_template_path)
65+
loader = FileSystemLoader(head_tail[0])
66+
template_name = head_tail[1]
67+
68+
else:
69+
loader = PackageLoader("jupyterhub_nomad_spawner")
70+
template_name = "job.hcl.j2"
71+
env = Environment(loader=loader, autoescape=select_autoescape())
72+
73+
template = env.get_template(template_name)
5874
job_hcl = template.render(**job_data.dict())
5975

6076
return job_hcl

jupyterhub_nomad_spawner/spawner.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
from traitlets import Bool
1313
from traitlets import Callable as TCallable
1414
from traitlets import Dict as TDict
15+
from traitlets import Enum as TEnum
1516
from traitlets import Int as TInt
1617
from traitlets import List as TList
17-
from traitlets import Unicode, Enum as TEnum
18+
from traitlets import Unicode
1819
from traitlets import Union as TUnion
1920
from traitlets import default
2021

@@ -334,6 +335,13 @@ def service_name(self) -> str:
334335
return f"{self.job_name}"
335336
raise ValueError("notebook_id is not set")
336337

338+
job_template_path = Unicode(
339+
help="""
340+
The path to the job template file with jinja2 placeholders
341+
see templates/job.hcl.j2
342+
"""
343+
).tag(config=True)
344+
337345
async def start(self):
338346
nomad_service_config = build_nomad_config_from_options(self)
339347
nomad_httpx_client = build_nomad_httpx_client(nomad_service_config)
@@ -363,8 +371,9 @@ async def start(self):
363371
elif self.vault_policies:
364372
policies = self.vault_policies
365373

374+
self.log.info("scheduling job %s", self.job_name)
366375
job_hcl = create_job(
367-
JobData(
376+
job_data=JobData(
368377
job_name=self.job_name,
369378
username=self.user.name,
370379
notebook_name=self.name,
@@ -377,7 +386,8 @@ async def start(self):
377386
memory=self.user_options["memory"],
378387
volume_data=volume_data,
379388
policies=policies,
380-
)
389+
),
390+
job_template_path=self.job_template_path,
381391
)
382392

383393
await nomad_service.schedule_job(job_hcl)
@@ -470,9 +480,6 @@ async def address_and_port_from_nomad(
470480
self.log.info("Getting service %s from nomad", self.service_name)
471481
return await nomad_service.get_service_address(self.job_name)
472482

473-
474-
475-
476483
async def poll(self):
477484
nomad_httpx_client = build_nomad_httpx_client(
478485
build_nomad_config_from_options(self)

0 commit comments

Comments
 (0)