Skip to content
views.py 48.5 KiB
Newer Older
import requests
from django.conf import settings
from django.shortcuts import render
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
from django.contrib.auth.models import User
from django.db.models import Q
from .models import Profile, LoginToken, Task, TaskStatuses, Container, Computing, KeyPair, Page
from .utils import send_email, format_exception, timezonize, os_shell, booleanize, get_rosetta_tasks_tunnel_host
from .utils import get_rosetta_tasks_proxy_host, random_username, setup_tunnel_and_proxy, finalize_user_creation
from .utils import sanitize_container_env_vars, get_or_create_container_from_repository
from .decorators import public_view, private_view
from .exceptions import ErrorMessage

# Setup logging
import logging
logger = logging.getLogger(__name__)

#====================
#  Page views
#====================

@public_view
def login_view(request):
    
    # Set post login page
    post_login_page = request.COOKIES.get('post_login_redirect')
    if post_login_page is None:
        post_login_page = '/main'

    # If authenticated user reloads the main URL
    if request.method == 'GET' and request.user.is_authenticated:
        response = HttpResponseRedirect(post_login_page)
        response.delete_cookie('post_login_redirect')
        return response
        # If local auth disabled, just redirect to OIDC
            return HttpResponseRedirect('/oidc/authenticate/')
    # If unauthenticated user tries to log in
    if request.method == 'POST':
        if not request.user.is_authenticated:
            username = request.POST.get('username')
            password = request.POST.get('password')
            # Use Django's machinery to attempt to see if the username/password
            # combination is valid - a User object is returned if it is.
            if "@" in username:
                # Get the username from the email
                try:
                    user = User.objects.get(email=username)
                    username = user.username
                except User.DoesNotExist:
                    if password:
                        raise ErrorMessage('Check email and password')
                    else:
                        # Return here, we don't want to give any hints about existing users
                        data['success'] = 'Ok, if we have your data you will receive a login link by email shortly.'
                        return render(request, 'success.html', {'data': data})
                if user.profile.auth != 'local':
                    # This actually hides that the user cannot be authenticated using the local auth.
                    raise ErrorMessage('Check email and password')
                user = authenticate(username=username, password=password)
                if user:
                    login(request, user)
                    response = HttpResponseRedirect(post_login_page)
                    response.delete_cookie('post_login_redirect')
                    return response
                else:
                    raise ErrorMessage('Check email and password')
            else:
                # If empty password and local auth, send mail with login token
                if user.profile.auth == 'local':
                    logger.debug('Sending login token via mail to {}'.format(user.email))
    
                    token = uuid.uuid4()
    
                    # Create token or update if existent (and never used)
                    try:
                        loginToken = LoginToken.objects.get(user=user)
                    except LoginToken.DoesNotExist:
                        LoginToken.objects.create(user=user, token=token)
                    else:
                        loginToken.token = token
                        loginToken.save()
                    try:
                        send_email(to=user.email, subject='Rosetta login link', text='Hello,\n\nhere is your login link: https://{}/login/?token={}\n\nOnce logged in, you can go to "My Account" and change password (or just keep using the login link feature).\n\nThe Rosetta Team.'.format(settings.ROSETTA_HOST, token))
                    except Exception as e:
                        logger.error(format_exception(e))
                        raise ErrorMessage('Something went wrong. Please retry later.')
    
                    # Return here, we don't want to give any hints about existing users
                    data['success'] = 'Ok, if we have your data you will receive a login link by email shortly.'
                    return render(request, 'success.html', {'data': data})
            # This should never happen.
            # User tried to log-in while already logged in: log him out and then render the login
    else:
        # If we are logging in through a token
        token = request.GET.get('token', None)
            loginTokens = LoginToken.objects.filter(token=token)
            if not loginTokens:
                raise ErrorMessage('Token not valid or expired')
            if len(loginTokens) > 1:
                raise Exception('Consistency error: more than one user with the same login token ({})'.format(len(loginTokens)))
            # Use the first and only token (todo: use the objects.get and correctly handle its exceptions)
            loginToken = loginTokens[0]
            # Get the user from the table
            user = loginToken.user
            # Set auth backend
            user.backend = 'django.contrib.auth.backends.ModelBackend'
            # Ok, log in the user
            login(request, user)
            loginToken.delete()
            response = HttpResponseRedirect(post_login_page)
            response.delete_cookie('post_login_redirect')
            return response
    # All other cases, render the login page again with no other data than title
    return render(request, 'login.html', {'data': data})
