async def _kv_atomic_delete_route_parts(self, jupyterhub_routespec, route_keys):
        escaped_jupyterhub_routespec = escapism.escape(
            jupyterhub_routespec, safe=self.key_safe_chars
        )

        index, v = await self.kv_client.kv.get(escaped_jupyterhub_routespec)
        if v is None:
            self.log.warning("Route %s doesn't exist. Nothing to delete", routespec)
            return
        target = v["Value"]
        escaped_target = escapism.escape(target, safe=self.key_safe_chars)

        try:
            status, response = await self.kv_client.txn.put(
                payload=[
                    {"KV": {"Verb": "delete", "Key": escaped_jupyterhub_routespec}},
                    {"KV": {"Verb": "delete", "Key": escaped_target}},
                    {"KV": {"Verb": "delete", "Key": route_keys.backend_url_path}},
                    {"KV": {"Verb": "delete", "Key": route_keys.backend_weight_path}},
                    {"KV": {"Verb": "delete", "Key": route_keys.frontend_backend_path}},
                    {"KV": {"Verb": "delete", "Key": route_keys.frontend_rule_path}},
                ]
            )
            status = 1
            response = ""
        except Exception as e:
            status = 0
            response = str(e)

        return status, response
Example #2
0
    def fetch(self, url, ref, checkout_path):
        """Fetch the contents of `url` and place it in `checkout_path`.

        The `ref` parameter specifies what "version" of the contents should be
        fetched. In the case of a git repository `ref` is the SHA-1 of a commit.

        Iterate through possible content providers until a valid provider,
        based on URL, is found.
        """
        picked_content_provider = None
        for ContentProvider in self.content_providers:
            cp = ContentProvider()
            spec = cp.detect(url, ref=ref)
            if spec is not None:
                picked_content_provider = cp
                self.log.info(
                    "Picked {cp} content "
                    "provider.\n".format(cp=cp.__class__.__name__)
                )
                break

        if picked_content_provider is None:
            self.log.error(
                "No matching content provider found for " "{url}.".format(url=url)
            )

        # I want to use a branch name accessible to the Europa app to display
        # the current branch contents. An option is to use the hash part of
        # the JupyterHub User but this is not known until after the repo2docker
        # image is created.
        # if isinstance(picked_content_provider, contentproviders.Git):
        #     suffix = datetime.now().strftime("%Y%m%d%H%M%S")
        #     self.branch = "europa-" + suffix
        #     spec["branch"] = self.branch
        self.branch = ref

        for log_line in picked_content_provider.fetch(
            spec, checkout_path, yield_output=self.json_logs
        ):
            self.log.info(log_line, extra=dict(phase="fetching"))

        if not self.output_image_spec:
            self.output_image_spec = (
                "r2d" + escapism.escape(self.repo, escape_char="-").lower()
            )
            # if we are building from a subdirectory include that in the
            # image name so we can tell builds from different sub-directories
            # apart.
            if self.subdir:
                self.output_image_spec += escapism.escape(
                    self.subdir, escape_char="-"
                ).lower()
            if picked_content_provider.content_id is not None:
                self.output_image_spec += picked_content_provider.content_id
            else:
                self.output_image_spec += str(int(time.time()))
Example #3
0
    def get_pvc_manifest(self):
        """
        Make a pvc manifest that will spawn current user's pvc.
        """
        # Default set of labels, picked up from
        # https://github.com/kubernetes/helm/blob/master/docs/chart_best_practices/labels.md
        labels = {
            'heritage': 'jupyterhub',
            'app': 'jupyterhub',
            'hub.jupyter.org/userid': escapism.escape(str(self.user.id))
        }

        labels.update(self._expand_all(self.user_storage_extra_labels))
        label_selector = self._expand_all(self.user_storage_pvc_selector)
        self.log.info("SEL %s" % label_selector)
        self.log.info("HEEEEERE!")
        self.log.info("%s %s %s %s %s %s", self.pvc_name,
                      self.user_storage_class, self.user_storage_access_modes,
                      self.user_storage_capacity, labels, label_selector)
        return make_pvc(name=self.pvc_name,
                        storage_class=self.user_storage_class,
                        access_modes=self.user_storage_access_modes,
                        storage=self.user_storage_capacity,
                        labels=labels,
                        label_selector=label_selector)
