Newer
Older
Stefano Alberto Russo
committed
import os
import uuid
Stefano Alberto Russo
committed
import socket
import subprocess
import base64
from django.conf import settings
from django.shortcuts import render
from django.contrib.auth import authenticate, login, logout
Stefano Alberto Russo
committed
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
from django.contrib.auth.models import User
Stefano Alberto Russo
committed
from django.shortcuts import redirect
from django.db.models import Q
Stefano Alberto Russo
committed
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
Stefano Alberto Russo
committed
from .utils import sanitize_container_env_vars, get_or_create_container_from_repository
Stefano Alberto Russo
committed
from .decorators import public_view, private_view
from .exceptions import ErrorMessage
# Setup logging
import logging
logger = logging.getLogger(__name__)
Stefano Alberto Russo
committed
Stefano Alberto Russo
committed
# Task cache
_task_cache = {}
Stefano Alberto Russo
committed
#====================
# Page views
#====================
@public_view
def login_view(request):
Stefano Alberto Russo
committed
data = {}
# 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
Stefano Alberto Russo
committed
else:
# If local auth disabled, just redirect to OIDC
Stefano Alberto Russo
committed
if settings.DISABLE_LOCAL_AUTH:
return HttpResponseRedirect('/oidc/authenticate/')
Stefano Alberto Russo
committed
# 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.
Stefano Alberto Russo
committed
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})
Stefano Alberto Russo
committed
if password:
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:
Stefano Alberto Russo
committed
# If empty password and local auth, send mail with login token
if user.profile.auth == 'local':
Stefano Alberto Russo
committed
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})
Stefano Alberto Russo
committed
# This should never happen.
# User tried to log-in while already logged in: log him out and then render the login
Stefano Alberto Russo
committed
logout(request)
else:
# If we are logging in through a token
token = request.GET.get('token', None)
if token:
Stefano Alberto Russo
committed
loginTokens = LoginToken.objects.filter(token=token)
Stefano Alberto Russo
committed
if not loginTokens:
raise ErrorMessage('Token not valid or expired')
Stefano Alberto Russo
committed
if len(loginTokens) > 1:
raise Exception('Consistency error: more than one user with the same login token ({})'.format(len(loginTokens)))
Stefano Alberto Russo
committed
# Use the first and only token (todo: use the objects.get and correctly handle its exceptions)
loginToken = loginTokens[0]
Stefano Alberto Russo
committed
# Get the user from the table
user = loginToken.user
Stefano Alberto Russo
committed
# Set auth backend
user.backend = 'django.contrib.auth.backends.ModelBackend'
Stefano Alberto Russo
committed
# Ok, log in the user
login(request, user)
loginToken.delete()
Stefano Alberto Russo
committed
# Now redirect to site
response = HttpResponseRedirect(post_login_page)
response.delete_cookie('post_login_redirect')
return response
Stefano Alberto Russo
committed
# All other cases, render the login page again with no other data than title
return render(request, 'login.html', {'data': data})
@private_view
def logout_view(request):
logout(request)
return HttpResponseRedirect('/')
Stefano Alberto Russo
committed
@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')
Stefano Alberto Russo
committed
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
Stefano Alberto Russo
committed
finalize_user_creation(user)
Stefano Alberto Russo
committed
# 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):
Stefano Alberto Russo
committed
return HttpResponseRedirect('/main/')
@public_view
def main_view(request):
Stefano Alberto Russo
committed
# Init data
data = {}
Stefano Alberto Russo
committed
Stefano Alberto Russo
committed
# Get custom home page if any
Stefano Alberto Russo
committed
try:
Stefano Alberto Russo
committed
page = Page.objects.get(id='main')
data['page'] = page
except Page.DoesNotExist:
Stefano Alberto Russo
committed
pass
return render(request, 'main.html', {'data': data})
Stefano Alberto Russo
committed
@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:
Stefano Alberto Russo
committed
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)
Stefano Alberto Russo
committed
# Fix None
if value and value.upper() == 'NONE':
value = None
if edit and edit.upper() == 'NONE':
edit = None
Stefano Alberto Russo
committed
# Set data.default_public_key
Stefano Alberto Russo
committed
with open(KeyPair.objects.get(user=request.user, default=True).public_key_file) as f:
data['default_public_key'] = f.read()
Stefano Alberto Russo
committed
# 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))
Stefano Alberto Russo
committed
# Timezone
if edit=='timezone' and value:
# Validate
timezonize(value)
profile.timezone = value
profile.save()
Stefano Alberto Russo
committed
# 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()
Stefano Alberto Russo
committed
# 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()
Stefano Alberto Russo
committed
# Generic property
elif edit and value:
raise Exception('Attribute to change is not valid')
Stefano Alberto Russo
committed
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})
Stefano Alberto Russo
committed
# 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
#=========================
Stefano Alberto Russo
committed
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'
Stefano Alberto Russo
committed
# Get action if any
Stefano Alberto Russo
committed
action = request.GET.get('action', None)
uuid = request.GET.get('uuid', None)
fromlist = request.GET.get('fromlist', False)
Stefano Alberto Russo
committed
details = booleanize(request.GET.get('details', None))
Stefano Alberto Russo
committed
# Do we have to operate on a specific task?
if uuid:
Stefano Alberto Russo
committed
Stefano Alberto Russo
committed
try:
# Get the task (raises if none available including no permission)
try:
task = Task.objects.get(user=request.user, uuid=uuid)
Stefano Alberto Russo
committed
except Task.DoesNotExist:
raise ErrorMessage('Task does not exists or no access rights')
Stefano Alberto Russo
committed
set_verified_status(task)
Stefano Alberto Russo
committed
data['task'] = task
Stefano Alberto Russo
committed
Stefano Alberto Russo
committed
# Task actions
Stefano Alberto Russo
committed
if action=='delete':
if task.status not in [TaskStatuses.stopped, TaskStatuses.exited]:
try:
task.computing.manager.stop_task(task)
except:
pass
Stefano Alberto Russo
committed
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
Stefano Alberto Russo
committed
# 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})
Stefano Alberto Russo
committed
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)
Stefano Alberto Russo
committed
Stefano Alberto Russo
committed
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))
Stefano Alberto Russo
committed
# Do we have to list all the tasks?
if not uuid or (uuid and not details):
Stefano Alberto Russo
committed
# Get all tasks for list
Stefano Alberto Russo
committed
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()
Stefano Alberto Russo
committed
set_verified_status(task)
Stefano Alberto Russo
committed
# Set task and tasks variables
data['task'] = None
data['tasks'] = tasks
return render(request, 'tasks.html', {'data': data})
#=========================
#=========================
@private_view
def new_task(request):
# Init data
data={}
data['user'] = request.user
Stefano Alberto Russo
committed
# Get task container helper function
def get_task_container(request):
Stefano Alberto Russo
committed
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)
Stefano Alberto Russo
committed
try:
Stefano Alberto Russo
committed
task_container = Container.objects.get(uuid=task_container_uuid, user=None)
Stefano Alberto Russo
committed
except Container.DoesNotExist:
try:
Stefano Alberto Russo
committed
task_container = Container.objects.get(uuid=task_container_uuid, user=request.user)
Stefano Alberto Russo
committed
except Container.DoesNotExist:
Stefano Alberto Russo
committed
raise Exception('Consistency error, container with uuid "{}" does not exists or user "{}" does not have access rights'.format(task_container_uuid, request.user.email))
return task_container
Stefano Alberto Russo
committed
# Get task computing helper function
def get_task_computing(request):
task_computing_uuid = request.POST.get('task_computing_uuid', None)
Stefano Alberto Russo
committed
try:
Stefano Alberto Russo
committed
task_computing = Computing.objects.get(uuid=task_computing_uuid, group=None)
Stefano Alberto Russo
committed
except Computing.DoesNotExist:
try:
Stefano Alberto Russo
committed
task_computing = Computing.objects.get(uuid=task_computing_uuid, group__user=request.user)
Stefano Alberto Russo
committed
except Computing.DoesNotExist:
Stefano Alberto Russo
committed
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)
Stefano Alberto Russo
committed
# Handle the various steps
if not step:
# Step one is assumed: chose software container
return HttpResponseRedirect('/software/?mode=new_task')
elif step == 'two':
# Get software container and arch
data['task_container'] = get_task_container(request)
Stefano Alberto Russo
committed
# List all computing resources
Stefano Alberto Russo
committed
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':
# Get software container and arch
data['task_container'] = get_task_container(request)
# Get computing resource
data['task_computing'] = get_task_computing(request)
Stefano Alberto Russo
committed
# 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
committed
# TODO: refactor and unroll this code
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
committed
if data['task_container'].image_arch:
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?
Stefano Alberto Russo
committed
if data['task_computing'].emulated_archs:
# For now by default our container engine is the first one
container_engine = data['task_computing'].container_engines[0]
Stefano Alberto Russo
committed
# 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]:
Stefano Alberto Russo
committed
data['arch_emulation'] = True
# 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
Stefano Alberto Russo
committed
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']:
Stefano Alberto Russo
committed
raise ErrorMessage('This computing resource does not support architecture \'{}\' nor as native or emulated'.format(data['task_container'].image_arch))
Stefano Alberto Russo
committed
else:
Stefano Alberto Russo
committed
raise ErrorMessage('This computing resource does not support architecture \'{}\' nor as native or emulated'.format(data['task_container'].image_arch))
Stefano Alberto Russo
committed
else:
Stefano Alberto Russo
committed
data['arch_auto_selection'] = True
#raise ErrorMessage('Auto selecting architectures is not supported yet')
data['task_auth_token'] = str(uuid.uuid4())
# Set current and next step
data['step'] = 'three'
data['next_step'] = 'last'
elif step == 'last':
# Get software container and arch
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)
Stefano Alberto Russo
committed
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
# Task access method
Stefano Alberto Russo
committed
access_method = request.POST.get('access_method', 'auto')
Stefano Alberto Russo
committed
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:
Stefano Alberto Russo
committed
raise ErrorMessage('Unknown access method "{}"'.format(access_method))
Stefano Alberto Russo
committed
# Computing options
computing_options = {}
# 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
Stefano Alberto Russo
committed
# CPUs, memory and partition if set
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:
Stefano Alberto Russo
committed
try:
int(computing_cpus)
Stefano Alberto Russo
committed
except:
raise Exception('Cannot convert computing_cpus to int')
computing_options['cpus'] = int(computing_cpus)
Stefano Alberto Russo
committed
if computing_memory:
computing_options['memory'] = computing_memory
if computing_partition:
computing_options['partition'] = computing_partition
Stefano Alberto Russo
committed
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
Stefano Alberto Russo
committed
# 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')
# Set step
data['step'] = 'created'
return render(request, 'new_task.html', {'data': data})
Stefano Alberto Russo
committed
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
#=========================
# 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)
Stefano Alberto Russo
committed
except Exception as e:
data['error'] = 'Error in viewing task log'
logger.error('Error in viewing task log with uuid="{}": "{}"'.format(uuid, e))
Stefano Alberto Russo
committed
raise
Stefano Alberto Russo
committed
return render(request, 'task_log.html', {'data': data})
Stefano Alberto Russo
committed
#=========================
# Software containers
Stefano Alberto Russo
committed
#=========================
@private_view
def software(request):
Stefano Alberto Russo
committed
# Init data
data={}
data['user'] = request.user
data['profile'] = Profile.objects.get(user=request.user)
Stefano Alberto Russo
committed
# Get action if any
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))
Stefano Alberto Russo
committed
Stefano Alberto Russo
committed
# Get filter/search if any
search_text = request.POST.get('search_text', '')
search_owner = request.POST.get('search_owner', 'All')
Stefano Alberto Russo
committed
# Set back to page data
data['search_owner'] = search_owner
data['search_text'] = search_text
data['details'] = details
Stefano Alberto Russo
committed
# 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)
Stefano Alberto Russo
committed
# Do we have to operate on a specific container, or family of containers?
if container_uuid:
Stefano Alberto Russo
committed
Stefano Alberto Russo
committed
try:
Stefano Alberto Russo
committed
Stefano Alberto Russo
committed
# Get the container (raises if none available including no permission)
try:
container = Container.objects.get(uuid=container_uuid)
Stefano Alberto Russo
committed
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')
Stefano Alberto Russo
committed
data['container'] = container
Stefano Alberto Russo
committed
Stefano Alberto Russo
committed
# Container actions
Stefano Alberto Russo
committed
if action and action=='delete':
Stefano Alberto Russo
committed
# Delete
container.delete()
# Redirect
return HttpResponseRedirect('/software')
Stefano Alberto Russo
committed
Stefano Alberto Russo
committed
except Exception as e:
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))
Stefano Alberto Russo
committed
return render(request, 'error.html', {'data': data})
Stefano Alberto Russo
committed
Stefano Alberto Russo
committed
else:
# Ddo we have to operate on a container family?
if container_family_id:
Stefano Alberto Russo
committed
# 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)
Stefano Alberto Russo
committed
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)
Stefano Alberto Russo
committed
# Ok, nilter by owner
if search_owner != 'All':
if search_owner == 'User':
platform_containers =[]
if search_owner == 'Platform':
user_containers = []
Stefano Alberto Russo
committed
# 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'] = {}
Stefano Alberto Russo
committed
# Container family support class
class ContainerFamily(object):
Stefano Alberto Russo
committed
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
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
Stefano Alberto Russo
committed
@ 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})
Stefano Alberto Russo
committed
#=========================
# Add software container
Stefano Alberto Russo
committed
#=========================
@private_view
def add_software(request):
Stefano Alberto Russo
committed
# Init data
data = {}
data['user'] = request.user
Stefano Alberto Russo
committed
# Loop back the new container mode in the page to handle the switch
data['new_container_from'] = request.GET.get('new_container_from', 'registry')
Stefano Alberto Russo
committed
# Container name if setting up a new container
container_name = request.POST.get('container_name', None)
Stefano Alberto Russo
committed
if container_name:
Stefano Alberto Russo
committed
# How do we have to add this new container?
new_container_from = request.POST.get('new_container_from', None)
Stefano Alberto Russo
committed
Stefano Alberto Russo
committed
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
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