def logout_view(request):
    logout(request)
    return HttpResponseRedirect('/')


@public_view
def register_view(request):

    data = {}

    # If authenticated user reloads the main URL
    if request.method == 'GET' and request.user.is_authenticated:
        return HttpResponseRedirect('/main/')

    # If unauthenticated register if post
    if request.method == 'POST':
        if not request.user.is_authenticated:
            email    = request.POST.get('email')
            password = request.POST.get('password')
            invitation = request.POST.get('invitation')
            
            if settings.INVITATION_CODE:
                if invitation != settings.INVITATION_CODE:
                    raise ErrorMessage('Wrong invitation code')

            if '@' not in email:
                raise ErrorMessage('Detected invalid email address')
            
            # Register the user
            user = User.objects.create_user(random_username(), password=password, email=email)

            # Is this necessary?
            user.save()
            
            data['user'] = user


            # Manually set the auth backend for the user
            user.backend = 'django.contrib.auth.backends.ModelBackend'
            login(request, user)
            
            data['status'] = 'activated'

    # All other cases, render the login page again with no other data than title
    return render(request, 'register.html', {'data': data})



@public_view
def entrypoint(request):
        page = Page.objects.get(id='main')
        data['page'] = page
    except Page.DoesNotExist:
    return render(request, 'main.html', {'data': data})


@public_view
def page_view(request, page_id):

    # Init data
    data = {}
    
    # Get the page
    try:
        page = Page.objects.get(id=page_id)
        data['page'] = page
    except Page.DoesNotExist:
        return HttpResponseNotFound('Page not found')

    return render(request, 'page.html', {'data': data})



#====================
# Account view
#====================

@private_view
def account(request):

    data={}
    data['user'] = request.user
    try:
        profile = Profile.objects.get(user=request.user)
    except Profile.DoesNotExist:
        profile = Profile.objects.create(user=request.user)
    data['profile'] = profile

    # Set values from POST and GET
    edit = request.POST.get('edit', None)
    if not edit:
        edit = request.GET.get('edit', None)
        data['edit'] = edit
    value = request.POST.get('value', None)
    # Fix None
    if value and value.upper() == 'NONE':
        value = None
    if edit and edit.upper() == 'NONE':
        edit = None
    # Set data.default_public_key
    with open(KeyPair.objects.get(user=request.user, default=True).public_key_file) as f:
        data['default_public_key'] = f.read()

    # Add computings (for extra confs)
    if request.user.profile.extra_confs:
        data['computings'] = list(Computing.objects.filter(group=None)) + list(Computing.objects.filter(group__user=request.user))

    # Edit values
    if edit and value:
        try:
            logger.info('Setting "{}" to "{}"'.format(edit,value))
            # Timezone
            if edit=='timezone' and value:
                # Validate
                timezonize(value)
                profile.timezone = value
                profile.save()
            # Email
            elif edit=='email' and value:
                # If no local auth, you should never get here
                if request.user.profile.auth != 'local':
                    raise ErrorMessage('Cannot change password using an external authentication service')
                request.user.email=value
                request.user.save()
            # Password
            elif edit=='password' and value:
                # If no local auth, you should never get here
                if request.user.profile.auth != 'local':
                    raise ErrorMessage('Cannot change password using an external authentication service')
                request.user.set_password(value)
                request.user.save()
            # Generic property
            elif edit and value:
                raise Exception('Attribute to change is not valid')
        except Exception as e:
            logger.error(format_exception(e))
            data['error'] = 'The property "{}" does not exists or the value "{}" is not valid.'.format(edit, value)
            return render(request, 'error.html', {'data': data})

    # Lastly, do we have to remove an extra conf?
    
    delete_extra_conf_uuid = request.GET.get('delete_extra_conf_uuid', None)
    if delete_extra_conf_uuid:
        #logger.debug('Deleting extra conf "{}"'.format(delete_extra_conf_uuid))
        new_extra_confs = {}
        for extra_conf_uuid in profile.extra_confs:
            if extra_conf_uuid != delete_extra_conf_uuid:
                new_extra_confs[extra_conf_uuid] = profile.extra_confs[extra_conf_uuid]
        profile.extra_confs = new_extra_confs
        profile.save()
        return redirect('/account')
                
            
    
    return render(request, 'account.html', {'data': data})




