diff --git a/src/shipit/api/shipit_api/api.py b/src/shipit/api/shipit_api/api.py index 95121786e2..7c18713427 100644 --- a/src/shipit/api/shipit_api/api.py +++ b/src/shipit/api/shipit_api/api.py @@ -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__) @@ -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: + 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'] @@ -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'): - 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' diff --git a/src/shipit/api/shipit_api/config.py b/src/shipit/api/shipit_api/config.py index f777a6b644..896ccc1412 100644 --- a/src/shipit/api/shipit_api/config.py +++ b/src/shipit/api/shipit_api/config.py @@ -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', + # }, + # ], + # }, + # }, } diff --git a/src/shipit/api/shipit_api/models.py b/src/shipit/api/shipit_api/models.py index 6e4bdbc546..f638f2de40 100644 --- a/src/shipit/api/shipit_api/models.py +++ b/src/shipit/api/shipit_api/models.py @@ -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__) @@ -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 '', } @@ -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', '')) @@ -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 diff --git a/src/shipit/api/shipit_api/tasks.py b/src/shipit/api/shipit_api/tasks.py index 8daabc0ef1..646e6393b3 100644 --- a/src/shipit/api/shipit_api/tasks.py +++ b/src/shipit/api/shipit_api/tasks.py @@ -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, @@ -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