How to define Spawner.args with a dict using new traitlets format?

We’re using z2jh 0.10.6 and jupyterhub 1.2.2. I noticed that we were getting warnings like this in the notebook pod logs:

Mar 5 11:53:49 jupyter-6042701be8aa59a7af9d628b notebook /opt/conda/lib/python3.8/site-packages/traitlets/traitlets.py:2939: FutureWarning: --tornado_settings={“headers”:{‘Access-Control-Allow-Origin’: ‘*’, ‘Content-Security-Policy’: “frame-ancestors * ‘self’”},“cookie_options”: {“SameSite”: “None”, “Secure”: True}} for dict-traits is deprecated in traitlets 5.0. You can pass --tornado_settings <key=value> … multiple times to add items to a dict.
warn(

In our hub helm chart values file, in extraConfig, we were setting the c.Spawner.args like this:

c.Spawner.args = [
        "--NotebookApp.tornado_settings={\"headers\":{'Access-Control-Allow-Origin': '*', 'Content-Security-Policy': \"frame-ancestors * 'self'\"},\"cookie_options\": {\"SameSite\": \"None\", \"Secure\": True}}",
...

Following that warning, and looking at how the dict arg test code works in traitlets [1], we tried this:

      c.Spawner.args = [
        "--ServerApp.tornado_settings headers={'Access-Control-Allow-Origin': '*', 'Content-Security-Policy': \"frame-ancestors * 'self'\"}",
        "--ServerApp.tornado_settings cookie_options={\"SameSite\": \"None\", \"Secure\": True}",
...

But now the server pod startup fails with this:

Apr 9 15:57:22 jupyter-5e18a4193f4a3f001127f809 notebook [C 2021-04-09 20:57:22.397 SingleUserNotebookApp notebookapp:1620] No such file or directory: /home/jovyan/–ServerApp.tornado_settings headers={‘Access-Control-Allow-Origin’: ‘*’, ‘Content-Security-Policy’: “frame-ancestors * ‘self’”}

I started poking through the notebook->jupyter-server migration guide [2] and created a symlink from jupyter_server_config.py to jupyter_notebook_config.py in our singleuser notebook image, and I also followed [3] and set JUPYTERHUB_SINGLEUSER_APP='jupyter_server.serverapp.ServerApp' in the hub pod config but those don’t seem to help.

I’m assuming the way we’ve defined the Spawner.args dict values is somehow breaking the command line parser with traitlets but I’m at a loss as to what we should be using since the dict-arg tests in traitlets are pretty basic and I can’t find anything in the docs.

I’m also wondering if maybe we should just hold off until [4] is complete.

[1] https://github.com/ipython/traitlets/blob/9f6276418d85aee49d12ab6495cf8a6acea8b961/traitlets/config/tests/test_loader.py#L330-L342
[2] Migrating from Notebook Server — Jupyter Server documentation
[3] https://github.com/jupyterhub/jupyterhub/blob/d31af278887e52e8fc42aa822b7624a3ec9ead11/docs/source/reference/config-user-env.md#switching-to-jupyter-server
[4] https://github.com/jupyterhub/zero-to-jupyterhub-k8s/issues/776

If it helps we have jupyterlab 3.0.5 and jupyter_server 1.4.1 installed in the singleuser notebook image.

Do you still see those warnings/errors if you ignore JupyterHub and run Jupyter notebook/lab directly, passing the *tornado_settings* arguments on the command line?

Sorry for the late reply. Yes I’m still seeing those when trying to pass through to jupyterlab directly locally. To recreate:

cd /tmp
virtualenv .venv
source .venv/bin/activate
pip install jupyterlab
jupyter lab --ServerApp.tornado_settings headers={'Access-Control-Allow-Origin': '*', 'Content-Security-Policy': \"frame-ancestors * 'self'\"}

That results in:

(.venv) osboxes@osboxes:/tmp$ jupyter lab --ServerApp.tornado_settings headers={'Access-Control-Allow-Origin': '*', 'Content-Security-Policy': \"frame-ancestors * 'self'\"}
[C 2021-05-07 10:27:11.813 ServerApp] No such file or directory: /tmp/*,

When running in a shell you’ll have to quote the full arguments to prevent shell expansion (this isn’t needed in the config file since shell expansion shouldn’t happen). With these additional quotes:

jupyter lab --ServerApp.tornado_settings "headers={'Access-Control-Allow-Origin': '*', 'Content-Security-Policy': \"frame-ancestors * 'self'\"}"

starts, though I get some other error when I open it in my browser. This probably needs the help of someone from the jupyter-server team.

I reached this post while looking for a solution to set exactly the same kind of headers.

I didn’t manage to make it work with the command line params, so as an alternative I found these headers can be added to the ingress in kubernetes. This removes the need to have the tornado_settings set on the hub and on the single-user server.

For reference as it may be useful for someone in the future, in my case, I’m using GKE and followed this documentation: https://cloud.google.com/kubernetes-engine/docs/how-to/ingress-configuration#response_headers with setting the BackendConfig for the proxy.

Backend config:

apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
  name: jupyter-backend-config
spec:
  customResponseHeaders:
    headers:
    - "content-security-policy: frame-ancestors * 'self'"

and config for helm:

proxy:
  service:
    annotations:
      cloud.google.com/backend-config: '{"default": "jupyter-backend-config"}'