#=========================
#  Tasks view
#=========================

def set_verified_status(task):
    # Chech status with ping
    if task.status == 'running':
        logger.debug('Task is running, check if startup completed')

        logger.debug('Trying to establish connection on: "{}:{}"'.format(task.interface_ip,task.interface_port))
        s = socket.socket()
        try:
            s.settimeout(1)
            s.connect((task.interface_ip, task.interface_port))
            # Not necessary, we just check that the container interfcae is up
            #if not s.recv(10):
            #    logger.debug('No data read from socket')
            #    raise Exception('Could not read any data from socket')
        except Exception as e:
            logger.debug('Could not connect to socket')
            task.verified_status = 'starting up...'
        else:
            task.verified_status = 'running'
        finally:
            s.close()
    else:
        task.verified_status = task.status

@private_view
def tasks(request):

    # Init data
    data={}
    data['user']  = request.user
    data['profile'] = Profile.objects.get(user=request.user)
    data['title'] = 'Tasks'
    action  = request.GET.get('action', None)
    uuid    = request.GET.get('uuid', None)
    fromlist = request.GET.get('fromlist', False)
        try:
            
            # Get the task (raises if none available including no permission)
            try:
                task = Task.objects.get(user=request.user, uuid=uuid)
            except Task.DoesNotExist:
                raise ErrorMessage('Task does not exists or no access rights')
            if action=='delete':
                if task.status not in [TaskStatuses.stopped, TaskStatuses.exited]:
                    try:
                        task.computing.manager.stop_task(task)
                    except:
                        pass
                try:
                    # Get the task (raises if none available including no permission)
                    task = Task.objects.get(user=request.user, uuid=uuid)

                    # Re-remove proxy files before deleting the task itself just to be sure
                    try:
                        os.remove('/shared/etc_apache2_sites_enabled/{}.conf'.format(task.uuid))
                    except:
                        pass
                    try:
                        os.remove('/shared/etc_apache2_sites_enabled/{}.htpasswd'.format(task.uuid))
                    except:
                        pass
    
                    # Delete
                    task.delete()
                    
                    # Unset task
                    data['task'] = None    
    
                except Exception as e:
                    data['error'] = 'Error in deleting the task'
                    logger.error('Error in deleting task with uuid="{}": "{}"'.format(uuid, e))
                    return render(request, 'error.html', {'data': data})
    
            elif action=='stop': # or delete,a and if delete also remove object
                # Remove proxy files. Do it here or will cause issues when reloading the conf re-using ports of stopped tasks.
                try:
                    os.remove('/shared/etc_apache2_sites_enabled/{}.conf'.format(task.uuid))
                except:
                    pass
                try:
                    os.remove('/shared/etc_apache2_sites_enabled/{}.htpasswd'.format(task.uuid))
                except:
                    pass
                   
                task.computing.manager.stop_task(task)
        except Exception as e:
            data['error'] = 'Error in getting the task or performing the required action'
            logger.error('Error in getting the task with uuid="{}" or performing the required action: "{}"'.format(uuid, e))
            return render(request, 'error.html', {'data': data})
        # Ok, redirect if there was an action
        if action:
            if fromlist:
                return redirect('/tasks')
            else:
                if not task.uuid:
                    # it has just been deleted
                    return redirect('/tasks')
                else:
                    return redirect('/tasks/?uuid={}'.format(task.uuid))

    # Do we have to list all the tasks?
    if not uuid or (uuid and not details):
        try:
            tasks = Task.objects.filter(user=request.user).order_by('created') 
        except Exception as e:
            data['error'] = 'Error in getting Tasks info'
            logger.error('Error in getting Virtual Devices: "{}"'.format(e))
            return render(request, 'error.html', {'data': data})
    
        # Update task statuses
        for task in tasks:
            task.update_status()
        # Set task and tasks variables
        data['task']  = None   
        data['tasks'] = tasks

    return render(request, 'tasks.html', {'data': data})


