1
+ """A kopf handler for the Jupyter CRD.
2
+ """
1
3
import logging
2
4
import random
3
5
import os
8
10
import kopf
9
11
10
12
# Some (key) default deployment variables...
11
- default_image = "jupyter/minimal-notebook:notebook-6.3.0"
12
- default_sa = "default"
13
- default_cpu_limit = "1"
14
- default_cpu_request = "10m"
15
- default_mem_limit = "1Gi"
16
- default_mem_request = "256Mi"
17
- default_user_id = 1000
18
- default_group_id = 100
19
- default_ingress_proxy_body_size = "500m"
13
+ _DEFAULT_IMAGE = "jupyter/minimal-notebook:notebook-6.3.0"
14
+ _DEFAULT_SA = "default"
15
+ _DEFAULT_CPU_LIMIT = "1"
16
+ _DEFAULT_CPU_REQUEST = "10m"
17
+ _DEFAULT_MEM_LIMIT = "1Gi"
18
+ _DEFAULT_MEM_REQUEST = "256Mi"
19
+ _DEFAULT_USER_ID = 1000
20
+ _DEFAULT_GROUP_ID = 100
21
+ _DEFAULT_INGRESS_PROXY_BODY_SIZE = "500m"
20
22
21
23
# The ingress class
22
- ingress_class = "nginx"
24
+ _INGRESS_CLASS = "nginx"
23
25
# The ingress domain must be provided.
24
26
ingress_domain = os .environ ["INGRESS_DOMAIN" ]
25
27
# The ingress TLS secret is optional.
49
51
# As part of the startup we erase the existing '~/.bashrc' and,
50
52
# as a minimum, set a more suitable PS1 (see ch2385).
51
53
# 'conda init' then puts its stuff into the same file.
52
- notebook_startup = """#!/bin/bash
54
+ _NOTEBOOK_STARTUP = """#!/bin/bash
53
55
echo "PS1='\$(pwd) \$UID$ '" > ~/.bashrc
54
56
echo "umask 0002" >> ~/.bashrc
55
57
conda init
70
72
71
73
# The bash-profile
72
74
# which simply launches the .bashrc
73
- bash_profile = """if [ -f ~/.bashrc ]; then
75
+ _BASH_PROFILE = """if [ -f ~/.bashrc ]; then
74
76
source ~/.bashrc
75
77
fi
76
78
"""
79
81
# A ConfigMap whose content is written into '/etc'
80
82
# and copied to the $HOME/.jupyter by the notebook_startup
81
83
# script (above).
82
- notebook_config = """{
84
+ _NOTEBOOK_CONFIG = """{
83
85
"NotebookApp": {
84
86
"token": "%(token)s",
85
87
"base_url": "%(base_url)s"
@@ -97,6 +99,14 @@ def configure(settings: kopf.OperatorSettings, **_: Any) -> None:
97
99
98
100
@kopf .on .create ("squonk.it" , "v1" , "jupyternotebooks" , id = "jupyter" )
99
101
def create (spec : Dict [str , Any ], name : str , namespace : str , ** _ : Any ) -> Dict [str , Any ]:
102
+ """Handler for CRD create events.
103
+ Here we construct the required Kubernetes objects,
104
+ adopting them in kopf before using the corresponding Kubernetes API
105
+ to create them.
106
+
107
+ We handle errors typically raising 'kopf.PermanentError' to prevent
108
+ Kubernetes constantly calling back for a given create.
109
+ """
100
110
101
111
characters = string .ascii_letters + string .digits
102
112
token = "" .join (random .sample (characters , 16 ))
@@ -113,22 +123,22 @@ def create(spec: Dict[str, Any], name: str, namespace: str, **_: Any) -> Dict[st
113
123
"apiVersion" : "v1" ,
114
124
"kind" : "ConfigMap" ,
115
125
"metadata" : {"name" : "bp-%s" % name , "labels" : {"app" : name }},
116
- "data" : {".bash_profile" : bash_profile },
126
+ "data" : {".bash_profile" : _BASH_PROFILE },
117
127
}
118
128
119
129
startup_cm_body = {
120
130
"apiVersion" : "v1" ,
121
131
"kind" : "ConfigMap" ,
122
132
"metadata" : {"name" : "startup-%s" % name , "labels" : {"app" : name }},
123
- "data" : {"start.sh" : notebook_startup },
133
+ "data" : {"start.sh" : _NOTEBOOK_STARTUP },
124
134
}
125
135
126
136
config_vars = {"token" : token , "base_url" : name }
127
137
config_cm_body = {
128
138
"apiVersion" : "v1" ,
129
139
"kind" : "ConfigMap" ,
130
140
"metadata" : {"name" : "config-%s" % name , "labels" : {"app" : name }},
131
- "data" : {"jupyter_notebook_config.json" : notebook_config % config_vars },
141
+ "data" : {"jupyter_notebook_config.json" : _NOTEBOOK_CONFIG % config_vars },
132
142
}
133
143
134
144
kopf .adopt (bp_cm_body )
@@ -152,14 +162,14 @@ def create(spec: Dict[str, Any], name: str, namespace: str, **_: Any) -> Dict[st
152
162
153
163
notebook_interface = material .get ("notebook" , {}).get ("interface" , "lab" )
154
164
155
- image = material .get ("image" , default_image )
156
- service_account = material .get ("serviceAccountName" , default_sa )
165
+ image = material .get ("image" , _DEFAULT_IMAGE )
166
+ service_account = material .get ("serviceAccountName" , _DEFAULT_SA )
157
167
158
168
resources = material .get ("resources" , {})
159
- cpu_limit = resources .get ("limits" , {}).get ("cpu" , default_cpu_limit )
160
- cpu_request = resources .get ("requests" , {}).get ("cpu" , default_cpu_request )
161
- memory_limit = resources .get ("limits" , {}).get ("memory" , default_mem_limit )
162
- memory_request = resources .get ("requests" , {}).get ("memory" , default_mem_request )
169
+ cpu_limit = resources .get ("limits" , {}).get ("cpu" , _DEFAULT_CPU_LIMIT )
170
+ cpu_request = resources .get ("requests" , {}).get ("cpu" , _DEFAULT_CPU_REQUEST )
171
+ memory_limit = resources .get ("limits" , {}).get ("memory" , _DEFAULT_MEM_LIMIT )
172
+ memory_request = resources .get ("requests" , {}).get ("memory" , _DEFAULT_MEM_REQUEST )
163
173
164
174
# Data Manager API compliance.
165
175
#
@@ -170,10 +180,10 @@ def create(spec: Dict[str, Any], name: str, namespace: str, **_: Any) -> Dict[st
170
180
# as the Kubernetes 'File System Group' (fsGroup).
171
181
# This should allow us to run and manipulate the files.
172
182
sc_run_as_user = material .get ("securityContext" , {}).get (
173
- "runAsUser" , default_user_id
183
+ "runAsUser" , _DEFAULT_USER_ID
174
184
)
175
185
sc_run_as_group = material .get ("securityContext" , {}).get (
176
- "runAsGroup" , default_group_id
186
+ "runAsGroup" , _DEFAULT_GROUP_ID
177
187
)
178
188
179
189
# Project storage
@@ -309,7 +319,7 @@ def create(spec: Dict[str, Any], name: str, namespace: str, **_: Any) -> Dict[st
309
319
logging .info ("Creating Ingress %s..." , name )
310
320
311
321
ingress_proxy_body_size = material .get (
312
- "ingressProxyBodySize" , default_ingress_proxy_body_size
322
+ "ingressProxyBodySize" , _DEFAULT_INGRESS_PROXY_BODY_SIZE
313
323
)
314
324
315
325
ingress_path = f"/{ name } "
@@ -322,7 +332,7 @@ def create(spec: Dict[str, Any], name: str, namespace: str, **_: Any) -> Dict[st
322
332
"name" : name ,
323
333
"labels" : {"app" : name },
324
334
"annotations" : {
325
- "kubernetes.io/ingress.class" : ingress_class ,
335
+ "kubernetes.io/ingress.class" : _INGRESS_CLASS ,
326
336
"nginx.ingress.kubernetes.io/proxy-body-size" : f"{ ingress_proxy_body_size } " ,
327
337
},
328
338
},
0 commit comments