зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1415868 - add support for defining actions with kind=hook; r=jonasfj,tomprince
This does not affect any existing actions. MozReview-Commit-ID: 9j5cT2kA7UU --HG-- extra : rebase_source : 1191d7ecb05b8083a4923b9dbe97218faf65a088
This commit is contained in:
Родитель
0ba14ea32c
Коммит
0f0fde3dad
|
@ -22,9 +22,7 @@ from taskgraph.parameters import Parameters
|
|||
actions = []
|
||||
callbacks = {}
|
||||
|
||||
Action = namedtuple('Action', [
|
||||
'name', 'title', 'description', 'order', 'context', 'schema', 'task_template_builder',
|
||||
])
|
||||
Action = namedtuple('Action', ['order', 'action_builder'])
|
||||
|
||||
|
||||
def is_json(data):
|
||||
|
@ -37,7 +35,8 @@ def is_json(data):
|
|||
|
||||
|
||||
def register_callback_action(name, title, symbol, description, order=10000,
|
||||
context=[], available=lambda parameters: True, schema=None):
|
||||
context=[], available=lambda parameters: True,
|
||||
schema=None, kind='task', generic=True):
|
||||
"""
|
||||
Register an action callback that can be triggered from supporting
|
||||
user interfaces, such as Treeherder.
|
||||
|
@ -90,6 +89,11 @@ def register_callback_action(name, title, symbol, description, order=10000,
|
|||
schema : dict
|
||||
JSON schema specifying input accepted by the action.
|
||||
This is optional and can be left ``null`` if no input is taken.
|
||||
kind : string
|
||||
The action kind to define - must be one of `task` or `hook`. Only for
|
||||
transitional purposes.
|
||||
generic : boolean
|
||||
For kind=hook, whether this is a generic action or has its own permissions.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -98,11 +102,15 @@ def register_callback_action(name, title, symbol, description, order=10000,
|
|||
"""
|
||||
mem = {"registered": False} # workaround nonlocal missing in 2.x
|
||||
|
||||
assert isinstance(title, basestring), 'title must be a string'
|
||||
assert isinstance(description, basestring), 'description must be a string'
|
||||
title = title.strip()
|
||||
description = description.strip()
|
||||
|
||||
def register_callback(cb):
|
||||
assert isinstance(name, basestring), 'name must be a string'
|
||||
assert isinstance(title, basestring), 'title must be a string'
|
||||
assert isinstance(description, basestring), 'description must be a string'
|
||||
assert isinstance(order, int), 'order must be an integer'
|
||||
assert kind in ('task', 'hook'), 'kind must be task or hook'
|
||||
assert callable(schema) or is_json(schema), 'schema must be a JSON compatible object'
|
||||
assert isinstance(cb, FunctionType), 'callback must be a function'
|
||||
# Allow for json-e > 25 chars in the symbol.
|
||||
|
@ -113,58 +121,113 @@ def register_callback_action(name, title, symbol, description, order=10000,
|
|||
assert not mem['registered'], 'register_callback_action must be used as decorator'
|
||||
assert cb.__name__ not in callbacks, 'callback name {} is not unique'.format(cb.__name__)
|
||||
|
||||
def task_template_builder(parameters, graph_config):
|
||||
def action_builder(parameters, graph_config):
|
||||
if not available(parameters):
|
||||
return None
|
||||
|
||||
actionPerm = 'generic' if generic else name
|
||||
|
||||
# gather up the common decision-task-supplied data for this action
|
||||
repo_param = '{}head_repository'.format(graph_config['project-repo-param-prefix'])
|
||||
repository = {
|
||||
'url': parameters[repo_param],
|
||||
'project': parameters['project'],
|
||||
'level': parameters['level'],
|
||||
}
|
||||
|
||||
revision = parameters['{}head_rev'.format(graph_config['project-repo-param-prefix'])]
|
||||
push = {
|
||||
'owner': 'mozilla-taskcluster-maintenance@mozilla.com',
|
||||
'pushlog_id': parameters['pushlog_id'],
|
||||
'revision': revision,
|
||||
}
|
||||
|
||||
task_group_id = os.environ.get('TASK_ID', slugid())
|
||||
match = re.match(r'https://(hg.mozilla.org)/(.*?)/?$', parameters[repo_param])
|
||||
if not match:
|
||||
raise Exception('Unrecognized {}'.format(repo_param))
|
||||
repo_scope = 'assume:repo:{}/{}:branch:default'.format(
|
||||
match.group(1), match.group(2))
|
||||
action = {
|
||||
'name': name,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'taskGroupId': task_group_id,
|
||||
'cb_name': cb.__name__,
|
||||
'symbol': symbol,
|
||||
}
|
||||
|
||||
task_group_id = os.environ.get('TASK_ID', slugid())
|
||||
rv = {
|
||||
'name': name,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'context': context,
|
||||
}
|
||||
if schema:
|
||||
rv['schema'] = schema(graph_config=graph_config) if callable(schema) else schema
|
||||
|
||||
template = graph_config.taskcluster_yml
|
||||
# for kind=task, we embed the task from .taskcluster.yml in the action, with
|
||||
# suitable context
|
||||
if kind == 'task':
|
||||
template = graph_config.taskcluster_yml
|
||||
|
||||
with open(template, 'r') as f:
|
||||
taskcluster_yml = yaml.safe_load(f)
|
||||
if taskcluster_yml['version'] != 1:
|
||||
raise Exception('actions.json must be updated to work with .taskcluster.yml')
|
||||
if not isinstance(taskcluster_yml['tasks'], list):
|
||||
raise Exception('.taskcluster.yml "tasks" must be a list for action tasks')
|
||||
# tasks get all of the scopes the original push did, yuck; this is not
|
||||
# done with kind = hook.
|
||||
repo_scope = 'assume:repo:{}/{}:branch:default'.format(
|
||||
match.group(1), match.group(2))
|
||||
action['repo_scope'] = repo_scope
|
||||
|
||||
return {
|
||||
'$let': {
|
||||
'tasks_for': 'action',
|
||||
'repository': {
|
||||
'url': parameters[repo_param],
|
||||
'project': parameters['project'],
|
||||
'level': parameters['level'],
|
||||
},
|
||||
'push': {
|
||||
'owner': 'mozilla-taskcluster-maintenance@mozilla.com',
|
||||
'pushlog_id': parameters['pushlog_id'],
|
||||
'revision': revision,
|
||||
},
|
||||
'action': {
|
||||
'name': name,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'taskGroupId': task_group_id,
|
||||
'repo_scope': repo_scope,
|
||||
'cb_name': cb.__name__,
|
||||
'symbol': symbol,
|
||||
with open(template, 'r') as f:
|
||||
taskcluster_yml = yaml.safe_load(f)
|
||||
if taskcluster_yml['version'] != 1:
|
||||
raise Exception(
|
||||
'actions.json must be updated to work with .taskcluster.yml')
|
||||
if not isinstance(taskcluster_yml['tasks'], list):
|
||||
raise Exception(
|
||||
'.taskcluster.yml "tasks" must be a list for action tasks')
|
||||
|
||||
rv.update({
|
||||
'kind': 'task',
|
||||
'task': {
|
||||
'$let': {
|
||||
'tasks_for': 'action',
|
||||
'repository': repository,
|
||||
'push': push,
|
||||
'action': action,
|
||||
},
|
||||
'in': taskcluster_yml['tasks'][0],
|
||||
},
|
||||
'in': taskcluster_yml['tasks'][0]
|
||||
}
|
||||
})
|
||||
|
||||
actions.append(Action(
|
||||
name.strip(), title.strip(), description.strip(), order, context,
|
||||
schema, task_template_builder))
|
||||
# for kind=hook
|
||||
elif kind == 'hook':
|
||||
trustDomain = graph_config['trust-domain']
|
||||
level = parameters['level']
|
||||
rv.update({
|
||||
'kind': 'hook',
|
||||
'hookGroupId': 'project-{}'.format(trustDomain),
|
||||
'hookId': 'in-tree-action-{}-{}'.format(level, actionPerm),
|
||||
'hookPayload': {
|
||||
# provide the decision-task parameters as context for triggerHook
|
||||
"decision": {
|
||||
'action': action,
|
||||
'repository': repository,
|
||||
'push': push,
|
||||
# parameters is long, so fetch it from the actions.json variables
|
||||
'parameters': {'$eval': 'parameters'},
|
||||
},
|
||||
|
||||
# and pass everything else through from our own context
|
||||
"user": {
|
||||
'input': {'$eval': 'input'},
|
||||
'task': {'$eval': 'task'},
|
||||
'taskId': {'$eval': 'taskId'},
|
||||
'taskGroupId': {'$eval': 'taskGroupId'},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return rv
|
||||
|
||||
actions.append(Action(order, action_builder))
|
||||
|
||||
mem['registered'] = True
|
||||
callbacks[cb.__name__] = cb
|
||||
|
@ -186,32 +249,18 @@ def render_actions_json(parameters, graph_config):
|
|||
JSON object representation of the ``public/actions.json`` artifact.
|
||||
"""
|
||||
assert isinstance(parameters, Parameters), 'requires instance of Parameters'
|
||||
result = []
|
||||
actions = []
|
||||
for action in sorted(_get_actions(graph_config), key=lambda action: action.order):
|
||||
task = action.task_template_builder(parameters, graph_config)
|
||||
if task:
|
||||
assert is_json(task), 'task must be a JSON compatible object'
|
||||
res = {
|
||||
'kind': 'task',
|
||||
'name': action.name,
|
||||
'title': action.title,
|
||||
'description': action.description,
|
||||
'context': action.context,
|
||||
'schema': (
|
||||
action.schema(graph_config=graph_config) if callable(action.schema)
|
||||
else action.schema
|
||||
),
|
||||
'task': task,
|
||||
}
|
||||
if res['schema'] is None:
|
||||
res.pop('schema')
|
||||
result.append(res)
|
||||
action = action.action_builder(parameters, graph_config)
|
||||
if action:
|
||||
assert is_json(action), 'action must be a JSON compatible object'
|
||||
actions.append(action)
|
||||
return {
|
||||
'version': 1,
|
||||
'variables': {
|
||||
'parameters': dict(**parameters),
|
||||
},
|
||||
'actions': result,
|
||||
'actions': actions,
|
||||
}
|
||||
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче