From 4b335dfd94ea00e38b49f9e9f433c1852217c0c7 Mon Sep 17 00:00:00 2001
From: Stefano Alberto Russo <stefano.russo@gmail.com>
Date: Tue, 5 Apr 2022 00:00:08 +0200
Subject: [PATCH] Added compatibility with Binder.

---
 docker-compose-dev.yml                        |   2 +-
 .../webapp/code/rosetta/core_app/views.py     | 120 ++++++++++++++++++
 services/webapp/code/rosetta/urls.py          |   4 +
 services/webapp/requirements.txt              |   1 +
 4 files changed, 126 insertions(+), 1 deletion(-)

diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml
index 0134366..a723753 100644
--- a/docker-compose-dev.yml
+++ b/docker-compose-dev.yml
@@ -68,7 +68,7 @@ services:
       - TASK_PROXY_HOST=localhost
       - TASK_TUNNEL_HOST=localhost
       - ROSETTA_HOST=localhost
-      - REGISTRY_HOST=proxy     # Use same value as ROSETTA_HOST for production or to use "real" computing resurces 
+      - REGISTRY_HOST=proxy:5000     # Use same value as ROSETTA_HOST for production or to use "real" computing resurces 
     ports:
       - "8080:8080"
       - "7000-7020:7000-7020"
diff --git a/services/webapp/code/rosetta/core_app/views.py b/services/webapp/code/rosetta/core_app/views.py
index 0f53b84..ea90213 100644
--- a/services/webapp/code/rosetta/core_app/views.py
+++ b/services/webapp/code/rosetta/core_app/views.py
@@ -1,6 +1,7 @@
 import os
 import uuid
 import json
+import requests
 import subprocess
 import base64
 from django.conf import settings
@@ -1166,13 +1167,132 @@ def sharable_link_handler(request, short_uuid):
     return redirect(redirect_string)
 
 
+def get_or_create_container_from_repository(repository_url, repository_tag=None): 
+    repository_name = '{}/{}'.format(repository_url.split('/')[-2],repository_url.split('/')[-1])
+    
+    logger.debug('Called get_or_create_container_from_repository with repository_url="{}" and repository_tag="{}"'.format(repository_url,repository_tag))
+
+    # If building:
+    #{"message": "Successfully built 5a2089b2c334\n", "phase": "building"}
+    #{"message": "Successfully tagged r2dhttps-3a-2f-2fgithub-2ecom-2fnorvig-2fpytudes5e745c3:latest\n", "phase": "building"}
+    
+    # If reusing:
+    #{"message": "Reusing existing image (r2dhttps-3a-2f-2fgithub-2ecom-2fnorvig-2fpytudes5e745c3), not building."}
+    
+    # Build the Docker container for this repo
+    if repository_tag:
+        command = 'sudo jupyter-repo2docker --ref {} --user-id 1000 --user-name rosetta --no-run --json-logs {}'.format(repository_tag, repository_url)
+    else:
+        command = 'sudo jupyter-repo2docker --user-id 1000 --user-name rosetta --no-run --json-logs {}'.format(repository_url)
+    out = os_shell(command, capture=True)
+    if out.exit_code != 0:
+        logger.error(out.stderr)
+        raise ErrorMessage('Something went wrong when creating the Dockerfile for repository "{}"'.format(repository_url))   
+
+    # Convert output to lines
+    out_lines = out.stderr.split('\n')
+
+    # Get rep2docker image name from output
+    last_line_message = json.loads(out_lines[-1])['message']
+    if 'Reusing existing image' in last_line_message:
+        repo2docker_image_name = last_line_message.split('(')[1].split(')')[0]
+    elif 'Successfully tagged' in last_line_message:
+        repo2docker_image_name = last_line_message.split(' ')[2]                
+    else:
+        raise Exception('Cannot build')
+
+    # Set image registry, name and tag, Use "strip()" as sometimes the newline chars might jump in.
+    registry = os.environ.get('REGISTRY_HOST','proxy:5000').strip()
+    image_name = repository_name.lower().strip()
+    image_tag = repo2docker_image_name[-7:].strip() # The last part of the image name generated by repo2docker is the git short hash
+
+    # Re-tag image taking into account that if we are using the proxy as registry we use localhost or it won't work
+    if registry == 'proxy:5000':
+        push_registry = 'localhost:5000'
+    else:
+        push_registry = registry
+        
+    out = os_shell('sudo docker tag {} {}/{}:{}'.format(repo2docker_image_name,push_registry,image_name,image_tag) , capture=True)
+    if out.exit_code != 0:
+        logger.error(out.stderr)
+        raise ErrorMessage('Something went wrong when tagging the container for repository "{}"'.format(repository_url))   
+
+    # Push image to the (local) registry
+    out = os_shell('sudo docker push {}/{}:{}'.format(push_registry,image_name,image_tag) , capture=True)
+    if out.exit_code != 0:
+        logger.error(out.stderr)
+        raise ErrorMessage('Something went wrong when pushing the container for repository "{}"'.format(repository_url))   
+
+    # Create the container if not already existent
+    try:
+        container = Container.objects.get(registry=registry, image_name=image_name, image_tag=image_tag)
+    except Container.DoesNotExist:
+
+        # Set default container name and description
+        container_name = repository_name
+        container_description = 'Built from {}'.format(repository_url)
+            
+        # Get name repo name and description from GitHub (if repo is there)
+        if repository_url.startswith('https://github.com'):
+            try:
+                response = requests.get('https://api.github.com/repos/{}'.format(repository_name))
+                json_content = json.loads(response.content)
+                container_name = json_content['name'].title()
+                container_description = json_content['description']
+                if not container_description.endswith('.'):
+                    container_description+='.'
+                container_description += ' Built from {}'.format(repository_url)
+            except:
+                pass
+
+        container = Container.objects.create(user = None,
+                                             name = container_name,
+                                             description = container_description,
+                                             registry = registry,
+                                             image_name = image_name,
+                                             image_tag  = image_tag,
+                                             image_arch = 'amd64',
+                                             image_os = 'linux',
+                                             interface_port = '8888',
+                                             interface_protocol = 'http',
+                                             interface_transport = 'tcp/ip',
+                                             supports_custom_interface_port = False,
+                                             supports_interface_auth = False)
+    return container
 
 
 
