Skip to content

Commit d56faf7

Browse files
committed
Added meca provider
1 parent 45ad8fa commit d56faf7

File tree

6 files changed

+127
-18
lines changed

6 files changed

+127
-18
lines changed

binderhub/app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
from .ratelimit import RateLimiter
5757
from .registry import DockerRegistry
5858
from .repoproviders import (
59+
MecaRepoProvider,
5960
DataverseProvider,
6061
FigshareProvider,
6162
GistRepoProvider,
@@ -586,6 +587,7 @@ def _default_build_namespace(self):
586587
"figshare": FigshareProvider,
587588
"hydroshare": HydroshareProvider,
588589
"dataverse": DataverseProvider,
590+
"meca": MecaRepoProvider,
589591
},
590592
config=True,
591593
help="""

binderhub/event-schemas/launch.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"Zenodo",
1515
"Figshare",
1616
"Hydroshare",
17-
"Dataverse"
17+
"Dataverse",
18+
"MECA"
1819
],
1920
"description": "Provider for the repository being launched"
2021
},

binderhub/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"figshare": "Figshare",
2323
"hydroshare": "Hydroshare",
2424
"dataverse": "Dataverse",
25+
"meca": "MECA",
2526
}
2627

2728

binderhub/repoproviders.py

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import time
1616
import urllib.parse
1717
from datetime import datetime, timedelta, timezone
18-
from urllib.parse import urlparse
18+
from urllib.parse import urlparse, urlunparse
1919

2020
import escapism
2121
from prometheus_client import Gauge
@@ -263,6 +263,104 @@ def get_build_slug(self):
263263
return f"zenodo-{self.record_id}"
264264

265265

266+
class MecaRepoProvider(RepoProvider):
267+
"""BinderHub Provider that can handle the contents of a MECA bundle
268+
269+
Users must provide a spec consisting of a public the URL to the bundle
270+
The URL origin must conform to the origin trait when that is set
271+
"""
272+
273+
name = Unicode("MECA Bundle")
274+
275+
display_name = "MECA Bundle"
276+
277+
labels = {
278+
"text": "MECA Bundle URL (https://journals.curvenote.com/journal/submissions/12345/meca.zip)",
279+
"tag_text": "<no tag required>",
280+
"ref_prop_disabled": True,
281+
"label_prop_disabled": True,
282+
}
283+
284+
validate_bundle = Bool(config=True, help="Validate the file as MECA Bundle").tag(
285+
default=True
286+
)
287+
288+
allowed_origins = List(
289+
config=True,
290+
help="""List of allowed origins for the URL
291+
292+
If set, the URL must be on one of these origins.
293+
294+
If not set, the URL can be on any origin.
295+
""",
296+
)
297+
298+
@default("allowed_origins")
299+
def _allowed_origins_default(self):
300+
return []
301+
302+
def __init__(self, *args, **kwargs):
303+
super().__init__(*args, **kwargs)
304+
305+
url = unquote(self.spec)
306+
307+
if not val.url(url):
308+
raise ValueError(f"[MecaRepoProvider] Invalid URL {url}")
309+
310+
if (
311+
len(self.allowed_origins) > 0
312+
and urlparse(self.spec).hostname not in self.allowed_origins
313+
):
314+
raise ValueError("URL is not on an allowed origin")
315+
316+
self.url = url
317+
318+
self.log.info(f"MECA Bundle URL: {self.url}")
319+
self.log.info(f"MECA Bundle raw spec: {self.spec}")
320+
321+
async def get_resolved_ref(self):
322+
# Check the URL is reachable
323+
client = AsyncHTTPClient()
324+
req = HTTPRequest(self.url, method="HEAD", user_agent="BinderHub")
325+
self.log.info(f"get_resolved_ref() HEAD: {self.url}")
326+
try:
327+
r = await client.fetch(req)
328+
self.log.info(f"URL is reachable: {self.url}")
329+
self.hashed_slug = get_hashed_slug(
330+
self.url, r.headers.get("ETag") or r.headers.get("Content-Length")
331+
)
332+
except Exception as e:
333+
raise ValueError(f"URL is unreachable ({e})")
334+
335+
self.log.info(f"hashed_slug: {self.hashed_slug}")
336+
return self.hashed_slug
337+
338+
async def get_resolved_spec(self):
339+
if not hasattr(self, "hashed_slug"):
340+
await self.get_resolved_ref()
341+
self.log.info(f"get_resolved_spec(): {self.hashed_slug}")
342+
return self.spec
343+
344+
async def get_resolved_ref_url(self):
345+
self.log.info(f"get_resolved_ref_url(): {self.url}")
346+
return self.url
347+
348+
def get_repo_url(self):
349+
"""This is passed to repo2docker and is the URL that is to be fetched
350+
with a `http[s]+meca` protocol string. We do this by convention to enable
351+
detection of meca urls by the MecaContentProvider.
352+
"""
353+
parsed = urlparse(self.url)
354+
parsed = parsed._replace(scheme=f"{parsed.scheme}+meca")
355+
url = urlunparse(parsed)
356+
self.log.info(f"get_repo_url(): {url}")
357+
return url
358+
359+
def get_build_slug(self):
360+
"""Should return a unique build slug"""
361+
return self.hashed_slug
362+
363+
266364
class FigshareProvider(RepoProvider):
267365
"""Provide contents of a Figshare article
268366

