Skip to content
This repository has been archived by the owner on Nov 11, 2019. It is now read-only.

shipit/api: action hook support #1782

Merged
merged 2 commits into from
Jan 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions src/shipit/api/shipit_api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from shipit_api.tasks import UnsupportedFlavor
from shipit_api.tasks import fetch_actions_json
from shipit_api.tasks import generate_action_hook
from shipit_api.tasks import render_action_hook

logger = get_logger(__name__)

Expand Down Expand Up @@ -175,8 +176,18 @@ def schedule_phase(name, phase):
if not signoff.signed:
abort(400, 'Pending signoffs')

queue = get_service('queue')
queue.createTask(phase.task_id, phase.rendered)
task_or_hook = phase.task_json
if 'hook_payload' in task_or_hook:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this is correct. I don't know what's in task_or_hook or where it comes from.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is our internal structure.

hooks = get_service('hooks')
result = hooks.triggerHook(
task_or_hook['hook_group_id'],
task_or_hook['hook_id'],
phase.rendered_hook_payload,
)
phase.task_id = result['status']['taskId']
else:
queue = get_service('queue')
queue.createTask(phase.task_id, phase.rendered)

phase.submitted = True
phase.completed_by = g.userinfo['email']
Expand Down Expand Up @@ -211,18 +222,21 @@ def abandon_release(name):
continue

hook = generate_action_hook(
decision_task_id=phase.task_id,
task_group_id=phase.task_id,
action_name='cancel-all',
actions=actions,
input_={},
)
hook_payload_rendered = render_action_hook(
payload=hook['hook_payload'],
context=hook['context'],
delete_params=['existing_tasks', 'release_history', 'release_partner_config'],
)
# some parameters contain a lot of entries, so we hit the payload
# size limit. We don't use this parameter in any case, safe to
# remove
for long_param in ('existing_tasks', 'release_history', 'release_partner_config'):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, is this fixed now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just moved it to make the API file less crowded. :)

del hook['context']['parameters'][long_param]
logger.info('Cancel phase %s by hook %s', phase.name, hook)
logger.info('Cancel phase %s by hook %s with payload: %s',
phase.name, hook['hook_id'], hook_payload_rendered)
hooks = get_service('hooks')
res = hooks.triggerHook(hook['hook_group_id'], hook['hook_id'], hook['hook_payload'])
res = hooks.triggerHook(
hook['hook_group_id'], hook['hook_id'], hook_payload_rendered)
logger.debug('Done: %s', res)

r.status = 'aborted'
Expand Down
36 changes: 18 additions & 18 deletions src/shipit/api/shipit_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,22 +417,22 @@
# TODO: add other branches
# TODO: consider move this to secrets, aka per env config
SIGNOFFS = {
'projects/maple': {
'fennec': {
'ship_fennec': [
{
'name': '[relman] Ship Fennec',
'description': 'Publish Firefox for Android to Play Store',
# TODO: this group includes releng/relman/qa/etc, need to split or switch to real scopes
'permissions': 'vpn_cloudops_shipit',
},
{
'name': '[releng] Ship Fennec',
'description': 'Publish Firefox for Android to Play Store',
# XXX: stands for the LDAP group for now
'permissions': 'releng',
},
],
},
},
# 'projects/maple': {
# 'fennec': {
# 'ship_fennec': [
# {
# 'name': '[relman] Ship Fennec',
# 'description': 'Publish Firefox for Android to Play Store',
# # TODO: this group includes releng/relman/qa/etc, need to split or switch to real scopes
# 'permissions': 'vpn_cloudops_shipit',
# },
# {
# 'name': '[releng] Ship Fennec',
# 'description': 'Publish Firefox for Android to Play Store',
# # XXX: stands for the LDAP group for now
# 'permissions': 'releng',
# },
# ],
# },
# },
}
76 changes: 55 additions & 21 deletions src/shipit/api/shipit_api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
from shipit_api.tasks import fetch_actions_json
from shipit_api.tasks import find_action
from shipit_api.tasks import find_decision_task_id
from shipit_api.tasks import generate_action_hook
from shipit_api.tasks import generate_action_task
from shipit_api.tasks import render_action_hook
from shipit_api.tasks import render_action_task

log = get_logger(__name__)
Expand Down Expand Up @@ -92,14 +94,28 @@ def context_json(self):

@property
def rendered(self):
return render_action_task(self.task_json, self.context_json, self.task_id)
return render_action_task(self.task_json, self.context_json)

@property
def rendered_hook_payload(self):
context = self.context_json
previous_graph_ids = context['input']['previous_graph_ids']
# The first ID is always the decision task ID. We need to update the
# remaining tasks' IDs using their names.
decision_task_id, remaining = previous_graph_ids[0], previous_graph_ids[1:]
resolved_previous_graph_ids = [decision_task_id]
other_phases = {p.name: p.task_id for p in self.release.phases}
for phase_name in remaining:
resolved_previous_graph_ids.append(other_phases[phase_name])
context['input']['previous_graph_ids'] = resolved_previous_graph_ids
return render_action_hook(self.task_json['hook_payload'], context)