+#=========================
+#  New Binder Task
+#=========================
+
+@private_view
+def new_binder_task(request, repository):
+
+    # Init data
+    data={}
+    data['user']  = request.user
 
+    # Convert the Git repository as a Docker container
+    logger.debug('Got a new Binder task request for repository "%s"', repository)
+        
+    # Set repository name/tag/url        
+    repository_tag = repository.split('/')[-1]
+    repository_url = repository.replace('/'+repository_tag, '')
 
+    container = get_or_create_container_from_repository(repository_url, repository_tag)
+    
+    # Set the container
+    data['task_container'] = container
 
+    # List all computing resources 
+    data['computings'] = list(Computing.objects.filter(group=None)) + list(Computing.objects.filter(group__user=request.user))
+        
+    data['step'] = 'two'
+    data['next_step'] = 'three'
 
+    # Render the new task page and handle the rest from there
+    return render(request, 'new_task.html', {'data': data})
 
 
 
diff --git a/services/webapp/code/rosetta/urls.py b/services/webapp/code/rosetta/urls.py
index 88b14fd..ed69eb3 100644
--- a/services/webapp/code/rosetta/urls.py
+++ b/services/webapp/code/rosetta/urls.py
@@ -85,6 +85,10 @@ urlpatterns = [
     path('api/v1/base/agent/', core_app_api.agent_api.as_view(), name='agent_api'),
     path('api/v1/filemanager/', core_app_api.FileManagerAPI.as_view(), name='filemanager_api'),
 
+    # Binder compatibility
+    path('v2/git/<path:repository>', core_app_views.new_binder_task),
+
+
 ]
 
 
diff --git a/services/webapp/requirements.txt b/services/webapp/requirements.txt
index ab0fd71..5f8fcb2 100644
--- a/services/webapp/requirements.txt
+++ b/services/webapp/requirements.txt
@@ -8,3 +8,4 @@ sendgrid==5.3.0
 mozilla-django-oidc==1.2.4
 uwsgi==2.0.19.1
 python-magic==0.4.15
+jupyter-repo2docker==2022.2.0
-- 
GitLab