Example #4
0
    async def start(self):
        if self.repo is None:
            raise ValueError("Repo2DockerSpawner.repo must be set")
        resolved_ref = await resolve_ref(self.repo, self.ref)
        repo_escaped = escape(self.repo, escape_char='-').lower()
        image_spec = f'r2dspawner-{repo_escaped}:{resolved_ref}'

        image_info = await self.inspect_image(image_spec)
        if not image_info:
            self.log.info(f'Image {image_spec} not present, building...')
            r2d = Repo2Docker()
            r2d.repo = self.repo
            r2d.ref = resolved_ref
            r2d.user_id = 1000
            r2d.user_name = 'jovyan'

            r2d.output_image_spec = image_spec
            r2d.initialize()

            await self.run_in_executor(r2d.build)

        # HACK: DockerSpawner (and traitlets) don't seem to realize we're setting 'cmd',
        # and refuse to use our custom command. Explicitly set this variable for
        # now.
        self._user_set_cmd = True

        self.log.info(
            f'Launching with image {image_spec} for {self.user.name}')
        self.image = image_spec

        return await super().start()
 def _escape(self, s):
     """Escape a string to docker-safe characters"""
     return escape(
         s.lower(),
         safe=string.ascii_letters + string.digits + '-',
         escape_char='_',
     )
Example #6
0
def escape_docker(s):
    """Escape a string to docker-safe characters"""
    return escapism.escape(
        s,
        safe=_docker_safe_chars,
        escape_char=_docker_escape_char_docker,
    )
Example #7
0
 def get_route(self, routespec):
     name = escape(routespec, escape_char='-')
     target = self.client.read(
         self._k('backends', name, 'servers', 'notebook', 'url'))
     data = json.loads(self.client.read(
         self._k('frontends', name, 'extra')
     ))
Example #8
0
def test_escape_default():
    for s in test_strings:
        e = escape(s)
        assert isinstance(e, text)
        u = unescape(e)
        assert isinstance(u, text)
        assert u == s
    def __init__(self, headers):
        self.authenticated = all(
            [header in headers.keys() for header in self.auth_headers])
        if not self.authenticated:
            return
        parsed_id_token = self.parse_jwt_from_headers(headers)
        self.keycloak_user_id = parsed_id_token["sub"]
        self.email = parsed_id_token["email"]
        self.full_name = parsed_id_token["name"]
        self.username = parsed_id_token["preferred_username"]
        self.safe_username = escapism.escape(self.username,
                                             escape_char="-").lower()
        self.oidc_issuer = parsed_id_token["iss"]

        (
            self.git_url,
            self.git_auth_header,
            self.git_token,
        ) = self.git_creds_from_headers(headers)
        self.gitlab_client = Gitlab(
            self.git_url,
            api_version=4,
            oauth_token=self.git_token,
        )
        self.setup_k8s()
Example #10
0
    def add_route(self, routespec, backend, data):
        if routespec.startswith('/'):
            # url only spec
            host = None
            url_prefix = routespec
        else:
            host, url_prefix = routespec.split('/', 1)

        if data is None:
            data = {}
        data.update({'jupyterhub-route': True})

        name = escape(routespec, escape_char='-')
        self.log.info(name)
        self.client.write(
            self._k('backends', name, 'servers', 'notebook', 'url'), backend)

        self.client.write(
            self._k('frontends', name, 'backend'), name)

        self.client.write(
            self._k('frontends', name, 'routes', 'prefix', 'rule'), 'PathPrefix:{}'.format(url_prefix))
        if host:
            self.client.write(self._k(
                'frontends', name, 'routes', 'host', 'rule'), 'Host:{}'.format(host))

        self.client.write(
            self._k('frontends', name, 'extra'), json.dumps(data))
Example #11
0
 async def _kv_get_target(self, jupyterhub_routespec):
     escaped_jupyterhub_routespec = escapism.escape(
         jupyterhub_routespec, safe=self.key_safe_chars)
     _, res = await self.kv_client.kv.get(escaped_jupyterhub_routespec)
     if res is None:
         return None
     return res["Value"].decode()
Example #12
0
    async def _kv_get_data(self, target):
        escaped_target = escapism.escape(target, safe=self.key_safe_chars)
        _, res = await self.kv_client.kv.get(escaped_target)

        if res is None:
            return None
        return res["Value"].decode()
Example #13
0
 def escaped_name(self):
     if self._escaped_name is None:
         self._escaped_name = escape(self.user.name,
             safe=self._container_safe_chars,
             escape_char=self._container_escape_char,
         )
     return self._escaped_name