#=========================
#=========================

@private_view

    # Init data
    data={}
    data['user']    = request.user
    # Get task container helper function
    def get_task_container(request):
        task_container_uuid = request.POST.get('task_container_uuid', None)
        if not task_container_uuid:
            # At the second step the task uuid is set via a GET request 
            task_container_uuid = request.GET.get('task_container_uuid', None)
            task_container = Container.objects.get(uuid=task_container_uuid, user=None)
                task_container =  Container.objects.get(uuid=task_container_uuid, user=request.user)
                raise Exception('Consistency error, container with uuid "{}" does not exists or user "{}" does not have access rights'.format(task_container_uuid, request.user.email))
    # Get task computing helper function
    def get_task_computing(request):
        task_computing_uuid = request.POST.get('task_computing_uuid', None)
            task_computing = Computing.objects.get(uuid=task_computing_uuid, group=None)
                task_computing =  Computing.objects.get(uuid=task_computing_uuid, group__user=request.user)
                raise Exception('Consistency error, computing with uuid "{}" does not exists or user "{}" does not have access rights'.format(task_computing_uuid, request.user.email))        
        return task_computing

    # Get task name helper function
    def get_task_name(request):
        task_name = request.POST.get('task_name', None)
        if not task_name:
            raise ErrorMessage('Missing task name')
        return task_name

    # Get step if any, check both POST and GET
    step = request.POST.get('step', None)
    if not step:
        step = request.GET.get('step', None)
    # Handle the various steps
    if not step:
        
        # Step one is assumed: chose software container
        return HttpResponseRedirect('/software/?mode=new_task')
        data['task_container'] = get_task_container(request)
        # 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'

    elif step == 'three':

        data['task_container'] = get_task_container(request)

        # Get computing resource
        data['task_computing'] = get_task_computing(request)
        # Check that container required architecture is compatible with the computing resource
        # TODO: support setting the container engine/engine when creating the task
Stefano Alberto Russo's avatar
Stefano Alberto Russo committed
        if data['task_computing'].supported_archs is None: data['task_computing'].supported_archs=[]
        if data['task_computing'].emulated_archs is None: data['task_computing'].emulated_archs={}
        data['arch_emulation'] = False