@property
def json(self):
return {
'name': self.name,
'submitted': self.submitted,
'actionTaskId': self.task_id,
'actionTaskId': self.task_id or '',
'created': self.created or '',
'completed': self.completed or '',
}
Expand Down Expand Up @@ -154,7 +170,6 @@ def phase_signoffs(branch, product, phase):
]

def generate_phases(self, partner_urls=None, github_token=None):
blob = []
phases = []
previous_graph_ids = [self.decision_task_id]
next_version = bump_version(self.version.replace('esr', ''))
Expand All @@ -179,25 +194,44 @@ def generate_phases(self, partner_urls=None, github_token=None):
'buildNumber': info['buildNumber'],
'locales': info['locales']
}
target_action = find_action('release-promotion', self.actions)
kind = target_action['kind']
for phase in self.release_promotion_flavors():
action_task_input = copy.deepcopy(input_common)
action_task_input['previous_graph_ids'] = list(previous_graph_ids)
action_task_input['release_promotion_flavor'] = phase['name']
action_task_id, action_task, context = generate_action_task(
decision_task_id=self.decision_task_id,
action_name='release-promotion',
action_task_input=action_task_input,
actions=self.actions,
)
blob.append({
'task_id': action_task_id,
'task': action_task,
'status': 'pending'
})
if phase['in_previous_graph_ids']:
previous_graph_ids.append(action_task_id)
phase_obj = Phase(
phase['name'], action_task_id, json.dumps(action_task), json.dumps(context))
input_ = copy.deepcopy(input_common)
input_['release_promotion_flavor'] = phase['name']
input_['previous_graph_ids'] = list(previous_graph_ids)
if kind == 'task':
action_task_id, action_task, context = generate_action_task(
decision_task_id=self.decision_task_id,
action_name='release-promotion',
input_=input_,
actions=self.actions,
)
if phase['in_previous_graph_ids']:
previous_graph_ids.append(action_task_id)
phase_obj = Phase(
phase['name'], action_task_id, json.dumps(action_task), json.dumps(context))
elif kind == 'hook':
hook = generate_action_hook(
task_group_id=self.decision_task_id,
action_name='release-promotion',
actions=self.actions,
input_=input_,
)
hook_no_context = {k: v for k, v in hook.items() if k != 'context'}
phase_obj = Phase(
name=phase['name'],
task_id='',
task=json.dumps(hook_no_context),
context=json.dumps(hook['context']),
)
# we need to update input_['previous_graph_ids'] later, because
# the task IDs cannot be set for hooks in advance
if phase['in_previous_graph_ids']:
previous_graph_ids.append(phase['name'])
else:
raise ValueError(f'Unsupported kind: {kind}')

phase_obj.signoffs = self.phase_signoffs(self.branch, self.product, phase['name'])
phases.append(phase_obj)
self.phases = phases
Expand Down
25 changes: 17 additions & 8 deletions src/shipit/api/shipit_api/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ def extract_our_flavors(avail_flavors, product, version, partial_updates):
return SUPPORTED_FLAVORS[product_key]


def generate_action_task(decision_task_id, action_name, action_task_input, actions):
def generate_action_task(decision_task_id, action_name, input_, actions):
target_action = find_action(action_name, actions)
context = copy.deepcopy(actions['variables']) # parameters
action_task_id = slugid.nice().decode('utf-8')
context.update({
'input': action_task_input,
'input': input_,
'taskGroupId': decision_task_id,
'ownTaskId': action_task_id,
'taskId': None,
Expand All @@ -122,24 +122,33 @@ def generate_action_task(decision_task_id, action_name, action_task_input, actio
return action_task_id, action_task, context


def render_action_task(task, context, action_task_id):
def render_action_task(task, context):
action_task = jsone.render(task, context)
return action_task


def generate_action_hook(decision_task_id, action_name, actions):
def generate_action_hook(task_group_id, action_name, actions, input_):
target_action = find_action(action_name, actions)
context = copy.deepcopy(actions['variables']) # parameters
context.update({
'input': {},
'taskGroupId': decision_task_id,
'taskGroupId': task_group_id,
'taskId': None,
'task': None,
'input': input_,
})
hook_payload = jsone.render(target_action['hookPayload'], context)
return dict(
hook_group_id=target_action['hookGroupId'],
hook_id=target_action['hookId'],
hook_payload=hook_payload,
hook_payload=target_action['hookPayload'],
context=context,
)


def render_action_hook(payload, context, delete_params=[]):
rendered_payload = jsone.render(payload, context)
# some parameters contain a lot of entries, so we hit the payload
# size limit. We don't use this parameter in any case, safe to
# remove
for param in delete_params:
del rendered_payload['decision']['parameters'][param]
return rendered_payload