def create_pv(username, namespace, path, storage_size):
    safe_chars = set(string.ascii_lowercase + string.digits)

    # Need to format the username that same way jupyterhub does.
    username = escapism.escape(username, safe=safe_chars, escape_char='-').lower()

    name = 'gpfs-{!s}'.format(username)

    claim_name = 'claim-{!s}'.format(username)

    path = os.path.join(path, username)

    metadata = client.V1ObjectMeta(name=name, namespace=namespace)

    claim_ref = client.V1ObjectReference(namespace=namespace, name=claim_name)

    host_path = client.V1HostPathVolumeSource(path, 'DirectoryOrCreate')

    spec = client.V1PersistentVolumeSpec(
        access_modes=[
            'ReadWriteOnce',
        ], 
        capacity={
            'storage': storage_size,
        }, 
        claim_ref=claim_ref, 
        host_path=host_path, 
        storage_class_name='gpfs',
        persistent_volume_reclaim_policy='Retain',
        volume_mode='Filesystem')

    pv = client.V1PersistentVolume('v1', 'PersistentVolume', metadata, spec)

    return pv, path
Example #15
0
 def escaped_name(self):
     if self._escaped_name is None:
         self._escaped_name = escape(self.user.name,
             safe=self._container_safe_chars,
             escape_char=self._container_escape_char,
         )
     return self._escaped_name
Example #16
0
    def _get_route_unsafe(self, traefik_routespec):
        safe = string.ascii_letters + string.digits + "_-"
        escaped_routespec = escapism.escape(traefik_routespec, safe=safe)
        routespec = self._routespec_from_traefik_path(traefik_routespec)
        result = {"data": "", "target": "", "routespec": routespec}

        def get_target_data(d, to_find):
            if to_find == "url":
                key = "target"
            else:
                key = to_find
            if result[key]:
                return
            for k, v in d.items():
                if k == to_find:
                    result[key] = v
                if isinstance(v, dict):
                    get_target_data(v, to_find)

        for key, value in self.routes_cache["backends"].items():
            if escaped_routespec in key:
                get_target_data(value, "url")
        for key, value in self.routes_cache["frontends"].items():
            if escaped_routespec in key:
                get_target_data(value, "data")
        if not result["data"] and not result["target"]:
            self.log.info("No route for {} found!".format(routespec))
            result = None
        else:
            result["data"] = json.loads(result["data"])
        return result
Example #17
0
 def safe_name_for_routespec(self, routespec):
     safe_chars = set(string.ascii_lowercase + string.digits)
     safe_name = generate_hashed_slug(
         'jupyter-' +
         escapism.escape(routespec, safe=safe_chars, escape_char='-') +
         '-route')
     return safe_name
Example #18
0
def interpolate_properties(spawner, template):
    safe_chars = set(string.ascii_lowercase + string.digits)
    username = escapism.escape(spawner.user.name,
                               safe=safe_chars,
                               escape_char='-').lower()

    return template.format(userid=spawner.user.id, username=username)
Example #19
0
 def _escape(self, s):
     """Escape a string to docker-safe characters"""
     return escape(
         s,
         safe=self._docker_safe_chars,
         escape_char=self._docker_escape_char,
     )
Example #20
0
def test_subdir_in_image_name():
    app = Repo2Docker(repo=TEST_REPO, subdir="a directory")
    app.initialize()
    app.build()

    escaped_dirname = escapism.escape("a directory", escape_char="-").lower()
    assert escaped_dirname in app.output_image_spec
Example #21
0
def escape_kubernet(s):
    """Escape a string to kubernet-safe characters"""
    return escapism.escape(
        s,
        safe=_docker_safe_chars,
        escape_char=_docker_escape_char_kubernet,
    )
Example #22
0
def get_escaped_string(value):
    """
    This allows me to escape names just like kubespawner does.
    """
    safe_chars = set(string.ascii_lowercase + string.digits)
    return escapism.escape(value, safe=safe_chars,
                           escape_char='-').lower().rstrip("-")
Example #23
0
 def _escape(self, s):
     """Escape a string to docker-safe characters"""
     return escape(
         s,
         safe=self._docker_safe_chars,
         escape_char=self._docker_escape_char,
     )
Example #24
0
 def get_user_server_pods(user):
     safe_username = escapism.escape(user["name"], escape_char="-").lower()
     pods = v1.list_namespaced_pod(
         kubernetes_namespace,
         label_selector=
         f"heritage=jupyterhub,renku.io/username={safe_username}",
     )
     return pods.items