Stefano Alberto Russo's avatar
Stefano Alberto Russo committed
        
            if (data['task_container'].image_arch != data['task_computing'].arch) and (data['task_container'].image_arch not in data['task_computing'].supported_archs):

                # Does container engines/engines support emulated archs?
                                        
                    # For now by default our container engine is the first one
                    container_engine = data['task_computing'].container_engines[0]
                    # Check for emulation against the engine
                    if container_engine in data['task_computing'].emulated_archs and data['task_container'].image_arch in data['task_computing'].emulated_archs[container_engine]:

                    # Check for emulation against the engine
                    def get_engines(container_engine):
                        if not '[' in container_engine:
                            return None
                        else:
                            container_engines = container_engine.split('[')[1].replace(']','').split(',')
                            return container_engines
                    for container_engine in get_engines(container_engine):
                        if container_engine in data['task_computing'].emulated_archs and data['task_container'].image_arch in data['task_computing'].emulated_archs[container_engine]:
                            data['arch_emulation'] = True

                    if not data['arch_emulation']:
                        raise ErrorMessage('This computing resource does not support architecture \'{}\' nor as native or emulated'.format(data['task_container'].image_arch))
                
                    raise ErrorMessage('This computing resource does not support architecture \'{}\' nor as native or emulated'.format(data['task_container'].image_arch))
            data['arch_auto_selection'] = True
            #raise ErrorMessage('Auto selecting architectures is not supported yet')

        # Generate random auth token        
        data['task_auth_token'] = str(uuid.uuid4())

        # Set current and next step
        data['step'] = 'three'
        data['next_step'] = 'last'


    elif step == 'last':

        data['task_container'] = get_task_container(request)

        # Get computing resource
        data['task_computing'] = get_task_computing(request)

        # Get task name
        data['task_name'] = get_task_name(request)

        # Generate the task uuid
        task_uuid = str(uuid.uuid4())

        # Create the task object
        task = Task(uuid      = task_uuid,
                    user      = request.user,
                    name      = data['task_name'],
                    status    = TaskStatuses.created,
                    container = data['task_container'],
                    computing = data['task_computing'])

        # Add auth
        task_auth_password = request.POST.get('task_auth_password', None)
        if task_auth_password and not request.user.profile.is_power_user:
            raise ErrorMessage('Sorry, only power users can set a custom task password.')
        task_auth_token = request.POST.get('task_auth_token', None)
        if task_auth_password:
            if task_auth_password != task_auth_token: # Just an extra check probably not much useful
                if not task_auth_password:
                    raise ErrorMessage('No task password set')
                if len(task_auth_password) < 6:
                    raise ErrorMessage('Task password must be at least 6 chars')
                task.password = task_auth_password # Not stored in the ORM model, just a temporary var.
        else:
            task.auth_token = task_auth_token # This is saved on the ORM model
            task.password = task_auth_token # Not stored

        # Any task requires the TCP tunnel for now
        task.requires_tcp_tunnel = True
        access_method = request.POST.get('access_method', 'auto')
        if access_method and access_method != 'auto' and not request.user.profile.is_power_user:
            raise ErrorMessage('Sorry, only power users can set a task access method other than \'auto\'.')
        if access_method == 'auto':
            if task.container.interface_protocol in ['http','https']:
                task.requires_proxy      = True
                task.requires_proxy_auth = True
            else:
                task.requires_proxy      = False
                task.requires_proxy_auth = False                
        elif access_method == 'direct_tunnel':
            task.requires_proxy      = False
            task.requires_proxy_auth = False            
        elif access_method == 'https_proxy':
            task.requires_proxy      = True
            task.requires_proxy_auth = True
        else:
            raise ErrorMessage('Unknown access method "{}"'.format(access_method))
        # Container engine if any set
        container_engine = request.POST.get('container_engine', None)
        if container_engine:
            if not container_engine in data['task_computing'].container_engines:
                raise ErrorMessage('Unknown container engine "{}"'.format(container_engine))
            computing_options['container_engine'] = container_engine
        computing_cpus = request.POST.get('computing_cpus', None)
        computing_memory = request.POST.get('computing_memory', None)
        computing_partition = request.POST.get('computing_partition', None)
        
        if computing_cpus:
                raise Exception('Cannot convert computing_cpus to int')
            computing_options['cpus'] = int(computing_cpus)
        if computing_memory:
            computing_options['memory'] = computing_memory
        if computing_partition:
            computing_options['partition'] = computing_partition        
        if computing_options:
            task.computing_options = computing_options
                    
        # Save the task before starting it, or the computing manager will not be able to work properly
        task.save()

        # Start the task
        try:
            task.computing.manager.start_task(task)
        except:
            # Delete the task if could not start it
            task.delete()
            
            # ..and re-raise
            raise

        # Ensure proxy conf directory exists
        if not os.path.exists('/shared/etc_apache2_sites_enabled'):
            os.makedirs('/shared/etc_apache2_sites_enabled')
    
        # Add here proxy auth file as we have the password
        if task.requires_proxy_auth:
            out = os_shell('ssh -o StrictHostKeyChecking=no proxy "cd /shared/etc_apache2_sites_enabled/ && htpasswd -bc {}.htpasswd {} {}"'.format(task.uuid, task.user.email, task.password), capture=True)
            if out.exit_code != 0:
                logger.error(out.stderr)
                raise ErrorMessage('Something went wrong when enabling proxy auth')   

    return render(request, 'new_task.html', {'data': data})
