We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
allow setting service account from config.ayml
We currently disable [service accounts](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) for all user pods, disallowing kubernetes API access. This is the correct default for us, but we should allow users to easily override it if they need.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `images/hub/jupyterhub_config.py`
Content:
```
1 import os
2 import sys
3 import yaml
4 from tornado.httpclient import AsyncHTTPClient
5 
6 def get_config(key, default=None):
7     """
8     Find a config item of a given name & return it
9 
10     Parses everything as YAML, so lists and dicts are available too
11     """
12     path = os.path.join('/etc/jupyterhub/config', key)
13     try:
14         with open(path) as f:
15             data = yaml.safe_load(f)
16             return data
17     except FileNotFoundError:
18         return default
19 
20 def get_secret(key, default=None):
21     """Get a secret from /etc/jupyterhub/secret"""
22     path = os.path.join('/etc/jupyterhub/secret', key)
23     try:
24         with open(path) as f:
25             return f.read().strip()
26     except FileNotFoundError:
27         return default
28 
29 
30 # Configure JupyterHub to use the curl backend for making HTTP requests,
31 # rather than the pure-python implementations. The default one starts
32 # being too slow to make a large number of requests to the proxy API
33 # at the rate required.
34 AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient")
35 
36 c.JupyterHub.spawner_class = 'kubespawner.KubeSpawner'
37 
38 # Connect to a proxy running in a different pod
39 c.ConfigurableHTTPProxy.api_url = 'http://{}:{}'.format(os.environ['PROXY_API_SERVICE_HOST'], int(os.environ['PROXY_API_SERVICE_PORT']))
40 c.ConfigurableHTTPProxy.should_start = False
41 
42 # Do not shut down user pods when hub is restarted
43 c.JupyterHub.cleanup_servers = False
44 
45 # Check that the proxy has routes appropriately setup
46 # This isn't the best named setting :D
47 c.JupyterHub.last_activity_interval = 60
48 
49 # Max number of servers that can be spawning at any one time
50 c.JupyterHub.concurrent_spawn_limit = get_config('hub.concurrent-spawn-limit')
51 
52 active_server_limit = get_config('hub.active-server-limit', None)
53 
54 if active_server_limit is not None:
55     c.JupyterHub.active_server_limit = int(active_server_limit)
56 
57 c.JupyterHub.ip = os.environ['PROXY_PUBLIC_SERVICE_HOST']
58 c.JupyterHub.port = int(os.environ['PROXY_PUBLIC_SERVICE_PORT'])
59 
60 # the hub should listen on all interfaces, so the proxy can access it
61 c.JupyterHub.hub_ip = '0.0.0.0'
62 
63 c.KubeSpawner.namespace = os.environ.get('POD_NAMESPACE', 'default')
64 
65 c.KubeSpawner.start_timeout = get_config('singleuser.start-timeout')
66 
67 # Use env var for this, since we want hub to restart when this changes
68 c.KubeSpawner.singleuser_image_spec = os.environ['SINGLEUSER_IMAGE']
69 
70 c.KubeSpawner.singleuser_extra_labels = get_config('singleuser.extra-labels', {})
71 
72 c.KubeSpawner.singleuser_uid = get_config('singleuser.uid')
73 c.KubeSpawner.singleuser_fs_gid = get_config('singleuser.fs-gid')
74 
75 c.KubeSpawner.singleuser_node_selector = get_config('singleuser.node-selector')
76 # Configure dynamically provisioning pvc
77 storage_type = get_config('singleuser.storage.type')
78 if storage_type == 'dynamic':
79     c.KubeSpawner.pvc_name_template = 'claim-{username}{servername}'
80     c.KubeSpawner.user_storage_pvc_ensure = True
81     storage_class = get_config('singleuser.storage.dynamic.storage-class', None)
82     if storage_class:
83         c.KubeSpawner.user_storage_class = storage_class
84     c.KubeSpawner.user_storage_access_modes = ['ReadWriteOnce']
85     c.KubeSpawner.user_storage_capacity = get_config('singleuser.storage.capacity')
86 
87     # Add volumes to singleuser pods
88     c.KubeSpawner.volumes = [
89         {
90             'name': 'volume-{username}{servername}',
91             'persistentVolumeClaim': {
92                 'claimName': 'claim-{username}{servername}'
93             }
94         }
95     ]
96     c.KubeSpawner.volume_mounts = [
97         {
98             'mountPath': get_config('singleuser.storage.home_mount_path'),
99             'name': 'volume-{username}{servername}'
100         }
101     ]
102 elif storage_type == 'static':
103     pvc_claim_name = get_config('singleuser.storage.static.pvc-name')
104     c.KubeSpawner.volumes = [{
105         'name': 'home',
106         'persistentVolumeClaim': {
107             'claimName': pvc_claim_name
108         }
109     }]
110 
111     c.KubeSpawner.volume_mounts = [{
112         'mountPath': get_config('singleuser.storage.home_mount_path'),
113         'name': 'home',
114         'subPath': get_config('singleuser.storage.static.sub-path')
115     }]
116 
117 c.KubeSpawner.volumes.extend(get_config('singleuser.storage.extra-volumes', []))
118 c.KubeSpawner.volume_mounts.extend(get_config('singleuser.storage.extra-volume-mounts', []))
119 
120 lifecycle_hooks = get_config('singleuser.lifecycle-hooks')
121 if lifecycle_hooks:
122     c.KubeSpawner.singleuser_lifecycle_hooks = lifecycle_hooks
123 
124 init_containers = get_config('singleuser.init-containers')
125 if init_containers:
126     c.KubeSpawner.singleuser_init_containers = init_containers
127 
128 # Gives spawned containers access to the API of the hub
129 c.KubeSpawner.hub_connect_ip = os.environ['HUB_SERVICE_HOST']
130 c.KubeSpawner.hub_connect_port = int(os.environ['HUB_SERVICE_PORT'])
131 
132 c.JupyterHub.hub_connect_ip = os.environ['HUB_SERVICE_HOST']
133 c.JupyterHub.hub_connect_port = int(os.environ['HUB_SERVICE_PORT'])
134 
135 c.KubeSpawner.mem_limit = get_config('singleuser.memory.limit')
136 c.KubeSpawner.mem_guarantee = get_config('singleuser.memory.guarantee')
137 c.KubeSpawner.cpu_limit = get_config('singleuser.cpu.limit')
138 c.KubeSpawner.cpu_guarantee = get_config('singleuser.cpu.guarantee')
139 
140 # Allow switching authenticators easily
141 auth_type = get_config('auth.type')
142 email_domain = 'local'
143 
144 if auth_type == 'google':
145     c.JupyterHub.authenticator_class = 'oauthenticator.GoogleOAuthenticator'
146     c.GoogleOAuthenticator.client_id = get_config('auth.google.client-id')
147     c.GoogleOAuthenticator.client_secret = get_config('auth.google.client-secret')
148     c.GoogleOAuthenticator.oauth_callback_url = get_config('auth.google.callback-url')
149     c.GoogleOAuthenticator.hosted_domain = get_config('auth.google.hosted-domain')
150     c.GoogleOAuthenticator.login_service = get_config('auth.google.login-service')
151     email_domain = get_config('auth.google.hosted-domain')
152 elif auth_type == 'github':
153     c.JupyterHub.authenticator_class = 'oauthenticator.GitHubOAuthenticator'
154     c.GitHubOAuthenticator.oauth_callback_url = get_config('auth.github.callback-url')
155     c.GitHubOAuthenticator.client_id = get_config('auth.github.client-id')
156     c.GitHubOAuthenticator.client_secret = get_config('auth.github.client-secret')
157 elif auth_type == 'cilogon':
158     c.JupyterHub.authenticator_class = 'oauthenticator.CILogonOAuthenticator'
159     c.CILogonOAuthenticator.oauth_callback_url = get_config('auth.cilogon.callback-url')
160     c.CILogonOAuthenticator.client_id = get_config('auth.cilogon.client-id')
161     c.CILogonOAuthenticator.client_secret = get_config('auth.cilogon.client-secret')
162 elif auth_type == 'gitlab':
163     c.JupyterHub.authenticator_class = 'oauthenticator.gitlab.GitLabOAuthenticator'
164     c.GitLabOAuthenticator.oauth_callback_url = get_config('auth.gitlab.callback-url')
165     c.GitLabOAuthenticator.client_id = get_config('auth.gitlab.client-id')
166     c.GitLabOAuthenticator.client_secret = get_config('auth.gitlab.client-secret')
167 elif auth_type == 'mediawiki':
168     c.JupyterHub.authenticator_class = 'oauthenticator.mediawiki.MWOAuthenticator'
169     c.MWOAuthenticator.client_id = get_config('auth.mediawiki.client-id')
170     c.MWOAuthenticator.client_secret = get_config('auth.mediawiki.client-secret')
171     c.MWOAuthenticator.index_url = get_config('auth.mediawiki.index-url')
172 elif auth_type == 'globus':
173     c.JupyterHub.authenticator_class = 'oauthenticator.globus.GlobusOAuthenticator'
174     c.GlobusOAuthenticator.oauth_callback_url = get_config('auth.globus.callback-url')
175     c.GlobusOAuthenticator.client_id = get_config('auth.globus.client-id')
176     c.GlobusOAuthenticator.client_secret = get_config('auth.globus.client-secret')
177     c.GlobusOAuthenticator.identity_provider = get_config('auth.globus.identity-provider', '')
178 elif auth_type == 'hmac':
179     c.JupyterHub.authenticator_class = 'hmacauthenticator.HMACAuthenticator'
180     c.HMACAuthenticator.secret_key = bytes.fromhex(get_config('auth.hmac.secret-key'))
181 elif auth_type == 'dummy':
182     c.JupyterHub.authenticator_class = 'dummyauthenticator.DummyAuthenticator'
183     c.DummyAuthenticator.password = get_config('auth.dummy.password', None)
184 elif auth_type == 'tmp':
185     c.JupyterHub.authenticator_class = 'tmpauthenticator.TmpAuthenticator'
186 elif auth_type == 'lti':
187     c.JupyterHub.authenticator_class = 'ltiauthenticator.LTIAuthenticator'
188     c.LTIAuthenticator.consumers = get_config('auth.lti.consumers')
189 elif auth_type == 'custom':
190     # full_class_name looks like "myauthenticator.MyAuthenticator".
191     # To create a docker image with this class availabe, you can just have the
192     # following Dockerifle:
193     #   FROM jupyterhub/k8s-hub:v0.4
194     #   RUN pip3 install myauthenticator
195     full_class_name = get_config('auth.custom.class-name')
196     c.JupyterHub.authenticator_class = full_class_name
197     auth_class_name = full_class_name.rsplit('.', 1)[-1]
198     auth_config = c[auth_class_name]
199     auth_config.update(get_config('auth.custom.config') or {})
200 else:
201     raise ValueError("Unhandled auth type: %r" % auth_type)
202 
203 c.Authenticator.enable_auth_state = get_config('auth.state.enabled', False)
204 
205 def generate_user_email(spawner):
206     """
207     Used as the EMAIL environment variable
208     """
209     return '{username}@{domain}'.format(
210         username=spawner.user.name, domain=email_domain
211     )
212 
213 def generate_user_name(spawner):
214     """
215     Used as GIT_AUTHOR_NAME and GIT_COMMITTER_NAME environment variables
216     """
217     return spawner.user.name
218 
219 c.KubeSpawner.environment = {
220     'EMAIL': generate_user_email,
221     # git requires these committer attributes
222     'GIT_AUTHOR_NAME': generate_user_name,
223     'GIT_COMMITTER_NAME': generate_user_name
224 }
225 
226 c.KubeSpawner.environment.update(get_config('singleuser.extra-env', {}))
227 
228 # Enable admins to access user servers
229 c.JupyterHub.admin_access = get_config('auth.admin.access')
230 c.Authenticator.admin_users = get_config('auth.admin.users', [])
231 c.Authenticator.whitelist = get_config('auth.whitelist.users', [])
232 
233 c.JupyterHub.base_url = get_config('hub.base_url')
234 
235 c.JupyterHub.services = []
236 
237 if get_config('cull.enabled', False):
238     cull_timeout = get_config('cull.timeout')
239     cull_every = get_config('cull.every')
240     cull_cmd = [
241         '/usr/local/bin/cull_idle_servers.py',
242         '--timeout=%s' % cull_timeout,
243         '--cull-every=%s' % cull_every,
244         '--url=http://127.0.0.1:8081' + c.JupyterHub.base_url + 'hub/api'
245     ]
246     if get_config('cull.users'):
247         cull_cmd.append('--cull-users')
248     c.JupyterHub.services.append({
249         'name': 'cull-idle',
250         'admin': True,
251         'command': cull_cmd,
252     })
253 
254 for name, service in get_config('hub.services', {}).items():
255     api_token = get_secret('services.token.%s' % name)
256     # jupyterhub.services is a list of dicts, but
257     # in the helm chart it is a dict of dicts for easier merged-config
258     service.setdefault('name', name)
259     if api_token:
260         service['api_token'] = api_token
261     c.JupyterHub.services.append(service)
262 
263 
264 c.JupyterHub.db_url = get_config('hub.db_url')
265 
266 cmd = get_config('singleuser.cmd', None)
267 if cmd:
268     c.Spawner.cmd = cmd
269 
270 
271 scheduler_strategy = get_config('singleuser.scheduler-strategy', 'spread')
272 
273 if scheduler_strategy == 'pack':
274     # FIXME: Support setting affinity directly in KubeSpawner
275     c.KubeSpawner.singleuser_extra_pod_config = {
276         'affinity': {
277             'podAffinity': {
278                 'preferredDuringSchedulingIgnoredDuringExecution': [{
279                     'weight': 100,
280                     'podAffinityTerm': {
281                         'labelSelector': {
282                             'matchExpressions': [{
283                                 'key': 'component',
284                                 'operator': 'In',
285                                 'values': ['singleuser-server']
286                             }]
287                         },
288                         'topologyKey': 'kubernetes.io/hostname'
289                     }
290                 }],
291             }
292         }
293     }
294 else:
295     # Set default to {} so subconfigs can easily update it
296     c.KubeSpawner.singleuser_extra_pod_config = {}
297 
298 extra_config_path = '/etc/jupyterhub/config/hub.extra-config.py'
299 if os.path.exists(extra_config_path):
300     load_subconfig(extra_config_path)
301
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/images/hub/jupyterhub_config.py b/images/hub/jupyterhub_config.py
--- a/images/hub/jupyterhub_config.py
+++ b/images/hub/jupyterhub_config.py
@@ -72,6 +72,10 @@
 c.KubeSpawner.singleuser_uid = get_config('singleuser.uid')
 c.KubeSpawner.singleuser_fs_gid = get_config('singleuser.fs-gid')
 
+service_account_name = get_config('singleuser.service-account-name', None)
+if service_account_name:
+    c.KubeSpawner.singleuser_service_account = service_account_name
+
 c.KubeSpawner.singleuser_node_selector = get_config('singleuser.node-selector')
 # Configure dynamically provisioning pvc
 storage_type = get_config('singleuser.storage.type')

{"golden_diff": "diff --git a/images/hub/jupyterhub_config.py b/images/hub/jupyterhub_config.py\n--- a/images/hub/jupyterhub_config.py\n+++ b/images/hub/jupyterhub_config.py\n@@ -72,6 +72,10 @@\n c.KubeSpawner.singleuser_uid = get_config('singleuser.uid')\n c.KubeSpawner.singleuser_fs_gid = get_config('singleuser.fs-gid')\n \n+service_account_name = get_config('singleuser.service-account-name', None)\n+if service_account_name:\n+    c.KubeSpawner.singleuser_service_account = service_account_name\n+\n c.KubeSpawner.singleuser_node_selector = get_config('singleuser.node-selector')\n # Configure dynamically provisioning pvc\n storage_type = get_config('singleuser.storage.type')\n", "issue": "allow setting service account from config.ayml\nWe currently disable [service accounts](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) for all user pods, disallowing kubernetes API access. This is the correct default for us, but we should allow users to easily override it if they need.\n", "before_files": [{"content": "import os\nimport sys\nimport yaml\nfrom tornado.httpclient import AsyncHTTPClient\n\ndef get_config(key, default=None):\n    \"\"\"\n    Find a config item of a given name & return it\n\n    Parses everything as YAML, so lists and dicts are available too\n    \"\"\"\n    path = os.path.join('/etc/jupyterhub/config', key)\n    try:\n        with open(path) as f:\n            data = yaml.safe_load(f)\n            return data\n    except FileNotFoundError:\n        return default\n\ndef get_secret(key, default=None):\n    \"\"\"Get a secret from /etc/jupyterhub/secret\"\"\"\n    path = os.path.join('/etc/jupyterhub/secret', key)\n    try:\n        with open(path) as f:\n            return f.read().strip()\n    except FileNotFoundError:\n        return default\n\n\n# Configure JupyterHub to use the curl backend for making HTTP requests,\n# rather than the pure-python implementations. The default one starts\n# being too slow to make a large number of requests to the proxy API\n# at the rate required.\nAsyncHTTPClient.configure(\"tornado.curl_httpclient.CurlAsyncHTTPClient\")\n\nc.JupyterHub.spawner_class = 'kubespawner.KubeSpawner'\n\n# Connect to a proxy running in a different pod\nc.ConfigurableHTTPProxy.api_url = 'http://{}:{}'.format(os.environ['PROXY_API_SERVICE_HOST'], int(os.environ['PROXY_API_SERVICE_PORT']))\nc.ConfigurableHTTPProxy.should_start = False\n\n# Do not shut down user pods when hub is restarted\nc.JupyterHub.cleanup_servers = False\n\n# Check that the proxy has routes appropriately setup\n# This isn't the best named setting :D\nc.JupyterHub.last_activity_interval = 60\n\n# Max number of servers that can be spawning at any one time\nc.JupyterHub.concurrent_spawn_limit = get_config('hub.concurrent-spawn-limit')\n\nactive_server_limit = get_config('hub.active-server-limit', None)\n\nif active_server_limit is not None:\n    c.JupyterHub.active_server_limit = int(active_server_limit)\n\nc.JupyterHub.ip = os.environ['PROXY_PUBLIC_SERVICE_HOST']\nc.JupyterHub.port = int(os.environ['PROXY_PUBLIC_SERVICE_PORT'])\n\n# the hub should listen on all interfaces, so the proxy can access it\nc.JupyterHub.hub_ip = '0.0.0.0'\n\nc.KubeSpawner.namespace = os.environ.get('POD_NAMESPACE', 'default')\n\nc.KubeSpawner.start_timeout = get_config('singleuser.start-timeout')\n\n# Use env var for this, since we want hub to restart when this changes\nc.KubeSpawner.singleuser_image_spec = os.environ['SINGLEUSER_IMAGE']\n\nc.KubeSpawner.singleuser_extra_labels = get_config('singleuser.extra-labels', {})\n\nc.KubeSpawner.singleuser_uid = get_config('singleuser.uid')\nc.KubeSpawner.singleuser_fs_gid = get_config('singleuser.fs-gid')\n\nc.KubeSpawner.singleuser_node_selector = get_config('singleuser.node-selector')\n# Configure dynamically provisioning pvc\nstorage_type = get_config('singleuser.storage.type')\nif storage_type == 'dynamic':\n    c.KubeSpawner.pvc_name_template = 'claim-{username}{servername}'\n    c.KubeSpawner.user_storage_pvc_ensure = True\n    storage_class = get_config('singleuser.storage.dynamic.storage-class', None)\n    if storage_class:\n        c.KubeSpawner.user_storage_class = storage_class\n    c.KubeSpawner.user_storage_access_modes = ['ReadWriteOnce']\n    c.KubeSpawner.user_storage_capacity = get_config('singleuser.storage.capacity')\n\n    # Add volumes to singleuser pods\n    c.KubeSpawner.volumes = [\n        {\n            'name': 'volume-{username}{servername}',\n            'persistentVolumeClaim': {\n                'claimName': 'claim-{username}{servername}'\n            }\n        }\n    ]\n    c.KubeSpawner.volume_mounts = [\n        {\n            'mountPath': get_config('singleuser.storage.home_mount_path'),\n            'name': 'volume-{username}{servername}'\n        }\n    ]\nelif storage_type == 'static':\n    pvc_claim_name = get_config('singleuser.storage.static.pvc-name')\n    c.KubeSpawner.volumes = [{\n        'name': 'home',\n        'persistentVolumeClaim': {\n            'claimName': pvc_claim_name\n        }\n    }]\n\n    c.KubeSpawner.volume_mounts = [{\n        'mountPath': get_config('singleuser.storage.home_mount_path'),\n        'name': 'home',\n        'subPath': get_config('singleuser.storage.static.sub-path')\n    }]\n\nc.KubeSpawner.volumes.extend(get_config('singleuser.storage.extra-volumes', []))\nc.KubeSpawner.volume_mounts.extend(get_config('singleuser.storage.extra-volume-mounts', []))\n\nlifecycle_hooks = get_config('singleuser.lifecycle-hooks')\nif lifecycle_hooks:\n    c.KubeSpawner.singleuser_lifecycle_hooks = lifecycle_hooks\n\ninit_containers = get_config('singleuser.init-containers')\nif init_containers:\n    c.KubeSpawner.singleuser_init_containers = init_containers\n\n# Gives spawned containers access to the API of the hub\nc.KubeSpawner.hub_connect_ip = os.environ['HUB_SERVICE_HOST']\nc.KubeSpawner.hub_connect_port = int(os.environ['HUB_SERVICE_PORT'])\n\nc.JupyterHub.hub_connect_ip = os.environ['HUB_SERVICE_HOST']\nc.JupyterHub.hub_connect_port = int(os.environ['HUB_SERVICE_PORT'])\n\nc.KubeSpawner.mem_limit = get_config('singleuser.memory.limit')\nc.KubeSpawner.mem_guarantee = get_config('singleuser.memory.guarantee')\nc.KubeSpawner.cpu_limit = get_config('singleuser.cpu.limit')\nc.KubeSpawner.cpu_guarantee = get_config('singleuser.cpu.guarantee')\n\n# Allow switching authenticators easily\nauth_type = get_config('auth.type')\nemail_domain = 'local'\n\nif auth_type == 'google':\n    c.JupyterHub.authenticator_class = 'oauthenticator.GoogleOAuthenticator'\n    c.GoogleOAuthenticator.client_id = get_config('auth.google.client-id')\n    c.GoogleOAuthenticator.client_secret = get_config('auth.google.client-secret')\n    c.GoogleOAuthenticator.oauth_callback_url = get_config('auth.google.callback-url')\n    c.GoogleOAuthenticator.hosted_domain = get_config('auth.google.hosted-domain')\n    c.GoogleOAuthenticator.login_service = get_config('auth.google.login-service')\n    email_domain = get_config('auth.google.hosted-domain')\nelif auth_type == 'github':\n    c.JupyterHub.authenticator_class = 'oauthenticator.GitHubOAuthenticator'\n    c.GitHubOAuthenticator.oauth_callback_url = get_config('auth.github.callback-url')\n    c.GitHubOAuthenticator.client_id = get_config('auth.github.client-id')\n    c.GitHubOAuthenticator.client_secret = get_config('auth.github.client-secret')\nelif auth_type == 'cilogon':\n    c.JupyterHub.authenticator_class = 'oauthenticator.CILogonOAuthenticator'\n    c.CILogonOAuthenticator.oauth_callback_url = get_config('auth.cilogon.callback-url')\n    c.CILogonOAuthenticator.client_id = get_config('auth.cilogon.client-id')\n    c.CILogonOAuthenticator.client_secret = get_config('auth.cilogon.client-secret')\nelif auth_type == 'gitlab':\n    c.JupyterHub.authenticator_class = 'oauthenticator.gitlab.GitLabOAuthenticator'\n    c.GitLabOAuthenticator.oauth_callback_url = get_config('auth.gitlab.callback-url')\n    c.GitLabOAuthenticator.client_id = get_config('auth.gitlab.client-id')\n    c.GitLabOAuthenticator.client_secret = get_config('auth.gitlab.client-secret')\nelif auth_type == 'mediawiki':\n    c.JupyterHub.authenticator_class = 'oauthenticator.mediawiki.MWOAuthenticator'\n    c.MWOAuthenticator.client_id = get_config('auth.mediawiki.client-id')\n    c.MWOAuthenticator.client_secret = get_config('auth.mediawiki.client-secret')\n    c.MWOAuthenticator.index_url = get_config('auth.mediawiki.index-url')\nelif auth_type == 'globus':\n    c.JupyterHub.authenticator_class = 'oauthenticator.globus.GlobusOAuthenticator'\n    c.GlobusOAuthenticator.oauth_callback_url = get_config('auth.globus.callback-url')\n    c.GlobusOAuthenticator.client_id = get_config('auth.globus.client-id')\n    c.GlobusOAuthenticator.client_secret = get_config('auth.globus.client-secret')\n    c.GlobusOAuthenticator.identity_provider = get_config('auth.globus.identity-provider', '')\nelif auth_type == 'hmac':\n    c.JupyterHub.authenticator_class = 'hmacauthenticator.HMACAuthenticator'\n    c.HMACAuthenticator.secret_key = bytes.fromhex(get_config('auth.hmac.secret-key'))\nelif auth_type == 'dummy':\n    c.JupyterHub.authenticator_class = 'dummyauthenticator.DummyAuthenticator'\n    c.DummyAuthenticator.password = get_config('auth.dummy.password', None)\nelif auth_type == 'tmp':\n    c.JupyterHub.authenticator_class = 'tmpauthenticator.TmpAuthenticator'\nelif auth_type == 'lti':\n    c.JupyterHub.authenticator_class = 'ltiauthenticator.LTIAuthenticator'\n    c.LTIAuthenticator.consumers = get_config('auth.lti.consumers')\nelif auth_type == 'custom':\n    # full_class_name looks like \"myauthenticator.MyAuthenticator\".\n    # To create a docker image with this class availabe, you can just have the\n    # following Dockerifle:\n    #   FROM jupyterhub/k8s-hub:v0.4\n    #   RUN pip3 install myauthenticator\n    full_class_name = get_config('auth.custom.class-name')\n    c.JupyterHub.authenticator_class = full_class_name\n    auth_class_name = full_class_name.rsplit('.', 1)[-1]\n    auth_config = c[auth_class_name]\n    auth_config.update(get_config('auth.custom.config') or {})\nelse:\n    raise ValueError(\"Unhandled auth type: %r\" % auth_type)\n\nc.Authenticator.enable_auth_state = get_config('auth.state.enabled', False)\n\ndef generate_user_email(spawner):\n    \"\"\"\n    Used as the EMAIL environment variable\n    \"\"\"\n    return '{username}@{domain}'.format(\n        username=spawner.user.name, domain=email_domain\n    )\n\ndef generate_user_name(spawner):\n    \"\"\"\n    Used as GIT_AUTHOR_NAME and GIT_COMMITTER_NAME environment variables\n    \"\"\"\n    return spawner.user.name\n\nc.KubeSpawner.environment = {\n    'EMAIL': generate_user_email,\n    # git requires these committer attributes\n    'GIT_AUTHOR_NAME': generate_user_name,\n    'GIT_COMMITTER_NAME': generate_user_name\n}\n\nc.KubeSpawner.environment.update(get_config('singleuser.extra-env', {}))\n\n# Enable admins to access user servers\nc.JupyterHub.admin_access = get_config('auth.admin.access')\nc.Authenticator.admin_users = get_config('auth.admin.users', [])\nc.Authenticator.whitelist = get_config('auth.whitelist.users', [])\n\nc.JupyterHub.base_url = get_config('hub.base_url')\n\nc.JupyterHub.services = []\n\nif get_config('cull.enabled', False):\n    cull_timeout = get_config('cull.timeout')\n    cull_every = get_config('cull.every')\n    cull_cmd = [\n        '/usr/local/bin/cull_idle_servers.py',\n        '--timeout=%s' % cull_timeout,\n        '--cull-every=%s' % cull_every,\n        '--url=http://127.0.0.1:8081' + c.JupyterHub.base_url + 'hub/api'\n    ]\n    if get_config('cull.users'):\n        cull_cmd.append('--cull-users')\n    c.JupyterHub.services.append({\n        'name': 'cull-idle',\n        'admin': True,\n        'command': cull_cmd,\n    })\n\nfor name, service in get_config('hub.services', {}).items():\n    api_token = get_secret('services.token.%s' % name)\n    # jupyterhub.services is a list of dicts, but\n    # in the helm chart it is a dict of dicts for easier merged-config\n    service.setdefault('name', name)\n    if api_token:\n        service['api_token'] = api_token\n    c.JupyterHub.services.append(service)\n\n\nc.JupyterHub.db_url = get_config('hub.db_url')\n\ncmd = get_config('singleuser.cmd', None)\nif cmd:\n    c.Spawner.cmd = cmd\n\n\nscheduler_strategy = get_config('singleuser.scheduler-strategy', 'spread')\n\nif scheduler_strategy == 'pack':\n    # FIXME: Support setting affinity directly in KubeSpawner\n    c.KubeSpawner.singleuser_extra_pod_config = {\n        'affinity': {\n            'podAffinity': {\n                'preferredDuringSchedulingIgnoredDuringExecution': [{\n                    'weight': 100,\n                    'podAffinityTerm': {\n                        'labelSelector': {\n                            'matchExpressions': [{\n                                'key': 'component',\n                                'operator': 'In',\n                                'values': ['singleuser-server']\n                            }]\n                        },\n                        'topologyKey': 'kubernetes.io/hostname'\n                    }\n                }],\n            }\n        }\n    }\nelse:\n    # Set default to {} so subconfigs can easily update it\n    c.KubeSpawner.singleuser_extra_pod_config = {}\n\nextra_config_path = '/etc/jupyterhub/config/hub.extra-config.py'\nif os.path.exists(extra_config_path):\n    load_subconfig(extra_config_path)\n", "path": "images/hub/jupyterhub_config.py"}], "after_files": [{"content": "import os\nimport sys\nimport yaml\nfrom tornado.httpclient import AsyncHTTPClient\n\ndef get_config(key, default=None):\n    \"\"\"\n    Find a config item of a given name & return it\n\n    Parses everything as YAML, so lists and dicts are available too\n    \"\"\"\n    path = os.path.join('/etc/jupyterhub/config', key)\n    try:\n        with open(path) as f:\n            data = yaml.safe_load(f)\n            return data\n    except FileNotFoundError:\n        return default\n\ndef get_secret(key, default=None):\n    \"\"\"Get a secret from /etc/jupyterhub/secret\"\"\"\n    path = os.path.join('/etc/jupyterhub/secret', key)\n    try:\n        with open(path) as f:\n            return f.read().strip()\n    except FileNotFoundError:\n        return default\n\n\n# Configure JupyterHub to use the curl backend for making HTTP requests,\n# rather than the pure-python implementations. The default one starts\n# being too slow to make a large number of requests to the proxy API\n# at the rate required.\nAsyncHTTPClient.configure(\"tornado.curl_httpclient.CurlAsyncHTTPClient\")\n\nc.JupyterHub.spawner_class = 'kubespawner.KubeSpawner'\n\n# Connect to a proxy running in a different pod\nc.ConfigurableHTTPProxy.api_url = 'http://{}:{}'.format(os.environ['PROXY_API_SERVICE_HOST'], int(os.environ['PROXY_API_SERVICE_PORT']))\nc.ConfigurableHTTPProxy.should_start = False\n\n# Do not shut down user pods when hub is restarted\nc.JupyterHub.cleanup_servers = False\n\n# Check that the proxy has routes appropriately setup\n# This isn't the best named setting :D\nc.JupyterHub.last_activity_interval = 60\n\n# Max number of servers that can be spawning at any one time\nc.JupyterHub.concurrent_spawn_limit = get_config('hub.concurrent-spawn-limit')\n\nactive_server_limit = get_config('hub.active-server-limit', None)\n\nif active_server_limit is not None:\n    c.JupyterHub.active_server_limit = int(active_server_limit)\n\nc.JupyterHub.ip = os.environ['PROXY_PUBLIC_SERVICE_HOST']\nc.JupyterHub.port = int(os.environ['PROXY_PUBLIC_SERVICE_PORT'])\n\n# the hub should listen on all interfaces, so the proxy can access it\nc.JupyterHub.hub_ip = '0.0.0.0'\n\nc.KubeSpawner.namespace = os.environ.get('POD_NAMESPACE', 'default')\n\nc.KubeSpawner.start_timeout = get_config('singleuser.start-timeout')\n\n# Use env var for this, since we want hub to restart when this changes\nc.KubeSpawner.singleuser_image_spec = os.environ['SINGLEUSER_IMAGE']\n\nc.KubeSpawner.singleuser_extra_labels = get_config('singleuser.extra-labels', {})\n\nc.KubeSpawner.singleuser_uid = get_config('singleuser.uid')\nc.KubeSpawner.singleuser_fs_gid = get_config('singleuser.fs-gid')\n\nservice_account_name = get_config('singleuser.service-account-name', None)\nif service_account_name:\n    c.KubeSpawner.singleuser_service_account = service_account_name\n\nc.KubeSpawner.singleuser_node_selector = get_config('singleuser.node-selector')\n# Configure dynamically provisioning pvc\nstorage_type = get_config('singleuser.storage.type')\nif storage_type == 'dynamic':\n    c.KubeSpawner.pvc_name_template = 'claim-{username}{servername}'\n    c.KubeSpawner.user_storage_pvc_ensure = True\n    storage_class = get_config('singleuser.storage.dynamic.storage-class', None)\n    if storage_class:\n        c.KubeSpawner.user_storage_class = storage_class\n    c.KubeSpawner.user_storage_access_modes = ['ReadWriteOnce']\n    c.KubeSpawner.user_storage_capacity = get_config('singleuser.storage.capacity')\n\n    # Add volumes to singleuser pods\n    c.KubeSpawner.volumes = [\n        {\n            'name': 'volume-{username}{servername}',\n            'persistentVolumeClaim': {\n                'claimName': 'claim-{username}{servername}'\n            }\n        }\n    ]\n    c.KubeSpawner.volume_mounts = [\n        {\n            'mountPath': get_config('singleuser.storage.home_mount_path'),\n            'name': 'volume-{username}{servername}'\n        }\n    ]\nelif storage_type == 'static':\n    pvc_claim_name = get_config('singleuser.storage.static.pvc-name')\n    c.KubeSpawner.volumes = [{\n        'name': 'home',\n        'persistentVolumeClaim': {\n            'claimName': pvc_claim_name\n        }\n    }]\n\n    c.KubeSpawner.volume_mounts = [{\n        'mountPath': get_config('singleuser.storage.home_mount_path'),\n        'name': 'home',\n        'subPath': get_config('singleuser.storage.static.sub-path')\n    }]\n\nc.KubeSpawner.volumes.extend(get_config('singleuser.storage.extra-volumes', []))\nc.KubeSpawner.volume_mounts.extend(get_config('singleuser.storage.extra-volume-mounts', []))\n\nlifecycle_hooks = get_config('singleuser.lifecycle-hooks')\nif lifecycle_hooks:\n    c.KubeSpawner.singleuser_lifecycle_hooks = lifecycle_hooks\n\ninit_containers = get_config('singleuser.init-containers')\nif init_containers:\n    c.KubeSpawner.singleuser_init_containers = init_containers\n\n# Gives spawned containers access to the API of the hub\nc.KubeSpawner.hub_connect_ip = os.environ['HUB_SERVICE_HOST']\nc.KubeSpawner.hub_connect_port = int(os.environ['HUB_SERVICE_PORT'])\n\nc.JupyterHub.hub_connect_ip = os.environ['HUB_SERVICE_HOST']\nc.JupyterHub.hub_connect_port = int(os.environ['HUB_SERVICE_PORT'])\n\nc.KubeSpawner.mem_limit = get_config('singleuser.memory.limit')\nc.KubeSpawner.mem_guarantee = get_config('singleuser.memory.guarantee')\nc.KubeSpawner.cpu_limit = get_config('singleuser.cpu.limit')\nc.KubeSpawner.cpu_guarantee = get_config('singleuser.cpu.guarantee')\n\n# Allow switching authenticators easily\nauth_type = get_config('auth.type')\nemail_domain = 'local'\n\nif auth_type == 'google':\n    c.JupyterHub.authenticator_class = 'oauthenticator.GoogleOAuthenticator'\n    c.GoogleOAuthenticator.client_id = get_config('auth.google.client-id')\n    c.GoogleOAuthenticator.client_secret = get_config('auth.google.client-secret')\n    c.GoogleOAuthenticator.oauth_callback_url = get_config('auth.google.callback-url')\n    c.GoogleOAuthenticator.hosted_domain = get_config('auth.google.hosted-domain')\n    c.GoogleOAuthenticator.login_service = get_config('auth.google.login-service')\n    email_domain = get_config('auth.google.hosted-domain')\nelif auth_type == 'github':\n    c.JupyterHub.authenticator_class = 'oauthenticator.GitHubOAuthenticator'\n    c.GitHubOAuthenticator.oauth_callback_url = get_config('auth.github.callback-url')\n    c.GitHubOAuthenticator.client_id = get_config('auth.github.client-id')\n    c.GitHubOAuthenticator.client_secret = get_config('auth.github.client-secret')\nelif auth_type == 'cilogon':\n    c.JupyterHub.authenticator_class = 'oauthenticator.CILogonOAuthenticator'\n    c.CILogonOAuthenticator.oauth_callback_url = get_config('auth.cilogon.callback-url')\n    c.CILogonOAuthenticator.client_id = get_config('auth.cilogon.client-id')\n    c.CILogonOAuthenticator.client_secret = get_config('auth.cilogon.client-secret')\nelif auth_type == 'gitlab':\n    c.JupyterHub.authenticator_class = 'oauthenticator.gitlab.GitLabOAuthenticator'\n    c.GitLabOAuthenticator.oauth_callback_url = get_config('auth.gitlab.callback-url')\n    c.GitLabOAuthenticator.client_id = get_config('auth.gitlab.client-id')\n    c.GitLabOAuthenticator.client_secret = get_config('auth.gitlab.client-secret')\nelif auth_type == 'mediawiki':\n    c.JupyterHub.authenticator_class = 'oauthenticator.mediawiki.MWOAuthenticator'\n    c.MWOAuthenticator.client_id = get_config('auth.mediawiki.client-id')\n    c.MWOAuthenticator.client_secret = get_config('auth.mediawiki.client-secret')\n    c.MWOAuthenticator.index_url = get_config('auth.mediawiki.index-url')\nelif auth_type == 'globus':\n    c.JupyterHub.authenticator_class = 'oauthenticator.globus.GlobusOAuthenticator'\n    c.GlobusOAuthenticator.oauth_callback_url = get_config('auth.globus.callback-url')\n    c.GlobusOAuthenticator.client_id = get_config('auth.globus.client-id')\n    c.GlobusOAuthenticator.client_secret = get_config('auth.globus.client-secret')\n    c.GlobusOAuthenticator.identity_provider = get_config('auth.globus.identity-provider', '')\nelif auth_type == 'hmac':\n    c.JupyterHub.authenticator_class = 'hmacauthenticator.HMACAuthenticator'\n    c.HMACAuthenticator.secret_key = bytes.fromhex(get_config('auth.hmac.secret-key'))\nelif auth_type == 'dummy':\n    c.JupyterHub.authenticator_class = 'dummyauthenticator.DummyAuthenticator'\n    c.DummyAuthenticator.password = get_config('auth.dummy.password', None)\nelif auth_type == 'tmp':\n    c.JupyterHub.authenticator_class = 'tmpauthenticator.TmpAuthenticator'\nelif auth_type == 'lti':\n    c.JupyterHub.authenticator_class = 'ltiauthenticator.LTIAuthenticator'\n    c.LTIAuthenticator.consumers = get_config('auth.lti.consumers')\nelif auth_type == 'custom':\n    # full_class_name looks like \"myauthenticator.MyAuthenticator\".\n    # To create a docker image with this class availabe, you can just have the\n    # following Dockerifle:\n    #   FROM jupyterhub/k8s-hub:v0.4\n    #   RUN pip3 install myauthenticator\n    full_class_name = get_config('auth.custom.class-name')\n    c.JupyterHub.authenticator_class = full_class_name\n    auth_class_name = full_class_name.rsplit('.', 1)[-1]\n    auth_config = c[auth_class_name]\n    auth_config.update(get_config('auth.custom.config') or {})\nelse:\n    raise ValueError(\"Unhandled auth type: %r\" % auth_type)\n\nc.Authenticator.enable_auth_state = get_config('auth.state.enabled', False)\n\ndef generate_user_email(spawner):\n    \"\"\"\n    Used as the EMAIL environment variable\n    \"\"\"\n    return '{username}@{domain}'.format(\n        username=spawner.user.name, domain=email_domain\n    )\n\ndef generate_user_name(spawner):\n    \"\"\"\n    Used as GIT_AUTHOR_NAME and GIT_COMMITTER_NAME environment variables\n    \"\"\"\n    return spawner.user.name\n\nc.KubeSpawner.environment = {\n    'EMAIL': generate_user_email,\n    # git requires these committer attributes\n    'GIT_AUTHOR_NAME': generate_user_name,\n    'GIT_COMMITTER_NAME': generate_user_name\n}\n\nc.KubeSpawner.environment.update(get_config('singleuser.extra-env', {}))\n\n# Enable admins to access user servers\nc.JupyterHub.admin_access = get_config('auth.admin.access')\nc.Authenticator.admin_users = get_config('auth.admin.users', [])\nc.Authenticator.whitelist = get_config('auth.whitelist.users', [])\n\nc.JupyterHub.base_url = get_config('hub.base_url')\n\nc.JupyterHub.services = []\n\nif get_config('cull.enabled', False):\n    cull_timeout = get_config('cull.timeout')\n    cull_every = get_config('cull.every')\n    cull_cmd = [\n        '/usr/local/bin/cull_idle_servers.py',\n        '--timeout=%s' % cull_timeout,\n        '--cull-every=%s' % cull_every,\n        '--url=http://127.0.0.1:8081' + c.JupyterHub.base_url + 'hub/api'\n    ]\n    if get_config('cull.users'):\n        cull_cmd.append('--cull-users')\n    c.JupyterHub.services.append({\n        'name': 'cull-idle',\n        'admin': True,\n        'command': cull_cmd,\n    })\n\nfor name, service in get_config('hub.services', {}).items():\n    api_token = get_secret('services.token.%s' % name)\n    # jupyterhub.services is a list of dicts, but\n    # in the helm chart it is a dict of dicts for easier merged-config\n    service.setdefault('name', name)\n    if api_token:\n        service['api_token'] = api_token\n    c.JupyterHub.services.append(service)\n\n\nc.JupyterHub.db_url = get_config('hub.db_url')\n\ncmd = get_config('singleuser.cmd', None)\nif cmd:\n    c.Spawner.cmd = cmd\n\n\nscheduler_strategy = get_config('singleuser.scheduler-strategy', 'spread')\n\nif scheduler_strategy == 'pack':\n    # FIXME: Support setting affinity directly in KubeSpawner\n    c.KubeSpawner.singleuser_extra_pod_config = {\n        'affinity': {\n            'podAffinity': {\n                'preferredDuringSchedulingIgnoredDuringExecution': [{\n                    'weight': 100,\n                    'podAffinityTerm': {\n                        'labelSelector': {\n                            'matchExpressions': [{\n                                'key': 'component',\n                                'operator': 'In',\n                                'values': ['singleuser-server']\n                            }]\n                        },\n                        'topologyKey': 'kubernetes.io/hostname'\n                    }\n                }],\n            }\n        }\n    }\nelse:\n    # Set default to {} so subconfigs can easily update it\n    c.KubeSpawner.singleuser_extra_pod_config = {}\n\nextra_config_path = '/etc/jupyterhub/config/hub.extra-config.py'\nif os.path.exists(extra_config_path):\n    load_subconfig(extra_config_path)\n", "path": "images/hub/jupyterhub_config.py"}]}