diff --git a/README.md b/README.md index e789bcfec..520d102cc 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Register a client in IAM with the following properties: - introspection endpoint enabled Create the folder `instance` to put the application configuration files: - - (mandatory) `config.json` file (see the [example](app/config-sample.json)) + - (mandatory) `config.json` file (see the [example](instance/config-sample.json)) - (optional) `vault-config.json` file (see the [example]() needed to enable the integration with Vault ```` diff --git a/app/__init__.py b/app/__init__.py index 7fbfc47a8..0bd1e256a 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -15,20 +15,22 @@ import sys import socket -from flask import Flask +from flask import Flask, session from flask_alembic import Alembic from sqlalchemy_utils import database_exists, create_database from sqlalchemy import Table, Column, String, MetaData from werkzeug.middleware.proxy_fix import ProxyFix -from flask_dance.consumer import OAuth2ConsumerBlueprint from flask_mail import Mail from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate, upgrade +from flask_login import LoginManager from app.lib.ToscaInfo import ToscaInfo from app.lib.Vault import Vault - +from flaat import Flaat +from flask_dance.consumer import oauth_authorized import logging + # initialize SQLAlchemy db: SQLAlchemy = SQLAlchemy() @@ -57,6 +59,17 @@ if profile is not None and profile != 'default': app.config.from_object('config.' + profile) +flaat = Flaat() +flaat.set_web_framework('flask') +flaat.set_trusted_OP_list([idp['iss'] for idp in app.config.get('TRUSTED_OIDC_IDP_LIST')]) +flaat.set_timeout(20) +flaat.set_client_connect_timeout(20) +flaat.set_iss_config_timeout(20) + +from app.lib import indigoiam, egicheckin +from app.lib import dbhelpers +from app.models.User import User + @app.context_processor def inject_settings(): @@ -77,7 +90,9 @@ def inject_settings(): require_ssh_pubkey=app.config.get('FEATURE_REQUIRE_USER_SSH_PUBKEY') if app.config.get( 'FEATURE_REQUIRE_USER_SSH_PUBKEY') else "no", hidden_deployment_columns=app.config.get('FEATURE_HIDDEN_DEPLOYMENT_COLUMNS') if app.config.get( - 'FEATURE_HIDDEN_DEPLOYMENT_COLUMNS') else "" + 'FEATURE_HIDDEN_DEPLOYMENT_COLUMNS') else "", + enable_ports_request=app.config.get('FEATURE_PORTS_REQUEST') if app.config.get( + 'FEATURE_PORTS_REQUEST') else "no" ) @@ -94,22 +109,87 @@ def inject_settings(): from app.errors.routes import errors_bp app.register_blueprint(errors_bp) -iam_base_url = app.config['IAM_BASE_URL'] -iam_token_url = iam_base_url + '/token' -iam_refresh_url = iam_base_url + '/token' -iam_authorization_url = iam_base_url + '/authorize' - -iam_blueprint = OAuth2ConsumerBlueprint( - "iam", __name__, - client_id=app.config['IAM_CLIENT_ID'], - client_secret=app.config['IAM_CLIENT_SECRET'], - base_url=iam_base_url, - token_url=iam_token_url, - auto_refresh_url=iam_refresh_url, - authorization_url=iam_authorization_url, - redirect_to='home' -) -app.register_blueprint(iam_blueprint, url_prefix="/login") + +def get_auth_blueprint(self): + if 'auth_blueprint' in session.keys(): + bp = session['auth_blueprint'] + if bp == 'iam': + return app.iam_blueprint + if bp == 'egi': + return app.egicheckin_blueprint + return None + + +app.get_auth_blueprint = get_auth_blueprint.__get__('') + + +def get_auth_userinfo(self): + if 'auth_blueprint' in session.keys(): + bp = session['auth_blueprint'] + if bp == 'iam': + return app.iam_blueprint.session.get("/userinfo") + if bp == 'egi': + return app.egicheckin_blueprint.session.get('/auth/realms/egi/protocol/openid-connect/userinfo') + return None + + +app.get_auth_userinfo = get_auth_userinfo.__get__('') + + +def get_user_oauth(self): + userid = session['userid'] + if userid is not None: + user = dbhelpers.get_user(userid) + if user is not None and 'auth_blueprint' in session.keys(): + bp = session['auth_blueprint'] + if bp == 'iam': + return user.oauth['iam'] + if bp == 'egi': + return user.oauth['egiaai'] + return None + + +app.get_user_oauth = get_user_oauth.__get__('') + + +with app.app_context(): + app.iam_blueprint = indigoiam.create_blueprint() + app.register_blueprint(app.iam_blueprint, url_prefix="/login") + + # create/login local user on successful OAuth login + @oauth_authorized.connect_via(app.iam_blueprint) + def iam_logged_in(blueprint, token): + session['auth_blueprint'] = 'iam' + return indigoiam.auth_blueprint_login(blueprint, token) + + + if app.config.get('EGI_AAI_CLIENT_ID') and app.config.get('EGI_AAI_CLIENT_SECRET'): + app.egicheckin_blueprint = egicheckin.create_blueprint() + app.register_blueprint(app.egicheckin_blueprint, url_prefix="/login") + + @oauth_authorized.connect_via(app.egicheckin_blueprint) + def egicheckin_logged_in(blueprint, token): + session['auth_blueprint'] = 'egi' + return egicheckin.auth_blueprint_login(blueprint, token) + + # Inject the variable inject_egi_aai_enabled automatically into the context of templates + @app.context_processor + def inject_egi_aai_enabled(): + return dict(is_egi_aai_enabled=True) + + +login_manager = LoginManager() +login_manager.login_message = None +login_manager.login_message_category = "info" +login_manager.login_view = "login" + +login_manager.init_app(app) + + +@login_manager.user_loader +def load_user(user_id): + return dbhelpers.get_user(user_id) + from app.home.routes import home_bp app.register_blueprint(home_bp, url_prefix="/home") @@ -138,8 +218,6 @@ def inject_settings(): logging.basicConfig(level=numeric_level) -from app import models - # check if database exists engine = db.get_engine(app) if not database_exists(engine.url): # Checks for the first time diff --git a/app/config-sample.json b/app/config-sample.json deleted file mode 100644 index 28780deab..000000000 --- a/app/config-sample.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "IAM_CLIENT_ID": "my_client_id", - "IAM_CLIENT_SECRET": "my_client_secret", - "IAM_BASE_URL": "https://iam-test.indigo-datacloud.eu", - "ORCHESTRATOR_URL": "https://indigo-paas.cloud.ba.infn.it/orchestrator", - "SLAM_URL": "https://indigo-slam.cloud.ba.infn.it:8443", - "CMDB_URL": "https://indigo-paas.cloud.ba.infn.it/cmdb", - "IM_URL": "https://indigo-paas.cloud.ba.infn.it/im", - "VAULT_URL": "https://indigo-vault.cloud.ba.infn.it:8200", - "VAULT_ROLE": "orchestrator", - "VAULT_OIDC_AUDIENCE": "********", - "TOSCA_TEMPLATES_DIR": "/opt/tosca-templates", - "IAM_GROUP_MEMBERSHIP": [ "group1" ], - "SUPPORT_EMAIL": "support@example.com", - "ENABLE_ADVANCED_MENU": "no", - "EXTERNAL_LINKS": [ { "url": "https://indigo-paas.cloud.ba.infn.it/status-page", "menu_item_name": "Services status" } ], - "LOG_LEVEL": "info" -} diff --git a/app/deployments/routes.py b/app/deployments/routes.py index 5d1a5de15..764b40652 100644 --- a/app/deployments/routes.py +++ b/app/deployments/routes.py @@ -15,8 +15,8 @@ import copy from flask import Blueprint, session, render_template, flash, redirect, url_for, json, request -from app import app, iam_blueprint, tosca, vaultservice -from app.lib import auth, utils, settings, dbhelpers, yourls +from app import app, tosca, vaultservice +from app.lib import auth, utils, mail_utils, settings, dbhelpers, yourls from app.lib.ldap_user import LdapUserManager from app.models.Deployment import Deployment from app.providers import sla @@ -36,7 +36,6 @@ import re - deployments_bp = Blueprint('deployments_bp', __name__, template_folder='templates', static_folder='static') @@ -53,9 +52,9 @@ @deployments_bp.route('/all') @auth.authorized_with_valid_token def showdeployments(): - access_token = iam_blueprint.session.token['access_token'] + access_token = app.get_auth_blueprint().session.token['access_token'] - headers = {'Authorization': 'bearer %s' % access_token} + headers = {'Authorization': 'bearer %s' % access_token, 'Cache-Control': 'no-cache'} params = "createdBy=me&page={}&size={}".format(0, 999999) if 'active_usergroup' in session and session['active_usergroup'] is not None: @@ -82,7 +81,7 @@ def showdeployments(): @deployments_bp.route('//template') @auth.authorized_with_valid_token def deptemplate(depid=None): - access_token = iam_blueprint.session.token['access_token'] + access_token = app.get_auth_blueprint().session.token['access_token'] headers = {'Authorization': 'bearer %s' % access_token} url = settings.orchestratorUrl + "/deployments/" + depid + "/template" @@ -115,6 +114,12 @@ def unlockdeployment(depid=None): dbhelpers.add_object(dep) return redirect(url_for('deployments_bp.showdeployments')) +@deployments_bp.route('/edit', methods=['POST']) +@auth.authorized_with_valid_token +def editdeployment(): + form_data = request.form.to_dict() + dbhelpers.update_deployment(form_data["deployment_uuid"],dict(description=form_data["description"])) + return redirect(url_for('deployments_bp.showdeployments')) def preprocess_outputs(browser, outputs, stoutputs): for key, value in stoutputs.items(): @@ -130,9 +135,12 @@ def preprocess_outputs(browser, outputs, stoutputs): app.logger.debug('Error creating short url: {}'.format(str(e))) pass - if origin_url.scheme == 'http' and browser['name'] == "chrome" and browser['version'] >= 86: - message = stoutputs[key]['warning'] if 'warning' in stoutputs[key] else "" - stoutputs[key]['warning'] = "{}
{}".format("The download will be blocked by Chrome. Please, use Firefox for a full user experience.", message) + if origin_url.scheme == 'http' and browser['name'] == "chrome" and browser['version'] >= 86: + message = stoutputs[key]['warning'] if 'warning' in stoutputs[key] else "" + stoutputs[key]['warning'] = "{}
{}".format("The download will be blocked by Chrome. Please, " + "use Firefox for a full user experience.", + message) + @deployments_bp.route('//details') @auth.authorized_with_valid_token @@ -152,18 +160,17 @@ def depoutput(depid=None): stoutputs = json.loads(dep.stoutputs.strip('\"')) if dep.stoutputs else {} inputs = {} for k, v in i.items(): - if ((stinputs[k]['printable'] if 'printable' in stinputs[k] else True) if k in stinputs else True): + if (stinputs[k]['printable'] if 'printable' in stinputs[k] else True) if k in stinputs else True: inputs[k] = v - - additional_outputs = getadditionaloutputs(dep, iam_blueprint.session.token['access_token']) + additional_outputs = getadditionaloutputs(dep, app.get_auth_blueprint().session.token['access_token']) outputs = {**outputs, **additional_outputs} browser = request.user_agent.browser version = request.user_agent.version and int(request.user_agent.version.split('.')[0]) - preprocess_outputs(dict(name = browser, version = version), outputs, stoutputs) + preprocess_outputs(dict(name=browser, version=version), outputs, stoutputs) return render_template('depoutput.html', deployment=dep, @@ -171,6 +178,7 @@ def depoutput(depid=None): outputs=outputs, stoutputs=stoutputs) + def getadditionaloutputs(dep, access_token): uuid = dep.uuid status = dep.status @@ -182,7 +190,7 @@ def getadditionaloutputs(dep, access_token): # try to get kubeconfig file from log try: kubeconfig = extract_info_from_deplog(access_token, uuid, 'kubeconfig') - additional_outputs = { "kubeconfig": kubeconfig } + additional_outputs = {"kubeconfig": kubeconfig} update = True except: app.logger.debug("Error while extracting kubeconfig file from log for deployment {}".format(dep.uuid)) @@ -194,7 +202,6 @@ def getadditionaloutputs(dep, access_token): return additional_outputs - def extract_info_from_deplog(access_token, uuid, info_type): headers = {'Authorization': 'bearer %s' % access_token} @@ -209,7 +216,6 @@ def extract_info_from_deplog(access_token, uuid, info_type): if info_type == "kubeconfig": - match = None for line in lines: match = re.search('^.*KUBECONFIG file.*\n.*\n.*\n.*\n.*\n.*\"(apiVersion.*)\"\n.*\n.*$', line) if match is not None: @@ -217,9 +223,10 @@ def extract_info_from_deplog(access_token, uuid, info_type): return info + @deployments_bp.route('//templatedb') def deptemplatedb(depid): - if not iam_blueprint.session.authorized: + if not app.get_auth_blueprint().session.authorized: return redirect(url_for('home_bp.login')) # retrieve deployment from DB @@ -234,7 +241,7 @@ def deptemplatedb(depid): @deployments_bp.route('//log') @auth.authorized_with_valid_token def deplog(depid=None): - access_token = iam_blueprint.session.token['access_token'] + access_token = app.get_auth_blueprint().session.token['access_token'] headers = {'Authorization': 'bearer %s' % access_token} # app.logger.debug("Configuration: " + json.dumps(settings.orchestratorConf)) @@ -253,7 +260,7 @@ def deplog(depid=None): @deployments_bp.route('//infradetails') @auth.authorized_with_valid_token def depinfradetails(depid=None, path=None): - access_token = iam_blueprint.session.token['access_token'] + access_token = app.get_auth_blueprint().session.token['access_token'] headers = {'Authorization': 'bearer %s' % access_token} # app.logger.debug("Configuration: " + json.dumps(settings.orchestratorConf)) @@ -266,16 +273,17 @@ def depinfradetails(depid=None, path=None): app.logger.debug("VMs details: {}".format(response.text)) details = [] for vm_details in vminfos: - vminfo = utils.format_json_radl(vm_details["vmProperties"]) - details.append(vminfo) + vminfo = utils.format_json_radl(vm_details["vmProperties"]) + details.append(vminfo) return render_template('depinfradetails.html', vmsdetails=details) return redirect(url_for('deployments_bp.showdeployments')) + @deployments_bp.route('//qcgdetails') @auth.authorized_with_valid_token def depqcgdetails(depid=None, path=None): - access_token = iam_blueprint.session.token['access_token'] + access_token = app.get_auth_blueprint().session.token['access_token'] headers = {'Authorization': 'bearer %s' % access_token} # app.logger.debug("Configuration: " + json.dumps(settings.orchestratorConf)) @@ -298,7 +306,7 @@ def depqcgdetails(depid=None, path=None): @deployments_bp.route('//delete') @auth.authorized_with_valid_token def depdel(depid=None): - access_token = iam_blueprint.session.token['access_token'] + access_token = app.get_auth_blueprint().session.token['access_token'] headers = {'Authorization': 'bearer %s' % access_token} url = settings.orchestratorUrl + "/deployments/" + depid response = requests.delete(url, headers=headers) @@ -320,7 +328,7 @@ def depupdate(depid=None): if depid is not None: dep = dbhelpers.get_deployment(depid) if dep is not None: - access_token = iam_blueprint.session.token['access_token'] + access_token = app.get_auth_blueprint().session.token['access_token'] template = dep.template tosca_info = tosca.extracttoscainfo(yaml.full_load(io.StringIO(template)), None) inputs = json.loads(dep.inputs.strip('\"')) if dep.inputs else {} @@ -340,7 +348,6 @@ def depupdate(depid=None): settings.orchestratorConf['cmdb_url'], dep.deployment_type) ssh_pub_key = dbhelpers.get_ssh_pub_key(session['userid']) - return render_template('updatedep.html', template=tosca_info, template_description=tosca_info['description'], @@ -362,7 +369,7 @@ def depupdate(depid=None): @auth.authorized_with_valid_token def updatedep(): - access_token = iam_blueprint.session.token['access_token'] + access_token = app.get_auth_blueprint().session.token['access_token'] form_data = request.form.to_dict() @@ -391,8 +398,8 @@ def updatedep(): remove_sla_from_template(template) inputs = {k: v for (k, v) in form_data.items() if not k.startswith("extra_opts.") and not k == '_depid'} - #oldinputs = json.loads(dep.inputs.strip('\"')) if dep.inputs else {} - #inputs = {**oldinputs, **inputs} + # oldinputs = json.loads(dep.inputs.strip('\"')) if dep.inputs else {} + # inputs = {**oldinputs, **inputs} additionaldescription = form_data['additional_description'] @@ -420,7 +427,7 @@ def updatedep(): dep.feedback_required = feedback_required dep.description = additionaldescription dep.template = template_text - #dep.inputs = json.dumps(inputs), + # dep.inputs = json.dumps(inputs), oldinputs = json.loads(dep.inputs.strip('\"')) if dep.inputs else {} updatedinputs = {**oldinputs, **inputs} dep.inputs = json.dumps(updatedinputs), @@ -432,7 +439,7 @@ def updatedep(): @deployments_bp.route('/configure', methods=['GET', 'POST']) @auth.authorized_with_valid_token def configure(): - access_token = iam_blueprint.session.token['access_token'] + access_token = app.get_auth_blueprint().session.token['access_token'] selected_tosca = None @@ -442,8 +449,8 @@ def configure(): if 'selected_tosca' in request.args: selected_tosca = request.args['selected_tosca'] - if 'active_user_group' in request.args: - session['active_usergroup'] = request.args['active_user_group'] + #if 'active_user_group' in request.args: + # session['active_usergroup'] = request.args['active_user_group'] if 'selected_group' in request.args: templates = tosca.tosca_gmetadata[request.args['selected_group']]['templates'] @@ -454,7 +461,13 @@ def configure(): if selected_tosca: - template = tosca.tosca_info[selected_tosca] + template = copy.deepcopy(tosca.tosca_info[selected_tosca]) + # Manage eventual overrides + for k,v in template['inputs'].items(): + if 'group_overrides' in v and session['active_usergroup'] in v['group_overrides']: + overrides = v['group_overrides'][session['active_usergroup']] + template['inputs'][k] = {**v, **overrides} + sla_id = tosca_helpers.getslapolicy(template) slas = sla.get_slas(access_token, settings.orchestratorConf['slam_url'], settings.orchestratorConf['cmdb_url'], @@ -463,7 +476,8 @@ def configure(): ssh_pub_key = dbhelpers.get_ssh_pub_key(session['userid']) if not ssh_pub_key and app.config.get('FEATURE_REQUIRE_USER_SSH_PUBKEY') == 'yes': - flash('Warning! You will not be able to deploy your service as no Public SSH key has been uploaded.', "danger") + flash('Warning! You will not be able to deploy your service as no Public SSH key has been uploaded.', + "danger") return render_template('createdep.html', template=template, @@ -508,13 +522,12 @@ def add_sla_to_template(template, sla_id): @deployments_bp.route('/submit', methods=['POST']) @auth.authorized_with_valid_token def createdep(): - access_token = iam_blueprint.session.token['access_token'] + access_token = app.get_auth_blueprint().session.token['access_token'] selected_template = request.args.get('template') source_template = tosca.tosca_info[selected_template] app.logger.debug("Form data: " + json.dumps(request.form.to_dict())) - with io.open(os.path.join(settings.toscaDir, selected_template)) as stream: template = yaml.full_load(stream) # rewind file @@ -555,8 +568,16 @@ def createdep(): uuidgen_deployment = str(uuid_generator.uuid1()); for key,value in stinputs.items(): + + # Manage special type 'dependent_definition' as first + if value["type"] == "dependent_definition": + # retrieve the real type from dedicated field + if inputs[key + "-ref"] in stinputs: + value = stinputs[inputs[key + "-ref"]] + del inputs[key + "-ref"] + # Manage security groups - if value["type"]=="map" and value["entry_schema"]["type"]=="tosca.datatypes.network.PortSpec": + if value["type"]=="map" and (value["entry_schema"]["type"]=="tosca.datatypes.network.PortSpec" or value["entry_schema"]["type"]=="tosca.datatypes.indigo.network.PortSpec"): if key in inputs: try: inputs[key] = json.loads(form_data[key]) @@ -595,9 +616,12 @@ def createdep(): # elif value["entry_schema"]["type"]=="tosca.datatypes.indigo.User": # if app.config.get('FEATURE_REQUIRE_USER_SSH_PUBKEY')=='yes': # if dbhelpers.get_ssh_pub_key(session['userid']): - # inputs[key] = [ { "os_user_name": session['preferred_username'], "os_user_add_to_sudoers": True, "os_user_ssh_public_key": dbhelpers.get_ssh_pub_key(session['userid']) } ] + # inputs[key] = [ { "os_user_name": session['preferred_username'], + # "os_user_add_to_sudoers": True, "os_user_ssh_public_key": + # dbhelpers.get_ssh_pub_key(session['userid']) } ] # else: - # flash("Deployment request failed: no SSH key found. Please upload your key.", "danger") + # flash("Deployment request failed: no SSH key found. Please upload your key.", + # "danger") # doprocess = False # else: # inputs[key] = json_data @@ -610,17 +634,12 @@ def createdep(): app.logger.info("Add ssh user") if app.config.get('FEATURE_REQUIRE_USER_SSH_PUBKEY')=='yes': if dbhelpers.get_ssh_pub_key(session['userid']): - inputs[key] = [ { "os_user_name": session['preferred_username'], "os_user_add_to_sudoers": True, "os_user_ssh_public_key": dbhelpers.get_ssh_pub_key(session['userid']) } ] + inputs[key] = [{"os_user_name": session['preferred_username'], "os_user_add_to_sudoers": True, + "os_user_ssh_public_key": dbhelpers.get_ssh_pub_key(session['userid'])}] else: flash("Deployment request failed: no SSH key found. Please upload your key.", "danger") doprocess = False - # Manage special type 'dependent_definition' - if value["type"] == "dependent_definition": - # retrieve the real type from dedicated field - value["type"] = inputs[key + "-type"] - del inputs[key + "-type"] - # Manage Swift-related fields if value["type"] == "swift_autouuid": if key in inputs: @@ -658,6 +677,8 @@ def createdep(): try: del inputs[key] project = next(filter(lambda tenant: tenant.get('group') == session['active_usergroup'], value['auth']['tenants']), None) + if not project: + raise IndexError("Project not configured for S3") access, secret = keystone.get_or_create_ec2_creds(access_token, project.get('name'), value["auth"]["url"], value["auth"]["identity_provider"], value["auth"]["protocol"]) access_key_input_name = value["inputs"]["aws_access_key"] inputs[access_key_input_name] = access @@ -676,11 +697,15 @@ def createdep(): functions[func](**args) except Forbidden as e: app.logger.error("Error while testing S3: {}".format(e)) - flash(" Sorry, your request needs a special authorization. A notification has been sent automatically to the support team. You will be contacted soon.", 'danger') - utils.send_authorization_request_email("Sync&Share aaS for group {}".format(session["active_usergroup"])) + flash(" Sorry, your request needs a special authorization. " + "A notification has been sent automatically to the support team. You will be contacted soon.", + 'danger') + mail_utils.send_authorization_request_email( + "Sync&Share aaS for group {}".format(session["active_usergroup"])) doprocess = False except Exception as e: - flash(" The deployment submission failed with: {}. Please contact the admin(s): {}".format(e, app.config.get('SUPPORT_EMAIL')), 'danger') + flash(" The deployment submission failed with: {}. Please contact the admin(s): {}" + .format(e, app.config.get('SUPPORT_EMAIL')), 'danger') doprocess = False if value["type"] == "ldap_user": @@ -716,9 +741,29 @@ def createdep(): except Exception as e: app.logger.error("Error: {}".format(e)) - flash(" The deployment submission failed with: {}. Please try later or contact the admin(s): {}".format(e, app.config.get('SUPPORT_EMAIL')), 'danger') + flash(" The deployment submission failed with: {}. Please try later or contact the admin(s): {}" + .format(e, app.config.get('SUPPORT_EMAIL')), 'danger') doprocess = False + if value["type"] == "userinfo": + if key in inputs: + if value["attribute"] == "sub": + inputs[key] = session['userid'] + + if value["type"] == "multiselect": + if key in inputs: + try: + lval = request.form.getlist(key) + if 'format' in value and value['format']['type'] == 'string': + inputs[key] = value['format']['delimiter'].join(lval) + else: + inputs[key] = lval + except Exception as e: + app.logger.error("Error processing input {}: {}".format(key,e)) + flash( + " The deployment submission failed with: {}. Please try later or contact the admin(s): {}".format( + e, app.config.get('SUPPORT_EMAIL')), 'danger') + doprocess = False if swift and swift_map: @@ -734,49 +779,46 @@ def createdep(): if swift_filename: - for f in swift_filename: + for f in swift_filename: - file = request.files[f] - if file: - upload_folder = app.config['UPLOAD_FOLDER'] - upload_folder = os.path.join(upload_folder, swift_uuid) - filename = secure_filename(file.filename) - fullfilename = os.path.join(upload_folder, filename) - if not os.path.exists(upload_folder): - os.makedirs(upload_folder) - file.save(fullfilename) + file = request.files[f] + if file: + upload_folder = app.config['UPLOAD_FOLDER'] + upload_folder = os.path.join(upload_folder, swift_uuid) + filename = secure_filename(file.filename) + fullfilename = os.path.join(upload_folder, filename) + if not os.path.exists(upload_folder): + os.makedirs(upload_folder) + file.save(fullfilename) - if f not in inputs: - inputs[f] = file.filename + if f not in inputs: + inputs[f] = file.filename - containername = basecontainername = swift.basecontainername - containers = swift.getownedcontainers() - basecontainer = next(filter(lambda x: x['name'] == basecontainername, containers), None) - if basecontainer is None: - swift.createcontainer(basecontainername) + containername = basecontainername = swift.basecontainername + containers = swift.getownedcontainers() + basecontainer = next(filter(lambda x: x['name'] == basecontainername, containers), None) + if basecontainer is None: + swift.createcontainer(basecontainername) + containername = basecontainername + "/" + swift_uuid - containername = basecontainername + "/" + swift_uuid + with open(fullfilename, 'rb') as ff: + calchash = swift.md5hash(ff) + with open(fullfilename, 'rb') as ff: + objecthash = swift.createobject(containername, filename, contents=ff.read()) - with open(fullfilename, 'rb') as f: - calchash = swift.md5hash(f) - with open(fullfilename, 'rb') as f: - objecthash = swift.createobject(containername, filename, contents=f.read()) + if hash is not None and objecthash != swift.emptyMd5: + swiftprocess = True - if hash is not None and objecthash != swift.emptyMd5: - swiftprocess = True + os.remove(fullfilename) + os.rmdir(upload_folder) - os.remove(fullfilename) - os.rmdir(upload_folder) - - if calchash != objecthash: + if calchash != objecthash: + doprocess = False + flash("Wrong swift file checksum!", 'danger') + else: doprocess = False - flash("Wrong swift file checksum!", 'danger') - else: - doprocess = False - flash("Missing file object!", 'danger') - - + flash("Missing file object!", 'danger') if doprocess: @@ -917,3 +959,21 @@ def add_storage_encryption(access_token, inputs): inputs['vault_secret_path'] = session['userid'] + '/' + vault_secret_uuid return storage_encryption, vault_secret_uuid, vault_secret_key + + +@deployments_bp.route('/sendportsreq', methods=['POST']) +def sendportsrequest(): + form_data = request.form.to_dict() + + try: + utils.send_ports_request_email(form_data['deployment_uuid'], email=form_data['email'], message=form_data['message']) + + flash( + "Your request has been sent to the support team. You will receive soon a notification email about your request. Thank you!", + "success") + + except Exception as error: + utils.logexception("sending email:".format(error)) + flash("Sorry, an error occurred while sending your request. Please retry.", "danger") + + return redirect(url_for('deployments_bp.showdeployments')) diff --git a/app/deployments/templates/createdep.html b/app/deployments/templates/createdep.html index 29868bcc3..1243b9e00 100644 --- a/app/deployments/templates/createdep.html +++ b/app/deployments/templates/createdep.html @@ -208,6 +208,53 @@

}); }); +function validatePorts(input){ + var port = $(input) + console.log("val: \"" + port.val() + "\""); + let val = port.val(); + let re = /(^\[[0-9]+,[0-9]+\]$)|(^[0-9]+$)/; + if (!re.test(val)){ + port.addClass("is-invalid"); + } + else { + console.log("ok"); + port.removeClass("is-invalid"); + } + +} + +function validateCidr(input){ + var cidr = $(input) + //console.log("val: \"" + cidr.val() + "\""); + let val = cidr.val(); + let re = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}\/([0-9]|[1-2][0-9]|3[0-2])$/; + if (!re.test(val)){ + cidr.addClass("is-invalid"); + } + else { + //console.log("ok"); + cidr.removeClass("is-invalid"); + } + +} + +function __attachValidationHandler(input) { + console.log(input); + var valfunc = input.data("validate-function"); + if (typeof valfunc !== 'undefined' && valfunc.length > 0) { + + input.on("blur", function(){ + eval(valfunc)($(this)); + }); + } +} + +$(document).ready(function () { + $("#depSubmit").find("input").each(function() { + __attachValidationHandler($(this)); + }); +}); + {% if enable_vault_integration and ssh_pub_key is none %} diff --git a/app/deployments/templates/deployments.html b/app/deployments/templates/deployments.html index 6250a046d..b0aa285de 100644 --- a/app/deployments/templates/deployments.html +++ b/app/deployments/templates/deployments.html @@ -35,7 +35,7 @@

My deployments

-
+
@@ -84,22 +84,19 @@

My deployments

Toggle Dropdown + {% if deployment.locked == 0 %} + Lock + Delete + {% else %} + Unlock + {% endif %} + + @@ -139,6 +144,76 @@

{{ deployment.uuid }}


STATUS: {{deployment.status}}

{% if deployment.status_reason %} -

ERROR MSG: {{deployment.status_reason}}

+

ERROR MSG: {{deployment.status_reason}}

{% endif %}

CREATED AT: {{deployment.creation_time}}

UPDATED AT: {{deployment.update_time}}

diff --git a/app/deployments/templates/input_types.html b/app/deployments/templates/input_types.html index bc0903f5d..3bd3647ea 100644 --- a/app/deployments/templates/input_types.html +++ b/app/deployments/templates/input_types.html @@ -5,7 +5,7 @@ {% set mode = "" %} {% endif %}
- {% if (value.type is not defined or (value.type != "hidden" and value.type != "swift_token" and value.type != "swift_autouuid" and value.type != "random_password" and value.type != "openstack_ec2credentials" and value.type != "uuidgen" and value.type != "ldap_user" and value.type != "ssh_user")) + {% if (value.type is not defined or (value.type != "hidden" and value.type != "swift_token" and value.type != "swift_autouuid" and value.type != "random_password" and value.type != "openstack_ec2credentials" and value.type != "uuidgen" and value.type != "ldap_user" and value.type != "ssh_user" and value.type != "userinfo")) and (update == False or value.updatable is not defined or value.updatable == True)%}