#=========================
#  Task log
#=========================

@private_view
def task_log(request):

    # Init data
    data={}
    data['user']  = request.user
    data['profile'] = Profile.objects.get(user=request.user)
    data['title'] = 'Tasks'

    # Get uuid and refresh if any
    uuid    = request.GET.get('uuid', None)
    refresh = request.GET.get('refresh', None)

    if not uuid:
        return render(request, 'error.html', {'data': 'uuid not set'})

    # Get the task (raises if none available including no permission)
    task = Task.objects.get(user=request.user, uuid=uuid)

    # Set back task and refresh
    data['task']    = task 
    data['refresh'] = refresh

    # Get the log
    try:

        data['log'] = task.computing.manager.get_task_log(task)

    except Exception as e:
        data['error'] = 'Error in viewing task log'
        logger.error('Error in viewing task log with uuid="{}": "{}"'.format(uuid, e))

    # Init data
    data={}
    data['user']    = request.user
    data['profile'] = Profile.objects.get(user=request.user)
    container_uuid = request.GET.get('container_uuid', None)
    container_family_id = request.GET.get('container_family_id', None)
    action = request.GET.get('action', None)
    details = booleanize(request.GET.get('details', False))

    # Get filter/search if any
    search_text   = request.POST.get('search_text', '')
    search_owner  = request.POST.get('search_owner', 'All')
    data['search_owner'] = search_owner
    data['search_text']  = search_text
    # Are we using this page as first step of a new task?
    data['mode'] = request.GET.get('mode', None)
    if not data['mode']:
        data['mode'] = request.POST.get('mode', None)

    # Do we have to operate on a specific container, or family of containers?
    if container_uuid:
            # Get the container (raises if none available including no permission)
            try:
                container = Container.objects.get(uuid=container_uuid)
            except Container.DoesNotExist:
                raise ErrorMessage('Container does not exists or no access rights')                
            if container.user and container.user != request.user:
                raise ErrorMessage('Container does not exists or no access rights')
                return HttpResponseRedirect('/software')
            data['error'] = 'Error in getting the software container or performing the required action'
            logger.error('Error in getting container with uuid="{}" or performing the required action: "{}"'.format(uuid, e))
            return render(request, 'error.html', {'data': data})
    else:
        # Ddo we have to operate on a container family?
        if container_family_id:
            # Get back name, registry and image from contsainer url
            container_name, container_registry, container_image_name = base64.b64decode(container_family_id.encode('utf8')).decode('utf8').split('\t')
          
            # get containers from the DB
            user_containers = Container.objects.filter(user=request.user, name=container_name, registry=container_registry, image_name=container_image_name)
            platform_containers = Container.objects.filter(user=None, name=container_name, registry=container_registry, image_name=container_image_name)
        else:    
            
            # Get containers (fitered by search term, or all)
            if search_text:
                search_query=(Q(name__icontains=search_text) | Q(description__icontains=search_text) | Q(image_name__icontains=search_text))
                user_containers = Container.objects.filter(search_query, user=request.user)
                platform_containers = Container.objects.filter(search_query, user=None)
            else:
                user_containers = Container.objects.filter(user=request.user)
                platform_containers = Container.objects.filter(user=None)
        # Ok, nilter by owner
        if search_owner != 'All':
            if search_owner == 'User':
                platform_containers =[]
            if search_owner == 'Platform':
                user_containers = []
        # Create all container list
        data['containers'] = list(user_containers) + list(platform_containers)
            
        # Merge containers with the same name, registry and image name
        data['container_families'] = {}
        # Container family support class
        class ContainerFamily(object):
            def __init__(self, id, name, registry, image_name):
                self.id = id
                self.name = name
                self.registry = registry
                self.image_name = image_name
                self.description = None
                self.members = []
                self.all_archs = []
                self.container_by_tags_by_arch = {} 
    
            def add(self, container):
                self.members.append(container)
    
                if not self.description:
                    self.description = container.description
    
                if not container.image_arch in self.all_archs:
                    self.all_archs.append(container.image_arch)
    
                if not container.image_arch in self.container_by_tags_by_arch:
                    self.container_by_tags_by_arch[container.image_arch]={}
                self.container_by_tags_by_arch[container.image_arch][container.image_tag] = container
                
                # Lastly, add the container to the "all tags"
                #if None not in self.container_by_tags_by_arch:
                #    self.container_by_tags_by_arch[None]={}
                #self.container_by_tags_by_arch[None][container.image_tag] = container
    
            @ property
            def color(self):
                try:
                    return self.members[0].color
                except IndexError:
                    return '#000000'
            
        # Populate container families
        for container in data['containers']:
            if container.family_id not in data['container_families']:
                data['container_families'][container.family_id] = ContainerFamily(container.family_id, container.name, container.registry, container.image_name)
            data['container_families'][container.family_id].add(container)
        # Finalize the families
        #for container.family_id in data['container_families']:
        #    if len(data['container_families'][container.family_id].all_archs) == 1:
        #        if data['container_families'][container.family_id].all_archs[0] != None:
        #            data['container_families'][container.family_id].container_by_tags_by_arch.pop(None)
                
    return render(request, 'software.html', {'data': data})