Example #25
0
def test_escape_custom_char():
    for escape_char in r'\-%+_':
        for s in test_strings:
            e = escape(s, escape_char=escape_char)
            assert isinstance(e, text)
            u = unescape(e, escape_char=escape_char)
            assert isinstance(u, text)
            assert u == s
Example #26
0
def test_safe_escape_char():
    escape_char = "-"
    safe = SAFE.union({escape_char})
    with pytest.warns(RuntimeWarning):
        e = escape(escape_char, safe=safe, escape_char=escape_char)
    assert e == "{}{:02X}".format(escape_char, ord(escape_char))
    u = unescape(e, escape_char=escape_char)
    assert u == escape_char
Example #27
0
    def fetch(self, url, ref, checkout_path):
        """Fetch the contents of `url` and place it in `checkout_path`.

        The `ref` parameter specifies what "version" of the contents should be
        fetched. In the case of a git repository `ref` is the SHA-1 of a commit.

        Iterate through possible content providers until a valid provider,
        based on URL, is found.
        """
        picked_content_provider = None
        for ContentProvider in self.content_providers:
            cp = ContentProvider()
            spec = cp.detect(url, ref=ref)
            if spec is not None:
                picked_content_provider = cp
                self.log.info(
                    "Picked {cp} content "
                    "provider.\n".format(cp=cp.__class__.__name__)
                )
                break

        if picked_content_provider is None:
            self.log.error(
                "No matching content provider found for " "{url}.".format(url=url)
            )

        for log_line in picked_content_provider.fetch(
            spec, checkout_path, yield_output=self.json_logs
        ):
            self.log.info(log_line, extra=dict(phase="fetching"))

        if not self.output_image_spec:
            self.output_image_spec = (
                "r2d" + escapism.escape(self.repo, escape_char="-").lower()
            )
            # if we are building from a subdirectory include that in the
            # image name so we can tell builds from different sub-directories
            # apart.
            if self.subdir:
                self.output_image_spec += escapism.escape(
                    self.subdir, escape_char="-"
                ).lower()
            if picked_content_provider.content_id is not None:
                self.output_image_spec += picked_content_provider.content_id
            else:
                self.output_image_spec += str(int(time.time()))
Example #28
0
    def initialize(self):
        args = self.get_argparser().parse_args()

        if args.debug:
            self.log_level = logging.DEBUG

        self.load_config_file(args.config)

        if os.path.exists(args.repo):
            # Let's treat this as a local directory we are building
            self.repo_type = 'local'
            self.repo = args.repo
            self.ref = None
            self.cleanup_checkout = False
        else:
            self.repo_type = 'remote'
            self.repo = args.repo
            self.ref = args.ref
            self.cleanup_checkout = args.clean

        if args.json_logs:
            # register JSON excepthook to avoid non-JSON output on errors
            sys.excepthook = self.json_excepthook
            # Need to reset existing handlers, or we repeat messages
            logHandler = logging.StreamHandler()
            formatter = jsonlogger.JsonFormatter()
            logHandler.setFormatter(formatter)
            self.log.handlers = []
            self.log.addHandler(logHandler)
            self.log.setLevel(logging.INFO)
        else:
            # due to json logger stuff above,
            # our log messages include carriage returns, newlines, etc.
            # remove the additional newline from the stream handler
            self.log.handlers[0].terminator = ''
            # We don't want a [Repo2Docker] on all messages
            self.log.handlers[0].formatter = logging.Formatter(
                fmt='%(message)s')

        if args.image_name:
            self.output_image_spec = args.image_name
        else:
            # Attempt to set a sane default!
            # HACK: Provide something more descriptive?
            self.output_image_spec = 'r2d' + escapism.escape(
                self.repo, escape_char='-').lower() + str(int(time.time()))

        self.push = args.push
        self.run = args.run
        self.json_logs = args.json_logs

        self.build = args.build
        if not self.build:
            # Can't push nor run if we aren't building
            self.run = False
            self.push = False

        self.run_cmd = args.cmd
Example #29
0
def safe_id(id):
    """
    Make sure meeting-ids are safe

    We try to keep meeting IDs to a safe subset of characters.
    Not sure if Jitsi requires this, but I think it goes on some
    URLs so easier to be safe.
    """
    return escape(id, safe=string.ascii_letters + string.digits + '-')
