-
Notifications
You must be signed in to change notification settings - Fork 401
Add AWS ECR support (round two) #1055
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
base: main
Are you sure you want to change the base?
Changes from 16 commits
7c74f8a
81f92ca
7d6f775
b0b438c
6cb5b6a
884af25
e25e597
56dd9cf
bd961a8
af56e93
296772b
ade3a8b
c1c37dd
b9ecea4
f9fd86c
a5edcd3
a9aa576
468bffc
1047f9e
88e4914
8309e97
c6939fe
019a9fe
00048e3
a74d814
dddecf8
a729986
95a2f96
9a4ad32
dfcb921
a2a670a
25faed2
60c8ed4
6e6a61c
b9877a7
a5f21e8
54c1b64
6266cdd
dc676c4
5a4894a
eeb7661
ad97b5a
e7624d9
378f539
762443e
40ddeeb
42a28bf
7cf26bf
d9a983f
482cdb9
591be68
c2b3f6e
56f9e81
6fbc789
8a25e45
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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" | ||
|
|
@@ -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) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is boto3 async?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not officially. There are third-party wrappers like aioboto3 - https://github.com/terrycain/aioboto3 - but we may not want to go there.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BinderHub is written using tornado so we can't make calls over the network with a library which blocks. If we do then all of the BinderHub process will block while that network request is happening. We either need to use a threadpool to execute the boto3 calls or use a async library that you can Depending on the complexity of the requests you are making another option would be to implement the HTTP call yourself using the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see. There is really only one request that needs to be made currently and it's relatively simple to do ourselves. Unless you see a value in integrating something like If you approve of this path, I will start work on implementing and testing it.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good to me.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After biting into AWS's request signing process - ref: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html - I realized heading down the request reimplementation may not be the best approach. Additionally, the kubernetes call is also not async, so I switched to using a threadpool as you suggested. It has now been implemented and tested. |
||
|
|
||
| # TODO: cache auth if not expired - authorizationData[i]["expiresAt"] | ||
| def _get_ecr_auth(self): | ||
| auths = self.ecr_client.get_authorization_token()["authorizationData"] | ||
| auth = next(x for x in auths if x["proxyEndpoint"] == self.url) | ||
| self._patch_docker_config_secret(auth) | ||
betatim marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return auth | ||
|
|
||
| username = "AWS" | ||
|
|
||
| @property | ||
| def password(self): | ||
| # Fetch password every time as ECR password expires | ||
| auth = self._get_ecr_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("ECR repo {} created".format(repo_name)) | ||
| except self.ecr_client.exceptions.RepositoryAlreadyExistsException: | ||
| self.log.info("ECR repo {} already exists".format(repo_name)) | ||
| return await super().get_image_manifest(repo_name, tag) | ||
|
|
||
| kube_client = Any() | ||
|
|
||
| @default("kube_client") | ||
| def _get_kube_client(self): | ||
| kubernetes.config.load_incluster_config() | ||
| return kubernetes.client.CoreV1Api() | ||
|
|
||
| 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}} | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,3 +11,4 @@ python-json-logger | |
| jupyterhub | ||
| jsonschema | ||
| pycurl | ||
| boto3 | ||
Uh oh!
There was an error while loading. Please reload this page.