binderhub/static/js/src/form.js

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getPathType } from "./path";
1+
import { getPathType } from './path';
22

33
/**
44
* Parse current values in form and return them with appropriate URL encoding
@@ -11,31 +11,32 @@ import { getPathType } from "./path";
1111
* @returns {}
1212
*/
1313
export function getBuildFormValues() {
14-
const providerPrefix = $("#provider_prefix").val().trim();
15-
let repo = $("#repository").val().trim();
16-
if (providerPrefix !== "git") {
17-
repo = repo.replace(/^(https?:\/\/)?gist.github.com\//, "");
18-
repo = repo.replace(/^(https?:\/\/)?github.com\//, "");
19-
repo = repo.replace(/^(https?:\/\/)?gitlab.com\//, "");
14+
const providerPrefix = $('#provider_prefix').val().trim();
15+
let repo = $('#repository').val().trim();
16+
if (providerPrefix !== 'git') {
17+
repo = repo.replace(/^(https?:\/\/)?gist.github.com\//, '');
18+
repo = repo.replace(/^(https?:\/\/)?github.com\//, '');
19+
repo = repo.replace(/^(https?:\/\/)?gitlab.com\//, '');
2020
}
2121
// trim trailing or leading '/' on repo
22-
repo = repo.replace(/(^\/)|(\/?$)/g, "");
22+
repo = repo.replace(/(^\/)|(\/?$)/g, '');
2323
// git providers encode the URL of the git repository as the repo
2424
// argument.
25-
if (repo.includes("://") || providerPrefix === "gl") {
25+
if (repo.includes('://') || providerPrefix === 'gl') {
2626
repo = encodeURIComponent(repo);
2727
}
2828

29-
let ref = $("#ref").val().trim() || $("#ref").attr("placeholder");
29+
let ref = $('#ref').val().trim() || $('#ref').attr('placeholder');
3030
if (
31-
providerPrefix === "zenodo" ||
32-
providerPrefix === "figshare" ||
33-
providerPrefix === "dataverse" ||
34-
providerPrefix === "hydroshare"
31+
providerPrefix === 'zenodo' ||
32+
providerPrefix === 'figshare' ||
33+
providerPrefix === 'dataverse' ||
34+
providerPrefix === 'hydroshare' ||
35+
providerPrefix === 'meca'
3536
) {
36-
ref = "";
37+
ref = '';
3738
}
38-
const path = $("#filepath").val().trim();
39+
const path = $('#filepath').val().trim();
3940
return {
4041
providerPrefix: providerPrefix,
4142
repo: repo,

docs/source/reference/repoproviders.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ Module: :mod:`binderhub.repoproviders`
6565
.. autoconfigurable:: DataverseProvider
6666
:members:
6767

68+
:class:`MecaRepoProvider`
69+
---------------------------
70+
71+
.. autoconfigurable:: MecaRepoProvider
72+
:members:
73+
6874

6975
:class:`GitRepoProvider`
7076
---------------------------

0 commit comments

Comments
 (0)