Repository: jupyterhub/zero-to-jupyterhub-k8s

Issue:
Mismatched helm chart versions
<!-- Thank you for contributing. These HTML comments will not render in the issue, but you can delete them once you've read them if you prefer! -->

### Bug description

Versions of helm charts are mismatched on the page that first installs the chart (0.9.0):

https://zero-to-jupyterhub.readthedocs.io/en/latest/jupyterhub/installation.html

and the page that shows how to upgrade the configuration (0.8.2):

https://zero-to-jupyterhub.readthedocs.io/en/latest/jupyterhub/customizing/extending-jupyterhub.html

I believe that this can have some rather unfortunate consequences, when trying to upgrade the configuration. 

In particular, I get this error: 

```
➜  kubectl get pod --namespace $NAMESPACE
NAME                     READY   STATUS             RESTARTS   AGE
hub-7f64886954-n8phz     0/1     CrashLoopBackOff   6          11m
proxy-77dfb66845-gg8gl   1/1     Running            0          11m
➜  kubectl logs hub-7f64886954-n8phz     
Loading /etc/jupyterhub/config/values.yaml
Loading /etc/jupyterhub/secret/values.yaml
[I 2020-12-21 22:25:53.684 JupyterHub app:1673] Using Authenticator: oauthenticator.github.GitHubOAuthenticator-0.8.0
[I 2020-12-21 22:25:53.684 JupyterHub app:1673] Using Spawner: kubespawner.spawner.KubeSpawner
[I 2020-12-21 22:25:53.685 JupyterHub app:1016] Loading cookie_secret from /srv/jupyterhub/jupyterhub_cookie_secret
[I 2020-12-21 22:25:53.699 JupyterHub dbutil:125] Upgrading sqlite:///jupyterhub.sqlite
[I 2020-12-21 22:25:53.700 JupyterHub dbutil:105] Backing up jupyterhub.sqlite => jupyterhub.sqlite.2020-12-21-222553
[I 2020-12-21 22:25:54.202 alembic.runtime.migration migration:130] Context impl SQLiteImpl.
[I 2020-12-21 22:25:54.203 alembic.runtime.migration migration:137] Will assume non-transactional DDL.
FAILED: Can't locate revision identified by '4dc2d5a8c53c'
[E 2020-12-21 22:25:54.207 alembic.util.messaging messaging:60] Can't locate revision identified by '4dc2d5a8c53c'
[E 2020-12-21 22:25:54.268 JupyterHub app:1958]
    Traceback (most recent call last):
      File "/usr/local/lib/python3.6/dist-packages/jupyterhub/app.py", line 1955, in launch_instance_async
        await self.initialize(argv)
      File "/usr/local/lib/python3.6/dist-packages/jupyterhub/app.py", line 1680, in initialize
        self.init_db()
      File "/usr/local/lib/python3.6/dist-packages/jupyterhub/app.py", line 1073, in init_db
        dbutil.upgrade_if_needed(self.db_url, log=self.log)
      File "/usr/local/lib/python3.6/dist-packages/jupyterhub/dbutil.py", line 130, in upgrade_if_needed
        upgrade(db_url)
      File "/usr/local/lib/python3.6/dist-packages/jupyterhub/dbutil.py", line 89, in upgrade
        ['alembic', '-c', alembic_ini, 'upgrade', revision]
      File "/usr/lib/python3.6/subprocess.py", line 291, in check_call
        raise CalledProcessError(retcode, cmd)
    subprocess.CalledProcessError: Command '['alembic', '-c', '/tmp/tmp3n17twak/alembic.ini', 'upgrade', 'head']' returned non-zero exit status 255.

```

#### Expected behaviour

I think this should be 0.9.0 in both places. 




Discussion:
Excellent point, this relates to https://github.com/jupyterhub/zero-to-jupyterhub-k8s/issues/1952! We end up with trouble having hardcoded versions in docs

Resolving patch:
diff --git a/doc/source/conf.py b/doc/source/conf.py
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -1,8 +1,7 @@
-# Configuration file for the Sphinx documentation builder.
+# Configuration file for Sphinx to build our documentation to HTML.
+#
+# Configuration reference: https://www.sphinx-doc.org/en/master/usage/configuration.html
 #
-# This file only contains a selection of the most common options. For a full
-# list see the documentation:
-# https://www.sphinx-doc.org/en/master/usage/configuration.html
 
 # -- Path setup --------------------------------------------------------------
 
@@ -17,7 +16,10 @@
 
 # -- Project specific imports ------------------------------------------------
 
-from datetime import date
+import datetime
+import os
+import re
+import subprocess
 
 import yaml
 
