Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AWS ECR support #920

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions binderhub/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,16 @@
import tornado.log
from tornado.log import app_log
import tornado.web
from traitlets import Unicode, Integer, Bool, Dict, validate, TraitError, default
from traitlets import (
Unicode,
Integer,
Bool,
Dict,
validate,
TraitError,
default,
Type,
)
from traitlets.config import Application
from jupyterhub.services.auth import HubOAuthCallbackHandler

Expand Down Expand Up @@ -196,6 +205,14 @@ def _valid_badge_base_url(self, proposal):
config=True,
)

registry_class = Type(
DockerRegistry,
help="""
Registry class implementation, change to define your own
""",
config=True
)

sticky_builds = Bool(
False,
help="""
Expand Down Expand Up @@ -542,7 +559,7 @@ def initialize(self, *args, **kwargs):
])
jinja_env = Environment(loader=loader, **jinja_options)
if self.use_registry and self.builder_required:
registry = DockerRegistry(parent=self)
registry = self.registry_class(parent=self)
else:
registry = None

Expand Down
63 changes: 62 additions & 1 deletion binderhub/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
import os
from urllib.parse import urlparse

import boto3
import kubernetes.client
import kubernetes.config
from tornado import gen, httpclient
from tornado.httputil import url_concat
from traitlets import default, Dict, Unicode, Any
from traitlets.config import LoggingConfigurable
from traitlets import Dict, Unicode, default

DEFAULT_DOCKER_REGISTRY_URL = "https://registry.hub.docker.com"
DEFAULT_DOCKER_AUTH_URL = "https://index.docker.io/v1"
Expand Down Expand Up @@ -224,3 +227,61 @@ def get_image_manifest(self, image, tag):
raise
else:
return json.loads(resp.body.decode("utf-8"))


class AWSElasticContainerRegistry(DockerRegistry):
aws_region = Unicode(
config=True,
help="""
AWS region for ECR service
""",
)

ecr_client = Any()

@default("ecr_client")
def _get_ecr_client(self):
return boto3.client("ecr", region_name=self.aws_region)

username = "AWS"

kubernetes.config.load_incluster_config()
kube_client = kubernetes.client.CoreV1Api()

def _get_ecr_auth(self):
return self.ecr_client.get_authorization_token()["authorizationData"][0]

@default("url")
def _default_url(self):
return self._get_ecr_auth()["proxyEndpoint"]

def _patch_docker_config_secret(self, auth):
"""Patch binder-push-secret"""
secret_data = {"auths": {self.url: {"auth": auth["authorizationToken"]}}}
secret_data = base64.b64encode(json.dumps(secret_data).encode("utf8")).decode(
"utf8"
)
with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace") as f:
namespace = f.read()
self.kube_client.patch_namespaced_secret(
"binder-push-secret", namespace, {"data": {"config.json": secret_data}}
)

@default("password")
def _get_ecr_pawssord(self):
"""Get ecr password"""
auth = self._get_ecr_auth()
self.password_expires = auth["expiresAt"]
self._patch_docker_config_secret(auth)
return base64.b64decode(auth['authorizationToken']).decode("utf-8").split(':')[1]

async def get_image_manifest(self, image, tag):
try:
repo_name = image.split("/", 1)[1]
self.ecr_client.create_repository(repositoryName=repo_name)
self.log.info("Creating ECR repo {}".format(repo_name))
except self.ecr_client.exceptions.RepositoryAlreadyExistsException:
self.log.info("ECR repo {} already exists".format(repo_name))
# TODO: check for expiration before reseting password
self.password = self._get_ecr_pawssord()
return await super().get_image_manifest(repo_name, tag)
1 change: 1 addition & 0 deletions doc/doc-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ python-json-logger
jupyterhub
jsonschema
#pycurl Do not install for docs as it breaks the RTD build. Its primary use is for mocks in testing .
boto3
4 changes: 4 additions & 0 deletions helm-chart/binderhub/templates/rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ rules:
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
- apiGroups:
- ""
resources: ["secrets"]
verbs: ["get", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ python-json-logger
jupyterhub
jsonschema
pycurl
boto3