Repository: jupyterhub/binderhub
Pull request #367 (2017-12-20 01:40:49)

Allow banning repo specs
And allow setting arbitrary extra config in the binderhub config


Patch:
diff --git a/binderhub/builder.py b/binderhub/builder.py
index 1db4722a..46d3779b 100644
--- a/binderhub/builder.py
+++ b/binderhub/builder.py
@@ -175,6 +175,13 @@ class BuildHandler(BaseHandler):
             await self.fail(str(e))
             return
 
+        if provider.is_banned():
+            await self.emit({
+                'phase': 'failed',
+                'message': 'Sorry, {} has been temporarily disabled from launching. Please contact admins for more info!'.format(spec)
+            })
+            return
+
         repo = self.repo = provider.get_repo_url()
 
         try:
diff --git a/binderhub/repoproviders.py b/binderhub/repoproviders.py
index a37850ca..14db1171 100644
--- a/binderhub/repoproviders.py
+++ b/binderhub/repoproviders.py
@@ -18,7 +18,7 @@ from tornado import gen
 from tornado.httpclient import AsyncHTTPClient, HTTPError
 from tornado.httputil import url_concat
 
-from traitlets import Dict, Unicode, Bool, default
+from traitlets import Dict, Unicode, Bool, default, List
 from traitlets.config import LoggingConfigurable
 
 GITHUB_RATE_LIMIT = Gauge('binderhub_github_rate_limit_remaining', 'GitHub rate limit remaining')
@@ -58,9 +58,27 @@ class RepoProvider(LoggingConfigurable):
         """
     )
 
+    banned_specs = List(
+        help="""
+        List of specs to blacklist building.
+
+        Should be a list of regexes (not regex objects) that match specs which should be blacklisted
+        """,
+        config=True
+    )
+
     unresolved_ref = Unicode()
 
 
+    def is_banned(self):
+        """
+        Return true if the given spec has been banned
+        """
+        for banned in self.banned_specs:
+            if re.match(banned, self.spec):
+                return True
+        return False
+
     @gen.coroutine
     def get_resolved_ref(self):
         raise NotImplementedError("Must be overridden in child class")
@@ -430,5 +448,3 @@ class GistRepoProvider(GitHubRepoProvider):
 
     def get_build_slug(self):
         return self.gist_id
-
-
diff --git a/helm-chart/binderhub/templates/configmap.yaml b/helm-chart/binderhub/templates/configmap.yaml
index 5f726241..73b73b63 100644
--- a/helm-chart/binderhub/templates/configmap.yaml
+++ b/helm-chart/binderhub/templates/configmap.yaml
@@ -21,3 +21,10 @@ data:
   {{ if .Values.dind.enabled }}
   dind.host-socket-dir: {{ .Values.dind.hostSocketDir | quote }}
   {{ end }}
+
+  {{ if .Values.extraConfig }}
+  {{ range $key, $value := .Values.extraConfig -}}
+  extra-config.{{ $key }}.py: |
+{{ $value | indent 4 }}
+  {{- end }}
+  {{- end }}
diff --git a/helm-chart/binderhub/values.yaml b/helm-chart/binderhub/values.yaml
index ffdfe918..8deb9c94 100644
--- a/helm-chart/binderhub/values.yaml
+++ b/helm-chart/binderhub/values.yaml
@@ -37,6 +37,8 @@ github:
   # ...or access_token
   accessToken:
 
+extraConfig: {}
+
 # have to set cors.allowOrigin twice:
 # once in top-level cors.allowOrigin,
 # and again in jupyterhub.hub.extraConfigMap.cors.allowOrigin
diff --git a/helm-chart/images/binderhub/binderhub_config.py b/helm-chart/images/binderhub/binderhub_config.py
index 40c229d3..0014db0b 100644
--- a/helm-chart/images/binderhub/binderhub_config.py
+++ b/helm-chart/images/binderhub/binderhub_config.py
@@ -1,4 +1,5 @@
 import os
+import glob
 import yaml
 
 def get_config(key, default=None):
@@ -46,3 +47,7 @@ if allow_origin:
             'Access-Control-Allow-Origin': allow_origin,
         }
     })
+
+
+for path in glob.glob('/etc/binderhub/config/extra-config.*.py'):
+    load_subconfig(path)


Test patch:
diff --git a/binderhub/tests/test_repoproviders.py b/binderhub/tests/test_repoproviders.py
index 6a1142af..c69db65e 100644
--- a/binderhub/tests/test_repoproviders.py
+++ b/binderhub/tests/test_repoproviders.py
@@ -45,6 +45,23 @@ def test_github_ref():
     ref = IOLoop().run_sync(provider.get_resolved_ref)
     assert ref == 'f7f3ff6d1bf708bdc12e5f10e18b2a90a4795603'
 
+def test_not_banned():
+    provider = GitHubRepoProvider(
+        spec='jupyterhub/zero-to-jupyterhub-k8s/v0.4',
+        banned_specs=[
+            '^yuvipanda.*'
+        ]
+    )
+    assert not provider.is_banned()
+
+def test_banned():
+    provider = GitHubRepoProvider(
+        spec='jupyterhub/zero-to-jupyterhub-k8s/v0.4',
+        banned_specs=[
+            '^jupyterhub.*'
+        ]
+    )
+    assert provider.is_banned()
 
 def test_github_missing_ref():
     provider = GitHubRepoProvider(spec='jupyterhub/zero-to-jupyterhub-k8s/v0.1.2.3.4.5.6')