@@ -30,32 +32,100 @@ def setup(app):
     app.add_css_file("custom.css")
 
 
-# -- Project information -----------------------------------------------------
-# ref: https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+# -- Referencable variables --------------------------------------------------
 
-project = "Zero to JupyterHub with Kubernetes"
-copyright = "{year}, Project Jupyter Contributors".format(year=date.today().year)
-author = "Project Jupyter Contributors"
 
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
+def _get_latest_tag():
+    """Get the latest tag on a commit in branch or return None."""
+    try:
+        # If the git command output is my-tag-14-g0aed65e,
+        # then the return value will become my-tag.
+        return (
+            subprocess.check_output(["git", "describe", "--tags", "--long"])
+            .decode("utf-8")
+            .strip()
+            .rsplit("-", maxsplit=2)[0]
+        )
+    except subprocess.CalledProcessError:
+        return None
+
+
+def _get_git_ref_from_chartpress_based_version(version):
+    """
+    Get a git ref from a chartpress set version of format like
+    1.2.3-beta.1.n123.h1234567, 1.2.3-n123.h1234567, or 1.2.3.
+    """
+    tag_hash_split = re.split("[\.|-]n\d\d\d\.h", version)
+    if len(tag_hash_split) == 2:
+        return tag_hash_split[1]
+    else:
+        return tag_hash_split[0]
+
+
+# FIXME: Stop relying on chartpress to modify Chart.yaml (and values.yaml) by
+#        creating a new feature of chartpress that allows us to directly acquire
+#        the dynamically set chart version from Chart.yaml. This would be
+#        similar to the --list-images feature of chartpress.
+subprocess.run(["chartpress", "--skip-build"], cwd=os.path.abspath("../.."))
 with open("../../jupyterhub/Chart.yaml") as f:
     chart = yaml.safe_load(f)
-version = chart["version"].split("-", 1)[0]
-release = chart["version"]
+subprocess.run(["chartpress", "--reset"], cwd=os.path.abspath("../.."))
+
+latest_tag = _get_latest_tag()
+chart_version = chart["version"]
+chart_version_git_ref = _get_git_ref_from_chartpress_based_version(chart_version)
+jupyterhub_version = chart["appVersion"]
+kube_version = chart["kubeVersion"].split("-", 1)[0]
+
+# These substitution variables only work in rst contexts, and not within links
+# etc. Reference them using |variable_name|.
+#
+# rst_epilog ref: https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-rst_epilog
+#
+# FIXME: We can't substitute something for an entire link, because it is
+#        considered illegal for security reasons presumably. But, only for
+#        rst_epilog, not for myst_substitutions. PS: security wise, I think the
+#        difference is okay because myst_substitutions is entirely defined in
+#        conf.py.
+rst_epilog = f"""
+.. |latest_tag| replace:: {latest_tag}
+.. |chart_version| replace:: {chart_version}
+.. |chart_version_git_ref| replace:: {chart_version_git_ref}
+.. |jupyterhub_version| replace:: {jupyterhub_version}
+.. |kube_version| replace:: {kube_version}
+"""
+
+# These substitution variables only work in markdown contexts, and does not work
+# within links etc. Reference using {{ variable_name }}
+#
+# myst_substitutions ref: https://myst-parser.readthedocs.io/en/latest/using/syntax-optional.html#substitutions-with-jinja2
+myst_substitutions = {
+    "latest_tag": latest_tag,
+    "chart_version": chart_version,
+    "chart_version_git_ref": chart_version_git_ref,
+    "jupyterhub_version": jupyterhub_version,
+    "kube_version": kube_version,
+    "requirements": f"[hub/images/requirements.txt](https://github.com/jupyterhub/zero-to-jupyterhub-k8s/blob/{chart_version_git_ref}/images/hub/requirements.txt)",
+}
+
+
+# -- General MyST configuration -----------------------------------------------------
+
+# myst_enable_extensions ref: https://myst-parser.readthedocs.io/en/latest/using/syntax-optional.html
+myst_enable_extensions = [
+    "substitution",
+]
 
-# Project specific variables
-# ref: https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-rst_epilog
-rst_epilog = """
-.. |hub_version| replace:: {v}
-""".format(
-    v=chart["appVersion"]
-)
+
+# -- Project information -----------------------------------------------------
+# ref: https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = "Zero to JupyterHub with Kubernetes"
+copyright = f"{datetime.date.today().year}, Project Jupyter Contributors"
+author = "Project Jupyter Contributors"
 
 
-# -- General configuration ---------------------------------------------------
+# -- General Sphinx configuration ---------------------------------------------------
 # ref: https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
 
 # Set the default role so we can use `foo` instead of ``foo``


Test patch: