"""Warehouse job class."""
from os import sep
from os.path import dirname
from collections import OrderedDict
from pyramid.httpexceptions import HTTPFound
from chrysalio.lib.i18n import translate_field
from chrysalio.lib.config import settings_get_list
from chrysalio.lib.utils import common_directory, make_digest, convert_value
from chrysalio.lib.utils import rst2html
from chrysalio.helpers.literal import Literal
from cioservice.models.dbjob import DBJob
from ..lib.i18n import _
from ..lib.utils import file_ids2fullnames, build_callback
# =============================================================================
[docs]class WJob(object):
"""Class to manage jobs manually called in a warehouse.
:type request: pyramid.request.Request
:param request:
Current request.
"""
# -------------------------------------------------------------------------
def __init__(self, request):
"""Constructor method."""
self._request = request
# -------------------------------------------------------------------------
[docs] def available(self, warehouse, caller='browse_view'):
"""Return a list of available jobs for the current warehouse.
:type warehouse: .lib.warehouse.Warehouse
:param warehouse:
Current warehouse object.
:param str caller: (default='browse_view')
Name of the entity which calls the warehouse.
:rtype: dict
:return:
A dictionary of tuples such as
``(icon, translated_label, service, context)``.
"""
jobs = warehouse.jobs(self._request) if warehouse else None
if not jobs:
return None
ciowarehouse = self._request.registry['modules']['ciowarehouse']
access = ciowarehouse.warehouse_access(self._request, warehouse)
file_writer = ciowarehouse.warehouse_file_writer(
self._request, warehouse, access)
meta_writer = ciowarehouse.warehouse_meta_writer(
self._request, warehouse, access)
groups = set(self._request.session['user']['groups'])
services = self._request.registry['services']
available = []
for job_id in sorted(
jobs, key=lambda k: jobs[k]['priority'], reverse=True):
job = jobs[job_id]
if services[job['service_id']].authorized(
caller=caller, warehouse=warehouse, job=job,
file_writer=file_writer, meta_writer=meta_writer,
user_id=self._request.session['user']['user_id'],
groups=groups):
available.append((job_id, (
job['icon'],
translate_field(self._request, job['i18n_label']),
services[job['service_id']], job['context'])))
return OrderedDict(available)
# -------------------------------------------------------------------------
[docs] def prepare(self, warehouse, paging, form, action):
"""Prepare a job and a build.
:type warehouse: .lib.warehouse.Warehouse
:param warehouse:
Current warehouse object.
:type paging: chrysalio.lib.paging.Paging
:param paging:
Paging containing all the files.
:param form:
Current form.
:param str action:
Current action.
:rtype: tuple
:return:
A tuple such as ``(action, build_id)``.
"""
# Get job
job = self.get(warehouse, action[4:])
if job is None:
return '', None
# Display dialog
if action[:4] == 'job?' and job['threaded'] != 'monitored':
return action, None
# Validate form
if action[:4] == 'job!' and job['threaded'] != 'monitored' \
and not form.validate():
return action.replace('!', '?'), None
# Get build parameters
file_ids = [k[1:] for k in self._request.POST if k[0] == '#']
params = self._build_parameters(warehouse, job, paging, file_ids)
if params is None:
return '', None
build_id = '{0}-{1}'.format(
job['job_id'], make_digest(','.join(sorted(params['files']))))
# Register into the build manager
error = self._request.registry['modules'][
'cioservice'].build_manager.register(build_id, job, params)
if error is not None:
self._request.session.flash(error, 'alert')
return '', None
# Possibly move to build page
if job['threaded'] == 'monitored':
raise HTTPFound(self._request.route_path(
'build_view', build_id=build_id))
return action, build_id
# -------------------------------------------------------------------------
[docs] def run(self, build_id):
"""Execute a job on a build.
:param str build_id:
ID of a registered build.
"""
# Get the build environment
build_manager = self._request.registry['modules'][
'cioservice'].build_manager
build_env = build_manager.build_env(build_id)
if build_env is None:
self._request.session.flash(
_('This build does not exist.'), 'alert')
return
# Execute the job in a thread
if build_env['job']['threaded'] == 'yes':
build_manager.clean_logs(self._request.dbsession)
error = build_manager.run(build_id, build_env)
if error:
self._request.session.flash(error, 'alert')
else:
self._request.session.flash(_(
'The action "${a}" has been launched.',
{'a': translate_field(
self._request, build_env['job']['i18n_label'])}))
return
# Execute the job in the request
if build_env['status'] == 'running': # pragma: nocover
self._request.session.flash(_('Job already in progress.'), 'alert')
return
build_manager.clean_logs(self._request.dbsession)
build_manager.set_launch_time(build_id, build_env)
build_env['params']['dbsession'] = self._request.dbsession
service = self._request.registry['services'][
build_env['job']['service_id']]
result = service.run(service.make_build(build_id, build_env['params']))
# Commit and refresh
build_callback(
self._request.registry, build_env, result, self._request)
# Push result into flash message
self._result2flash(result)
# -------------------------------------------------------------------------
[docs] def get(self, warehouse, job_id, caller='browse_view'):
"""Retrieve a warehouse job checking if it is authorized in this
context.
:type warehouse: .lib.warehouse.Warehouse
:param warehouse:
Current warehouse object if exists.
:param str job_id:
Job ID.
:param str caller: (default='browse_view')
Name of the entity which calls the warehouse.
:rtype: dict
See: :meth:`.lib.warehouse.Warehouse.job`
"""
if warehouse is None:
return None
job = warehouse.job(self._request, job_id)
if job is None:
self._request.session.flash(
_('This action does not exist for this warehouse.'), 'alert')
return None
ciowarehouse = self._request.registry['modules']['ciowarehouse']
access = ciowarehouse.warehouse_access(self._request, warehouse)
if not self._request.registry['services'][
job['service_id']].authorized(
caller=caller, job=job, warehouse=warehouse,
file_writer=ciowarehouse.warehouse_file_writer(
self._request, warehouse, access),
meta_writer=ciowarehouse.warehouse_meta_writer(
self._request, warehouse, access)):
self._request.session.flash(
_('This action is not authorized in this context.'), 'alert')
return None
return job
# -------------------------------------------------------------------------
def _build_parameters(self, warehouse, job, paging, file_ids):
"""Make up parameters for the build.
:type warehouse: .lib.warehouse.Warehouse
:param warehouse:
Current warehouse object if exists.
:param dict job:
Job dictionary.
:type paging: chrysalio.lib.paging.Paging
:param paging:
Paging containing all the files.
:param list file_ids:
List of file IDs.
:rtype: dict
"""
params = {
'job_id': job['job_id'],
'context': job['context'],
'lang': self._request.locale_name,
'ttl': job['ttl'],
'settings': dict(job['settings']),
'files': file_ids2fullnames(self._request, paging, file_ids),
'caller': dict(self._request.session['user']),
'callback': 'ciowarehouse'}
# Files
service = self._request.registry['services'][job['service_id']]
service.select_files(params)
if service.need_files(job['context']) and not params['files']:
self._request.session.flash(
service.select_files_message(params), 'alert')
return None
# Values of variables
dbjob = self._request.dbsession.query(DBJob).filter_by(
job_id=job['job_id']).first()
if dbjob is None:
self._request.session.flash(
_('This action no longer exists.'), 'alert')
return None
variables = service.variables(dbjob.context)
params['values'] = {
k: variables[k]['default'] for k in variables
if 'default' in variables[k]}
for dbvalue in dbjob.values:
if dbvalue.variable in variables:
params['values'][dbvalue.variable] = convert_value(
variables[dbvalue.variable]['type'], dbvalue.value)
for variable in self._request.params:
if variable[:4] == 'val:' and variable[4:] in variables:
params['values'][variable[4:]] = convert_value(
variables[variable[4:]]['type'],
self._request.params[variable])
# Resources
params['resources'] = [
k.strip() for k in params['values'].get(
'__resources__', '').split(',') if k.strip()] \
+ list({dirname(k) for k in params['files']}) \
+ settings_get_list(job['settings'], 'resources')
# Settings
params['settings'] = {
k: job['settings'][k] for k in job['settings']
if k not in ('files', 'resources')}
# Input
if warehouse:
params['settings']['input.home.path'] = warehouse.root
params['settings']['input.home.id'] = warehouse.uid
params['settings']['input.home.url'] = self._request.route_path(
'glance_directory', warehouse_id=warehouse.uid, path='')
if not service.need_write_permission(job['context']):
return params
# Output
params['output'] = job['settings'].get('output')
if not params['output']:
params['output'] = common_directory(list(params['files']))
if warehouse:
params['settings']['output.home.path'] = warehouse.root
params['settings']['output.home.id'] = warehouse.uid
params['settings']['output.home.url'] = \
self._request.route_path(
'glance_directory', warehouse_id=warehouse.uid,
path='')
return params
# -------------------------------------------------------------------------
def _result2flash(self, result):
"""Add flash messages according to the result.
:param dict result:
Result dictionary.
"""
visible = 'path' in self._request.matchdict and 'output' in result \
and (sep.join(self._request.matchdict['path']) or '.') \
in result['output']
if 'errors' in result:
for k in result['errors']:
self._request.session.flash(
Literal(rst2html(self._request.localizer.translate(k))),
'alert')
elif 'warnings' in result:
for k in result['warnings']:
self._request.session.flash(
Literal(rst2html(self._request.localizer.translate(k))),
'refresh' if visible else '')
if 'infos' in result and 'errors' not in result:
for k in result['infos']:
self._request.session.flash(
Literal(rst2html(self._request.localizer.translate(k))),
'refresh' if visible else '')