Example #30
0
    def get_pod_manifest(self):
        """
        Make a pod manifest that will spawn current user's notebook pod.
        """
        if callable(self.singleuser_uid):
            singleuser_uid = yield gen.maybe_future(self.singleuser_uid(self))
        else:
            singleuser_uid = self.singleuser_uid

        if callable(self.singleuser_fs_gid):
            singleuser_fs_gid = yield gen.maybe_future(
                self.singleuser_fs_gid(self))
        else:
            singleuser_fs_gid = self.singleuser_fs_gid

        if self.cmd:
            real_cmd = self.cmd + self.get_args()
        else:
            real_cmd = None

        # Default set of labels, picked up from
        # https://github.com/kubernetes/helm/blob/master/docs/chart_best_practices/labels.md
        labels = {
            'heritage': 'jupyterhub',
            'component': 'singleuser-server',
            'app': 'jupyterhub',
            'hub.jupyter.org/username': escapism.escape(self.user.name)
        }

        if self.name:
            # FIXME: Make sure this is dns safe?
            labels['hub.jupyter.org/servername'] = self.name

        labels.update(self._expand_all(self.singleuser_extra_labels))

        return make_pod(name=self.pod_name,
                        cmd=real_cmd,
                        port=self.port,
                        image_spec=self.singleuser_image_spec,
                        image_pull_policy=self.singleuser_image_pull_policy,
                        image_pull_secret=self.singleuser_image_pull_secrets,
                        node_selector=self.singleuser_node_selector,
                        run_as_uid=singleuser_uid,
                        fs_gid=singleuser_fs_gid,
                        run_privileged=self.singleuser_privileged,
                        env=self.get_env(),
                        volumes=self._expand_all(self.volumes),
                        volume_mounts=self._expand_all(self.volume_mounts),
                        working_dir=self.singleuser_working_dir,
                        labels=labels,
                        cpu_limit=self.cpu_limit,
                        cpu_guarantee=self.cpu_guarantee,
                        mem_limit=self.mem_limit,
                        mem_guarantee=self.mem_guarantee,
                        lifecycle_hooks=self.singleuser_lifecycle_hooks,
                        init_containers=self.singleuser_init_containers,
                        service_account=self.singleuser_service_account)
Example #31
0
def test_escape_custom_safe():
    safe = 'ABCDEFabcdef0123456789'
    escape_char = '\\'
    safe_set = set(safe + '\\')
    for s in test_strings:
        e = escape(s, safe=safe, escape_char=escape_char)
        assert all(c in safe_set for c in e)
        u = unescape(e, escape_char=escape_char)
        assert u == s
Example #32
0
 def escaped_name(self):
     """Escape the username so it's safe for docker objects"""
     if self._escaped_name is None:
         self._escaped_name = escape(
             self.user.name,
             safe=self._docker_safe_chars,
             escape_char=self._docker_escape_char,
         ).lower()
     return self._escaped_name
def _generate_container_name(realm, user_name, mapping_id):
    """Generates a proper name for the container.
    It combines the prefix, username and image name after escaping.

    Parameters
    ----------
    realm : string
        The docker realm
    user_name: string
        the user name
    mapping_id: string
        the mapping id

    Return
    ------
    A string combining the three parameters in an appropriate container name,
    plus a random token to prevent collisions with a similarly named rogue
    container.

    NOTE: the container name is not meant for parsing. It's only for human
    consumption in the docker list. All information and all searching should
    be extracted from labels.
    """
    escaped_realm = escape(realm,
                           safe=_CONTAINER_SAFE_CHARS,
                           escape_char=_CONTAINER_ESCAPE_CHAR)
    escaped_user_name = escape(user_name,
                               safe=_CONTAINER_SAFE_CHARS,
                               escape_char=_CONTAINER_ESCAPE_CHAR)
    escaped_mapping_id = escape(mapping_id,
                                safe=_CONTAINER_SAFE_CHARS,
                                escape_char=_CONTAINER_ESCAPE_CHAR)
    random_token = ''.join(random.choice(string.ascii_lowercase)
                           for _ in range(10))

    return "{}-{}-{}-{}".format(escaped_realm,
                                escaped_user_name,
                                escaped_mapping_id,
                                random_token
                                )
 def safe_name_for_routespec(self, routespec):
     safe_chars = set(string.ascii_lowercase + string.digits)
     safe_name = generate_hashed_slug(
         'jupyter-' + escapism.escape(routespec, safe=safe_chars, escape_char='-') + '-route'
     )
     return safe_name
Example #35
0
def escape(s):
    """Trivial escaping wrapper for well established stuff.
    Works for containers, file names. Note that it is not destructive,
    so it won't generate collisions."""
    return escapism.escape(s, _ESCAPE_SAFE_CHARS, _ESCAPE_CHAR)