def add_software(request):
    data = {}
    data['user'] = request.user
    
    # Loop back the new container mode in the page to handle the switch
    data['new_container_from'] = request.GET.get('new_container_from', 'registry')
    # Container name if setting up a new container
    container_name = request.POST.get('container_name', None)
        # How do we have to add this new container?
        new_container_from = request.POST.get('new_container_from', None)
        if new_container_from == 'registry':
    
            # Container description
            container_description = request.POST.get('container_description', None)
    
            # Container registry
            container_registry = request.POST.get('container_registry', None)
    
            # Container image name
            container_image_name = request.POST.get('container_image_name',None)
            
            # Container image tag
            container_image_tag = request.POST.get('container_image_tag', None)
    
            # Container image architecture
            container_image_arch = request.POST.get('container_image_arch', None)
    
            # Container image OS 
            container_image_os = request.POST.get('container_image_os', None)
    
            # Container image digest
            container_image_digest = request.POST.get('container_image_digest', None)
    
            # Container interface port
            container_interface_port = request.POST.get('container_interface_port', None) 
            if container_interface_port:       
                try:
                    container_interface_port = int(container_interface_port)
                except:
                    raise ErrorMessage('Invalid container port "{}"')
            else:
                container_interface_port = None
    
            # Container interface protocol 
            container_interface_protocol = request.POST.get('container_interface_protocol', None)
    
            if container_interface_protocol and not container_interface_protocol in ['http','https']:
                raise ErrorMessage('Sorry, only power users can add custom software containers with interface protocols other than \'http\' or \'https\'.')
    
            # Container interface transport 
            container_interface_transport = request.POST.get('container_interface_transport')
    
            # Capabilities
            container_supports_custom_interface_port = request.POST.get('container_supports_custom_interface_port', None)
            if container_supports_custom_interface_port and container_supports_custom_interface_port == 'True':
                container_supports_custom_interface_port = True
            else:
                container_supports_custom_interface_port = False