From 593eebf062cd1f73c117502e0491f3c8532695a3 Mon Sep 17 00:00:00 2001 From: John Westcott IV <32551173+john-westcott-iv@users.noreply.github.com> Date: Thu, 24 Mar 2022 11:58:15 -0400 Subject: [PATCH] Adding awx_ as well as tower_ variable names for webhooks (#11925) Adding utility to ease testing webhooks from command line Modifying all variables to use a constants list of variable names --- awx/api/views/webhooks.py | 17 +- awx/main/constants.py | 5 + awx/main/models/jobs.py | 7 +- awx/main/models/mixins.py | 2 +- awx/main/models/unified_jobs.py | 12 +- .../functional/models/test_unified_job.py | 41 +++-- .../unit/models/test_unified_job_unit.py | 42 ++--- awx/main/tests/unit/test_tasks.py | 45 ++--- tools/scripts/post_webhook.py | 136 +++++++++++++++ .../scripts/webhook_examples/github_push.json | 156 ++++++++++++++++++ .../scripts/webhook_examples/gitlab_push.json | 71 ++++++++ 11 files changed, 438 insertions(+), 96 deletions(-) create mode 100755 tools/scripts/post_webhook.py create mode 100644 tools/scripts/webhook_examples/github_push.json create mode 100644 tools/scripts/webhook_examples/gitlab_push.json diff --git a/awx/api/views/webhooks.py b/awx/api/views/webhooks.py index c3d1604b0a..05e67d4fe2 100644 --- a/awx/api/views/webhooks.py +++ b/awx/api/views/webhooks.py @@ -16,7 +16,7 @@ from awx.api import serializers from awx.api.generics import APIView, GenericAPIView from awx.api.permissions import WebhookKeyPermission from awx.main.models import Job, JobTemplate, WorkflowJob, WorkflowJobTemplate - +from awx.main.constants import JOB_VARIABLE_PREFIXES logger = logging.getLogger('awx.api.views.webhooks') @@ -136,15 +136,16 @@ class WebhookReceiverBase(APIView): 'webhook_credential': obj.webhook_credential, 'webhook_guid': event_guid, }, - 'extra_vars': { - 'tower_webhook_event_type': event_type, - 'tower_webhook_event_guid': event_guid, - 'tower_webhook_event_ref': event_ref, - 'tower_webhook_status_api': status_api, - 'tower_webhook_payload': request.data, - }, + 'extra_vars': {}, } + for name in JOB_VARIABLE_PREFIXES: + kwargs['extra_vars']['{}_webhook_event_type'.format(name)] = event_type + kwargs['extra_vars']['{}_webhook_event_guid'.format(name)] = event_guid + kwargs['extra_vars']['{}_webhook_event_ref'.format(name)] = event_ref + kwargs['extra_vars']['{}_webhook_status_api'.format(name)] = status_api + kwargs['extra_vars']['{}_webhook_payload'.format(name)] = request.data + new_job = obj.create_unified_job(**kwargs) new_job.signal_start() diff --git a/awx/main/constants.py b/awx/main/constants.py index cda6dd3a67..a650beeeef 100644 --- a/awx/main/constants.py +++ b/awx/main/constants.py @@ -95,3 +95,8 @@ CONTAINER_VOLUMES_MOUNT_TYPES = ['z', 'O', 'ro', 'rw'] MAX_ISOLATED_PATH_COLON_DELIMITER = 2 SURVEY_TYPE_MAPPING = {'text': str, 'textarea': str, 'password': str, 'multiplechoice': str, 'multiselect': str, 'integer': int, 'float': (float, int)} + +JOB_VARIABLE_PREFIXES = [ + 'awx', + 'tower', +] diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 3b22ecd02c..2660c69c62 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -55,6 +55,7 @@ from awx.main.models.mixins import ( WebhookMixin, WebhookTemplateMixin, ) +from awx.main.constants import JOB_VARIABLE_PREFIXES logger = logging.getLogger('awx.main.models.jobs') @@ -770,14 +771,14 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana def awx_meta_vars(self): r = super(Job, self).awx_meta_vars() if self.project: - for name in ('awx', 'tower'): + for name in JOB_VARIABLE_PREFIXES: r['{}_project_revision'.format(name)] = self.project.scm_revision r['{}_project_scm_branch'.format(name)] = self.project.scm_branch if self.scm_branch: - for name in ('awx', 'tower'): + for name in JOB_VARIABLE_PREFIXES: r['{}_job_scm_branch'.format(name)] = self.scm_branch if self.job_template: - for name in ('awx', 'tower'): + for name in JOB_VARIABLE_PREFIXES: r['{}_job_template_id'.format(name)] = self.job_template.pk r['{}_job_template_name'.format(name)] = self.job_template.name return r diff --git a/awx/main/models/mixins.py b/awx/main/models/mixins.py index 94e737859b..dc144f96ec 100644 --- a/awx/main/models/mixins.py +++ b/awx/main/models/mixins.py @@ -583,7 +583,7 @@ class WebhookMixin(models.Model): if not self.webhook_credential: return - status_api = self.extra_vars_dict.get('tower_webhook_status_api') + status_api = self.extra_vars_dict.get('awx_webhook_status_api') if not status_api: logger.debug("Webhook event did not have a status API endpoint associated, skipping.") return diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index 65804c97b0..0202360d66 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -51,7 +51,7 @@ from awx.main.utils.common import ( ) from awx.main.utils.encryption import encrypt_dict, decrypt_field from awx.main.utils import polymorphic -from awx.main.constants import ACTIVE_STATES, CAN_CANCEL +from awx.main.constants import ACTIVE_STATES, CAN_CANCEL, JOB_VARIABLE_PREFIXES from awx.main.redact import UriCleaner, REPLACE_STR from awx.main.consumers import emit_channel_notification from awx.main.fields import AskForField, OrderedManyToManyField @@ -1450,7 +1450,7 @@ class UnifiedJob( by AWX, for purposes of client playbook hooks """ r = {} - for name in ('awx', 'tower'): + for name in JOB_VARIABLE_PREFIXES: r['{}_job_id'.format(name)] = self.pk r['{}_job_launch_type'.format(name)] = self.launch_type @@ -1459,7 +1459,7 @@ class UnifiedJob( wj = self.get_workflow_job() if wj: schedule = getattr_dne(wj, 'schedule') - for name in ('awx', 'tower'): + for name in JOB_VARIABLE_PREFIXES: r['{}_workflow_job_id'.format(name)] = wj.pk r['{}_workflow_job_name'.format(name)] = wj.name r['{}_workflow_job_launch_type'.format(name)] = wj.launch_type @@ -1470,12 +1470,12 @@ class UnifiedJob( if not created_by: schedule = getattr_dne(self, 'schedule') if schedule: - for name in ('awx', 'tower'): + for name in JOB_VARIABLE_PREFIXES: r['{}_schedule_id'.format(name)] = schedule.pk r['{}_schedule_name'.format(name)] = schedule.name if created_by: - for name in ('awx', 'tower'): + for name in JOB_VARIABLE_PREFIXES: r['{}_user_id'.format(name)] = created_by.pk r['{}_user_name'.format(name)] = created_by.username r['{}_user_email'.format(name)] = created_by.email @@ -1484,7 +1484,7 @@ class UnifiedJob( inventory = getattr_dne(self, 'inventory') if inventory: - for name in ('awx', 'tower'): + for name in JOB_VARIABLE_PREFIXES: r['{}_inventory_id'.format(name)] = inventory.pk r['{}_inventory_name'.format(name)] = inventory.name diff --git a/awx/main/tests/functional/models/test_unified_job.py b/awx/main/tests/functional/models/test_unified_job.py index 76bd14cf38..4d17d09440 100644 --- a/awx/main/tests/functional/models/test_unified_job.py +++ b/awx/main/tests/functional/models/test_unified_job.py @@ -10,6 +10,7 @@ from django.contrib.contenttypes.models import ContentType # AWX from awx.main.models import UnifiedJobTemplate, Job, JobTemplate, WorkflowJobTemplate, WorkflowApprovalTemplate, Project, WorkflowJob, Schedule, Credential from awx.api.versioning import reverse +from awx.main.constants import JOB_VARIABLE_PREFIXES @pytest.mark.django_db @@ -125,17 +126,19 @@ class TestMetaVars: workflow_job.workflow_nodes.create(job=job) data = job.awx_meta_vars() - assert data['awx_user_id'] == admin_user.id - assert data['awx_user_name'] == admin_user.username - assert data['awx_workflow_job_id'] == workflow_job.pk - assert data['awx_workflow_job_launch_type'] == workflow_job.launch_type + for name in JOB_VARIABLE_PREFIXES: + assert data['{}_user_id'.format(name)] == admin_user.id + assert data['{}_user_name'.format(name)] == admin_user.username + assert data['{}_workflow_job_id'.format(name)] == workflow_job.pk + assert data['{}_workflow_job_launch_type'.format(name)] == workflow_job.launch_type def test_scheduled_job_metavars(self, job_template, admin_user): schedule = Schedule.objects.create(name='job-schedule', rrule='DTSTART:20171129T155939z\nFREQ=MONTHLY', unified_job_template=job_template) job = Job.objects.create(name='fake-job', launch_type='workflow', schedule=schedule, job_template=job_template) data = job.awx_meta_vars() - assert data['awx_schedule_id'] == schedule.pk - assert 'awx_user_name' not in data + for name in JOB_VARIABLE_PREFIXES: + assert data['{}_schedule_id'.format(name)] == schedule.pk + assert '{}_user_name'.format(name) not in data def test_scheduled_workflow_job_node_metavars(self, workflow_job_template): schedule = Schedule.objects.create(name='job-schedule', rrule='DTSTART:20171129T155939z\nFREQ=MONTHLY', unified_job_template=workflow_job_template) @@ -144,22 +147,16 @@ class TestMetaVars: job = Job.objects.create(launch_type='workflow') workflow_job.workflow_nodes.create(job=job) - assert job.awx_meta_vars() == { - 'awx_job_id': job.id, - 'tower_job_id': job.id, - 'awx_job_launch_type': 'workflow', - 'tower_job_launch_type': 'workflow', - 'awx_workflow_job_name': 'workflow-job', - 'tower_workflow_job_name': 'workflow-job', - 'awx_workflow_job_id': workflow_job.id, - 'tower_workflow_job_id': workflow_job.id, - 'awx_workflow_job_launch_type': workflow_job.launch_type, - 'tower_workflow_job_launch_type': workflow_job.launch_type, - 'awx_parent_job_schedule_id': schedule.id, - 'tower_parent_job_schedule_id': schedule.id, - 'awx_parent_job_schedule_name': 'job-schedule', - 'tower_parent_job_schedule_name': 'job-schedule', - } + result_hash = {} + for name in JOB_VARIABLE_PREFIXES: + result_hash['{}_job_id'.format(name)] = job.id + result_hash['{}_job_launch_type'.format(name)] = 'workflow' + result_hash['{}_workflow_job_name'.format(name)] = 'workflow-job' + result_hash['{}_workflow_job_id'.format(name)] = workflow_job.id + result_hash['{}_workflow_job_launch_type'.format(name)] = workflow_job.launch_type + result_hash['{}_parent_job_schedule_id'.format(name)] = schedule.id + result_hash['{}_parent_job_schedule_name'.format(name)] = 'job-schedule' + assert job.awx_meta_vars() == result_hash @pytest.mark.django_db diff --git a/awx/main/tests/unit/models/test_unified_job_unit.py b/awx/main/tests/unit/models/test_unified_job_unit.py index c149953dc1..592c457b0c 100644 --- a/awx/main/tests/unit/models/test_unified_job_unit.py +++ b/awx/main/tests/unit/models/test_unified_job_unit.py @@ -2,6 +2,7 @@ import pytest from unittest import mock from awx.main.models import UnifiedJob, UnifiedJobTemplate, WorkflowJob, WorkflowJobNode, WorkflowApprovalTemplate, Job, User, Project, JobTemplate, Inventory +from awx.main.constants import JOB_VARIABLE_PREFIXES def test_incorrectly_formatted_variables(): @@ -83,26 +84,18 @@ class TestMetaVars: def test_job_metavars(self): maker = User(username='joe', pk=47, id=47) inv = Inventory(name='example-inv', id=45) - assert Job(name='fake-job', pk=42, id=42, launch_type='manual', created_by=maker, inventory=inv).awx_meta_vars() == { - 'tower_job_id': 42, - 'awx_job_id': 42, - 'tower_job_launch_type': 'manual', - 'awx_job_launch_type': 'manual', - 'awx_user_name': 'joe', - 'tower_user_name': 'joe', - 'awx_user_email': '', - 'tower_user_email': '', - 'awx_user_first_name': '', - 'tower_user_first_name': '', - 'awx_user_last_name': '', - 'tower_user_last_name': '', - 'awx_user_id': 47, - 'tower_user_id': 47, - 'tower_inventory_id': 45, - 'awx_inventory_id': 45, - 'tower_inventory_name': 'example-inv', - 'awx_inventory_name': 'example-inv', - } + result_hash = {} + for name in JOB_VARIABLE_PREFIXES: + result_hash['{}_job_id'.format(name)] = 42 + result_hash['{}_job_launch_type'.format(name)] = 'manual' + result_hash['{}_user_name'.format(name)] = 'joe' + result_hash['{}_user_email'.format(name)] = '' + result_hash['{}_user_first_name'.format(name)] = '' + result_hash['{}_user_last_name'.format(name)] = '' + result_hash['{}_user_id'.format(name)] = 47 + result_hash['{}_inventory_id'.format(name)] = 45 + result_hash['{}_inventory_name'.format(name)] = 'example-inv' + assert Job(name='fake-job', pk=42, id=42, launch_type='manual', created_by=maker, inventory=inv).awx_meta_vars() == result_hash def test_project_update_metavars(self): data = Job( @@ -113,7 +106,8 @@ class TestMetaVars: project=Project(name='jobs-sync', scm_revision='12345444'), job_template=JobTemplate(name='jobs-jt', id=92, pk=92), ).awx_meta_vars() - assert data['awx_project_revision'] == '12345444' - assert 'tower_job_template_id' in data - assert data['tower_job_template_id'] == 92 - assert data['tower_job_template_name'] == 'jobs-jt' + for name in JOB_VARIABLE_PREFIXES: + assert data['{}_project_revision'.format(name)] == '12345444' + assert '{}_job_template_id'.format(name) in data + assert data['{}_job_template_id'.format(name)] == 92 + assert data['{}_job_template_name'.format(name)] == 'jobs-jt' diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index 69a7f03c33..e0d0a33ce4 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -39,6 +39,7 @@ from awx.main.utils.safe_yaml import SafeLoader from awx.main.utils.execution_environments import CONTAINER_ROOT, to_host_path from awx.main.utils.licensing import Licenser +from awx.main.constants import JOB_VARIABLE_PREFIXES class TestJobExecution(object): @@ -363,32 +364,14 @@ class TestExtraVarSanitation(TestJobExecution): extra_vars = yaml.load(fd, Loader=SafeLoader) # ensure that strings are marked as unsafe - for unsafe in [ - 'awx_job_template_name', - 'tower_job_template_name', - 'awx_user_name', - 'tower_job_launch_type', - 'awx_project_revision', - 'tower_project_revision', - 'tower_user_name', - 'awx_job_launch_type', - 'awx_inventory_name', - 'tower_inventory_name', - ]: - assert hasattr(extra_vars[unsafe], '__UNSAFE__') + for name in JOB_VARIABLE_PREFIXES: + for variable_name in ['_job_template_name', '_user_name', '_job_launch_type', '_project_revision', '_inventory_name']: + assert hasattr(extra_vars['{}{}'.format(name, variable_name)], '__UNSAFE__') # ensure that non-strings are marked as safe - for safe in [ - 'awx_job_template_id', - 'awx_job_id', - 'awx_user_id', - 'tower_user_id', - 'tower_job_template_id', - 'tower_job_id', - 'awx_inventory_id', - 'tower_inventory_id', - ]: - assert not hasattr(extra_vars[safe], '__UNSAFE__') + for name in JOB_VARIABLE_PREFIXES: + for variable_name in ['_job_template_id', '_job_id', '_user_id', '_inventory_id']: + assert not hasattr(extra_vars['{}{}'.format(name, variable_name)], '__UNSAFE__') def test_launchtime_vars_unsafe(self, job, private_data_dir, mock_me): job.extra_vars = json.dumps({'msg': self.UNSAFE}) @@ -552,10 +535,9 @@ class TestGenericRun: call_args, _ = task._write_extra_vars_file.call_args_list[0] private_data_dir, extra_vars, safe_dict = call_args - assert extra_vars['tower_user_id'] == 123 - assert extra_vars['tower_user_name'] == "angry-spud" - assert extra_vars['awx_user_id'] == 123 - assert extra_vars['awx_user_name'] == "angry-spud" + for name in JOB_VARIABLE_PREFIXES: + assert extra_vars['{}_user_id'.format(name)] == 123 + assert extra_vars['{}_user_name'.format(name)] == "angry-spud" def test_survey_extra_vars(self, mock_me): job = Job() @@ -640,10 +622,9 @@ class TestAdhocRun(TestJobExecution): call_args, _ = task._write_extra_vars_file.call_args_list[0] private_data_dir, extra_vars = call_args - assert extra_vars['tower_user_id'] == 123 - assert extra_vars['tower_user_name'] == "angry-spud" - assert extra_vars['awx_user_id'] == 123 - assert extra_vars['awx_user_name'] == "angry-spud" + for name in JOB_VARIABLE_PREFIXES: + assert extra_vars['{}_user_id'.format(name)] == 123 + assert extra_vars['{}_user_name'.format(name)] == "angry-spud" class TestJobCredentials(TestJobExecution): diff --git a/tools/scripts/post_webhook.py b/tools/scripts/post_webhook.py new file mode 100755 index 0000000000..766a033bd1 --- /dev/null +++ b/tools/scripts/post_webhook.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python + +from hashlib import sha1 +from sys import exit +import click +import hmac +import http.client as http_client +import json +import logging +import requests +import urllib3 +import uuid + + +@click.command() +@click.option('--file', required=True, help='File containing the post data.') +@click.option('--key', "webhook_key", required=True, help='The webhook key for the job template.') +@click.option('--url', required=True, help='The webhook url for the job template (i.e. https://tower.jowestco.net:8043/api/v2/job_templates/637/github/.') +@click.option('--event-type', help='Specific value for Event header, defaults to "issues" for GitHub and "Push Hook" for GitLab') +@click.option('--verbose', is_flag=True, help='Dump HTTP communication for debugging') +@click.option('--insecure', is_flag=True, help='Ignore SSL certs if true') +def post_webhook(file, webhook_key, url, verbose, event_type, insecure): + """ + Helper command for submitting POST requests to Webhook endpoints. + + We have two sample webhooks in tools/scripts/webhook_examples for gitlab and github. + These or any other file can be pointed to with the --file parameter. + + \b + Additional example webhook events can be found online. + For GitLab see: + https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html + + \b + For GitHub see: + https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads + + \b + For setting up webhooks in AWX see: + https://docs.ansible.com/ansible-tower/latest/html/userguide/webhooks.html + + \b + Example usage for GitHub: + ./post_webhook.py \\ + --file webhook_examples/github_push.json \\ + --url https://tower.jowestco.net:8043/api/v2/job_templates/637/github/ \\ + --key AvqBR19JDFaLTsbF3p7FmiU9WpuHsJKdHDfTqKXyzv1HtwDGZ8 \\ + --insecure \\ + --type github + + \b + Example usage for GitLab: + ./post_webhook.py \\ + --file webhook_examples/gitlab_push.json \\ + --url https://tower.jowestco.net:8043/api/v2/job_templates/638/gitlab/ \\ + --key fZ8vUpfHfb1Dn7zHtyaAsyZC5IHFcZf2a2xiBc2jmrBDptCOL2 \\ + --insecure \\ + --type=gitlab + + \b + NOTE: GitLab webhooks are stored in the DB with a UID of the hash of the POST body. + After submitting one post GitLab post body a second POST of the same payload + can result in a response like: + Response code: 202 + Response body: + { + "message": "Webhook previously received, aborting." + } + + If you need to test multiple GitLab posts simply change your payload slightly + + """ + if insecure: + # Disable insecure warnings + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + if verbose: + # Enable HTTP debugging + http_client.HTTPConnection.debuglevel = 1 + # Configure logging + logging.basicConfig() + logging.getLogger().setLevel(logging.DEBUG) + requests_log = logging.getLogger("requests.packages.urllib3") + requests_log.setLevel(logging.DEBUG) + requests_log.propagate = True + + # read webhook payload + with open(file, 'r') as f: + post_data = json.loads(f.read()) + + # Construct Headers + headers = { + 'Content-Type': 'application/json', + } + + # Encode key and post_data + key_bytes = webhook_key.encode('utf-8', 'strict') + data_bytes = str(json.dumps(post_data)).encode('utf-8', 'strict') + + # Compute sha1 mac + mac = hmac.new(key_bytes, msg=data_bytes, digestmod=sha1) + + if url.endswith('/github/'): + headers.update( + { + 'X-Hub-Signature': 'sha1={}'.format(mac.hexdigest()), + 'X-GitHub-Event': 'issues' if event_type == 'default' else event_type, + 'X-GitHub-Delivery': str(uuid.uuid4()), + } + ) + elif url.endswith('/gitlab/'): + mac = hmac.new(key_bytes, msg=data_bytes, digestmod=sha1) + headers.update( + { + 'X-GitLab-Event': 'Push Hook' if event_type == 'default' else event_type, + 'X-GitLab-Token': webhook_key, + } + ) + else: + click.echo("This utility only knows how to support URLs that end in /github/ or /gitlab/.") + exit(250) + + # Make post + r = requests.post(url, data=json.dumps(post_data), headers=headers, verify=(not insecure)) + + if not verbose: + click.echo("Response code: {}".format(r.status_code)) + click.echo("Response body:") + try: + click.echo(json.dumps(r.json(), indent=4)) + except: + click.echo(r.text) + + +if __name__ == '__main__': + post_webhook() diff --git a/tools/scripts/webhook_examples/github_push.json b/tools/scripts/webhook_examples/github_push.json new file mode 100644 index 0000000000..4805c2881f --- /dev/null +++ b/tools/scripts/webhook_examples/github_push.json @@ -0,0 +1,156 @@ +{ + "ref": "refs/tags/simple-tag", + "before": "0000000000000000000000000000000000000000", + "after": "6113728f27ae82c7b1a177c8d03f9e96e0adf246", + "created": true, + "deleted": false, + "forced": false, + "base_ref": "refs/heads/main", + "compare": "https://github.com/Codertocat/Hello-World/compare/simple-tag", + "commits": [], + "head_commit": { + "id": "6113728f27ae82c7b1a177c8d03f9e96e0adf246", + "tree_id": "4b825dc642cb6eb9a060e54bf8d69288fbee4904", + "distinct": true, + "message": "Adding a .gitignore file", + "timestamp": "2019-05-15T15:20:41Z", + "url": "https://github.com/Codertocat/Hello-World/commit/6113728f27ae82c7b1a177c8d03f9e96e0adf246", + "author": { + "name": "Codertocat", + "email": "21031067+Codertocat@users.noreply.github.com", + "username": "Codertocat" + }, + "committer": { + "name": "Codertocat", + "email": "21031067+Codertocat@users.noreply.github.com", + "username": "Codertocat" + }, + "added": [ + ".gitignore" + ], + "removed": [], + "modified": [] + }, + "repository": { + "id": 186853002, + "node_id": "MDEwOlJlcG9zaXRvcnkxODY4NTMwMDI=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "private": false, + "owner": { + "name": "Codertocat", + "email": "21031067+Codertocat@users.noreply.github.com", + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://github.com/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": 1557933565, + "updated_at": "2019-05-15T15:20:41Z", + "pushed_at": 1557933657, + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Ruby", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 2, + "license": null, + "forks": 1, + "open_issues": 2, + "watchers": 0, + "default_branch": "master", + "stargazers": 0, + "master_branch": "master" + }, + "pusher": { + "name": "Codertocat", + "email": "21031067+Codertocat@users.noreply.github.com" + }, + "sender": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/tools/scripts/webhook_examples/gitlab_push.json b/tools/scripts/webhook_examples/gitlab_push.json new file mode 100644 index 0000000000..4389e11d1e --- /dev/null +++ b/tools/scripts/webhook_examples/gitlab_push.json @@ -0,0 +1,71 @@ +{ + "object_kind": "push", + "event_name": "push", + "before": "95790bf891e76fee5e1747ab589903a6a1f80f22", + "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "ref": "refs/heads/master", + "checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "user_id": 4, + "user_name": "John Smith", + "user_username": "jsmith", + "user_email": "john@example.com", + "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80", + "project_id": 15, + "project":{ + "id": 15, + "name":"Diaspora", + "description":"", + "web_url":"http://example.com/mike/diaspora", + "avatar_url":null, + "git_ssh_url":"git@example.com:mike/diaspora.git", + "git_http_url":"http://example.com/mike/diaspora.git", + "namespace":"Mike", + "visibility_level":0, + "path_with_namespace":"mike/diaspora", + "default_branch":"master", + "homepage":"http://example.com/mike/diaspora", + "url":"git@example.com:mike/diaspora.git", + "ssh_url":"git@example.com:mike/diaspora.git", + "http_url":"http://example.com/mike/diaspora.git" + }, + "repository":{ + "name": "Diaspora", + "url": "git@example.com:mike/diaspora.git", + "description": "", + "homepage": "http://example.com/mike/diaspora", + "git_http_url":"http://example.com/mike/diaspora.git", + "git_ssh_url":"git@example.com:mike/diaspora.git", + "visibility_level":0 + }, + "commits": [ + { + "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "message": "Update Catalan translation to e38cb41.\n\nSee https://gitlab.com/gitlab-org/gitlab for more information", + "title": "Update Catalan translation to e38cb41.", + "timestamp": "2011-12-12T14:27:31+02:00", + "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "author": { + "name": "Jordi Mallach", + "email": "jordi@softcatala.org" + }, + "added": ["CHANGELOG"], + "modified": ["app/controller/application.rb"], + "removed": [] + }, + { + "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "message": "fixed readme", + "title": "fixed readme", + "timestamp": "2012-01-03T23:36:29+02:00", + "url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "author": { + "name": "GitLab dev user", + "email": "gitlabdev@dv6700.(none)" + }, + "added": ["CHANGELOG"], + "modified": ["app/controller/application.rb"], + "removed": [] + } + ], + "total_commits_count": 4 +}