mirror of
https://github.com/ansible/awx.git
synced 2026-03-07 19:51:08 -03:30
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
This commit is contained in:
@@ -16,7 +16,7 @@ from awx.api import serializers
|
|||||||
from awx.api.generics import APIView, GenericAPIView
|
from awx.api.generics import APIView, GenericAPIView
|
||||||
from awx.api.permissions import WebhookKeyPermission
|
from awx.api.permissions import WebhookKeyPermission
|
||||||
from awx.main.models import Job, JobTemplate, WorkflowJob, WorkflowJobTemplate
|
from awx.main.models import Job, JobTemplate, WorkflowJob, WorkflowJobTemplate
|
||||||
|
from awx.main.constants import JOB_VARIABLE_PREFIXES
|
||||||
|
|
||||||
logger = logging.getLogger('awx.api.views.webhooks')
|
logger = logging.getLogger('awx.api.views.webhooks')
|
||||||
|
|
||||||
@@ -136,15 +136,16 @@ class WebhookReceiverBase(APIView):
|
|||||||
'webhook_credential': obj.webhook_credential,
|
'webhook_credential': obj.webhook_credential,
|
||||||
'webhook_guid': event_guid,
|
'webhook_guid': event_guid,
|
||||||
},
|
},
|
||||||
'extra_vars': {
|
'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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = obj.create_unified_job(**kwargs)
|
||||||
new_job.signal_start()
|
new_job.signal_start()
|
||||||
|
|
||||||
|
|||||||
@@ -95,3 +95,8 @@ CONTAINER_VOLUMES_MOUNT_TYPES = ['z', 'O', 'ro', 'rw']
|
|||||||
MAX_ISOLATED_PATH_COLON_DELIMITER = 2
|
MAX_ISOLATED_PATH_COLON_DELIMITER = 2
|
||||||
|
|
||||||
SURVEY_TYPE_MAPPING = {'text': str, 'textarea': str, 'password': str, 'multiplechoice': str, 'multiselect': str, 'integer': int, 'float': (float, int)}
|
SURVEY_TYPE_MAPPING = {'text': str, 'textarea': str, 'password': str, 'multiplechoice': str, 'multiselect': str, 'integer': int, 'float': (float, int)}
|
||||||
|
|
||||||
|
JOB_VARIABLE_PREFIXES = [
|
||||||
|
'awx',
|
||||||
|
'tower',
|
||||||
|
]
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ from awx.main.models.mixins import (
|
|||||||
WebhookMixin,
|
WebhookMixin,
|
||||||
WebhookTemplateMixin,
|
WebhookTemplateMixin,
|
||||||
)
|
)
|
||||||
|
from awx.main.constants import JOB_VARIABLE_PREFIXES
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.models.jobs')
|
logger = logging.getLogger('awx.main.models.jobs')
|
||||||
@@ -770,14 +771,14 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana
|
|||||||
def awx_meta_vars(self):
|
def awx_meta_vars(self):
|
||||||
r = super(Job, self).awx_meta_vars()
|
r = super(Job, self).awx_meta_vars()
|
||||||
if self.project:
|
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_revision'.format(name)] = self.project.scm_revision
|
||||||
r['{}_project_scm_branch'.format(name)] = self.project.scm_branch
|
r['{}_project_scm_branch'.format(name)] = self.project.scm_branch
|
||||||
if self.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
|
r['{}_job_scm_branch'.format(name)] = self.scm_branch
|
||||||
if self.job_template:
|
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_id'.format(name)] = self.job_template.pk
|
||||||
r['{}_job_template_name'.format(name)] = self.job_template.name
|
r['{}_job_template_name'.format(name)] = self.job_template.name
|
||||||
return r
|
return r
|
||||||
|
|||||||
@@ -583,7 +583,7 @@ class WebhookMixin(models.Model):
|
|||||||
if not self.webhook_credential:
|
if not self.webhook_credential:
|
||||||
return
|
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:
|
if not status_api:
|
||||||
logger.debug("Webhook event did not have a status API endpoint associated, skipping.")
|
logger.debug("Webhook event did not have a status API endpoint associated, skipping.")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ from awx.main.utils.common import (
|
|||||||
)
|
)
|
||||||
from awx.main.utils.encryption import encrypt_dict, decrypt_field
|
from awx.main.utils.encryption import encrypt_dict, decrypt_field
|
||||||
from awx.main.utils import polymorphic
|
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.redact import UriCleaner, REPLACE_STR
|
||||||
from awx.main.consumers import emit_channel_notification
|
from awx.main.consumers import emit_channel_notification
|
||||||
from awx.main.fields import AskForField, OrderedManyToManyField
|
from awx.main.fields import AskForField, OrderedManyToManyField
|
||||||
@@ -1450,7 +1450,7 @@ class UnifiedJob(
|
|||||||
by AWX, for purposes of client playbook hooks
|
by AWX, for purposes of client playbook hooks
|
||||||
"""
|
"""
|
||||||
r = {}
|
r = {}
|
||||||
for name in ('awx', 'tower'):
|
for name in JOB_VARIABLE_PREFIXES:
|
||||||
r['{}_job_id'.format(name)] = self.pk
|
r['{}_job_id'.format(name)] = self.pk
|
||||||
r['{}_job_launch_type'.format(name)] = self.launch_type
|
r['{}_job_launch_type'.format(name)] = self.launch_type
|
||||||
|
|
||||||
@@ -1459,7 +1459,7 @@ class UnifiedJob(
|
|||||||
wj = self.get_workflow_job()
|
wj = self.get_workflow_job()
|
||||||
if wj:
|
if wj:
|
||||||
schedule = getattr_dne(wj, 'schedule')
|
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_id'.format(name)] = wj.pk
|
||||||
r['{}_workflow_job_name'.format(name)] = wj.name
|
r['{}_workflow_job_name'.format(name)] = wj.name
|
||||||
r['{}_workflow_job_launch_type'.format(name)] = wj.launch_type
|
r['{}_workflow_job_launch_type'.format(name)] = wj.launch_type
|
||||||
@@ -1470,12 +1470,12 @@ class UnifiedJob(
|
|||||||
if not created_by:
|
if not created_by:
|
||||||
schedule = getattr_dne(self, 'schedule')
|
schedule = getattr_dne(self, 'schedule')
|
||||||
if schedule:
|
if schedule:
|
||||||
for name in ('awx', 'tower'):
|
for name in JOB_VARIABLE_PREFIXES:
|
||||||
r['{}_schedule_id'.format(name)] = schedule.pk
|
r['{}_schedule_id'.format(name)] = schedule.pk
|
||||||
r['{}_schedule_name'.format(name)] = schedule.name
|
r['{}_schedule_name'.format(name)] = schedule.name
|
||||||
|
|
||||||
if created_by:
|
if created_by:
|
||||||
for name in ('awx', 'tower'):
|
for name in JOB_VARIABLE_PREFIXES:
|
||||||
r['{}_user_id'.format(name)] = created_by.pk
|
r['{}_user_id'.format(name)] = created_by.pk
|
||||||
r['{}_user_name'.format(name)] = created_by.username
|
r['{}_user_name'.format(name)] = created_by.username
|
||||||
r['{}_user_email'.format(name)] = created_by.email
|
r['{}_user_email'.format(name)] = created_by.email
|
||||||
@@ -1484,7 +1484,7 @@ class UnifiedJob(
|
|||||||
|
|
||||||
inventory = getattr_dne(self, 'inventory')
|
inventory = getattr_dne(self, 'inventory')
|
||||||
if inventory:
|
if inventory:
|
||||||
for name in ('awx', 'tower'):
|
for name in JOB_VARIABLE_PREFIXES:
|
||||||
r['{}_inventory_id'.format(name)] = inventory.pk
|
r['{}_inventory_id'.format(name)] = inventory.pk
|
||||||
r['{}_inventory_name'.format(name)] = inventory.name
|
r['{}_inventory_name'.format(name)] = inventory.name
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import UnifiedJobTemplate, Job, JobTemplate, WorkflowJobTemplate, WorkflowApprovalTemplate, Project, WorkflowJob, Schedule, Credential
|
from awx.main.models import UnifiedJobTemplate, Job, JobTemplate, WorkflowJobTemplate, WorkflowApprovalTemplate, Project, WorkflowJob, Schedule, Credential
|
||||||
from awx.api.versioning import reverse
|
from awx.api.versioning import reverse
|
||||||
|
from awx.main.constants import JOB_VARIABLE_PREFIXES
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -125,17 +126,19 @@ class TestMetaVars:
|
|||||||
|
|
||||||
workflow_job.workflow_nodes.create(job=job)
|
workflow_job.workflow_nodes.create(job=job)
|
||||||
data = job.awx_meta_vars()
|
data = job.awx_meta_vars()
|
||||||
assert data['awx_user_id'] == admin_user.id
|
for name in JOB_VARIABLE_PREFIXES:
|
||||||
assert data['awx_user_name'] == admin_user.username
|
assert data['{}_user_id'.format(name)] == admin_user.id
|
||||||
assert data['awx_workflow_job_id'] == workflow_job.pk
|
assert data['{}_user_name'.format(name)] == admin_user.username
|
||||||
assert data['awx_workflow_job_launch_type'] == workflow_job.launch_type
|
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):
|
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)
|
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)
|
job = Job.objects.create(name='fake-job', launch_type='workflow', schedule=schedule, job_template=job_template)
|
||||||
data = job.awx_meta_vars()
|
data = job.awx_meta_vars()
|
||||||
assert data['awx_schedule_id'] == schedule.pk
|
for name in JOB_VARIABLE_PREFIXES:
|
||||||
assert 'awx_user_name' not in data
|
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):
|
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)
|
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')
|
job = Job.objects.create(launch_type='workflow')
|
||||||
workflow_job.workflow_nodes.create(job=job)
|
workflow_job.workflow_nodes.create(job=job)
|
||||||
assert job.awx_meta_vars() == {
|
result_hash = {}
|
||||||
'awx_job_id': job.id,
|
for name in JOB_VARIABLE_PREFIXES:
|
||||||
'tower_job_id': job.id,
|
result_hash['{}_job_id'.format(name)] = job.id
|
||||||
'awx_job_launch_type': 'workflow',
|
result_hash['{}_job_launch_type'.format(name)] = 'workflow'
|
||||||
'tower_job_launch_type': 'workflow',
|
result_hash['{}_workflow_job_name'.format(name)] = 'workflow-job'
|
||||||
'awx_workflow_job_name': 'workflow-job',
|
result_hash['{}_workflow_job_id'.format(name)] = workflow_job.id
|
||||||
'tower_workflow_job_name': 'workflow-job',
|
result_hash['{}_workflow_job_launch_type'.format(name)] = workflow_job.launch_type
|
||||||
'awx_workflow_job_id': workflow_job.id,
|
result_hash['{}_parent_job_schedule_id'.format(name)] = schedule.id
|
||||||
'tower_workflow_job_id': workflow_job.id,
|
result_hash['{}_parent_job_schedule_name'.format(name)] = 'job-schedule'
|
||||||
'awx_workflow_job_launch_type': workflow_job.launch_type,
|
assert job.awx_meta_vars() == result_hash
|
||||||
'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',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import pytest
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from awx.main.models import UnifiedJob, UnifiedJobTemplate, WorkflowJob, WorkflowJobNode, WorkflowApprovalTemplate, Job, User, Project, JobTemplate, Inventory
|
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():
|
def test_incorrectly_formatted_variables():
|
||||||
@@ -83,26 +84,18 @@ class TestMetaVars:
|
|||||||
def test_job_metavars(self):
|
def test_job_metavars(self):
|
||||||
maker = User(username='joe', pk=47, id=47)
|
maker = User(username='joe', pk=47, id=47)
|
||||||
inv = Inventory(name='example-inv', id=45)
|
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() == {
|
result_hash = {}
|
||||||
'tower_job_id': 42,
|
for name in JOB_VARIABLE_PREFIXES:
|
||||||
'awx_job_id': 42,
|
result_hash['{}_job_id'.format(name)] = 42
|
||||||
'tower_job_launch_type': 'manual',
|
result_hash['{}_job_launch_type'.format(name)] = 'manual'
|
||||||
'awx_job_launch_type': 'manual',
|
result_hash['{}_user_name'.format(name)] = 'joe'
|
||||||
'awx_user_name': 'joe',
|
result_hash['{}_user_email'.format(name)] = ''
|
||||||
'tower_user_name': 'joe',
|
result_hash['{}_user_first_name'.format(name)] = ''
|
||||||
'awx_user_email': '',
|
result_hash['{}_user_last_name'.format(name)] = ''
|
||||||
'tower_user_email': '',
|
result_hash['{}_user_id'.format(name)] = 47
|
||||||
'awx_user_first_name': '',
|
result_hash['{}_inventory_id'.format(name)] = 45
|
||||||
'tower_user_first_name': '',
|
result_hash['{}_inventory_name'.format(name)] = 'example-inv'
|
||||||
'awx_user_last_name': '',
|
assert Job(name='fake-job', pk=42, id=42, launch_type='manual', created_by=maker, inventory=inv).awx_meta_vars() == result_hash
|
||||||
'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',
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_project_update_metavars(self):
|
def test_project_update_metavars(self):
|
||||||
data = Job(
|
data = Job(
|
||||||
@@ -113,7 +106,8 @@ class TestMetaVars:
|
|||||||
project=Project(name='jobs-sync', scm_revision='12345444'),
|
project=Project(name='jobs-sync', scm_revision='12345444'),
|
||||||
job_template=JobTemplate(name='jobs-jt', id=92, pk=92),
|
job_template=JobTemplate(name='jobs-jt', id=92, pk=92),
|
||||||
).awx_meta_vars()
|
).awx_meta_vars()
|
||||||
assert data['awx_project_revision'] == '12345444'
|
for name in JOB_VARIABLE_PREFIXES:
|
||||||
assert 'tower_job_template_id' in data
|
assert data['{}_project_revision'.format(name)] == '12345444'
|
||||||
assert data['tower_job_template_id'] == 92
|
assert '{}_job_template_id'.format(name) in data
|
||||||
assert data['tower_job_template_name'] == 'jobs-jt'
|
assert data['{}_job_template_id'.format(name)] == 92
|
||||||
|
assert data['{}_job_template_name'.format(name)] == 'jobs-jt'
|
||||||
|
|||||||
@@ -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.execution_environments import CONTAINER_ROOT, to_host_path
|
||||||
|
|
||||||
from awx.main.utils.licensing import Licenser
|
from awx.main.utils.licensing import Licenser
|
||||||
|
from awx.main.constants import JOB_VARIABLE_PREFIXES
|
||||||
|
|
||||||
|
|
||||||
class TestJobExecution(object):
|
class TestJobExecution(object):
|
||||||
@@ -363,32 +364,14 @@ class TestExtraVarSanitation(TestJobExecution):
|
|||||||
extra_vars = yaml.load(fd, Loader=SafeLoader)
|
extra_vars = yaml.load(fd, Loader=SafeLoader)
|
||||||
|
|
||||||
# ensure that strings are marked as unsafe
|
# ensure that strings are marked as unsafe
|
||||||
for unsafe in [
|
for name in JOB_VARIABLE_PREFIXES:
|
||||||
'awx_job_template_name',
|
for variable_name in ['_job_template_name', '_user_name', '_job_launch_type', '_project_revision', '_inventory_name']:
|
||||||
'tower_job_template_name',
|
assert hasattr(extra_vars['{}{}'.format(name, variable_name)], '__UNSAFE__')
|
||||||
'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__')
|
|
||||||
|
|
||||||
# ensure that non-strings are marked as safe
|
# ensure that non-strings are marked as safe
|
||||||
for safe in [
|
for name in JOB_VARIABLE_PREFIXES:
|
||||||
'awx_job_template_id',
|
for variable_name in ['_job_template_id', '_job_id', '_user_id', '_inventory_id']:
|
||||||
'awx_job_id',
|
assert not hasattr(extra_vars['{}{}'.format(name, variable_name)], '__UNSAFE__')
|
||||||
'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__')
|
|
||||||
|
|
||||||
def test_launchtime_vars_unsafe(self, job, private_data_dir, mock_me):
|
def test_launchtime_vars_unsafe(self, job, private_data_dir, mock_me):
|
||||||
job.extra_vars = json.dumps({'msg': self.UNSAFE})
|
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]
|
call_args, _ = task._write_extra_vars_file.call_args_list[0]
|
||||||
|
|
||||||
private_data_dir, extra_vars, safe_dict = call_args
|
private_data_dir, extra_vars, safe_dict = call_args
|
||||||
assert extra_vars['tower_user_id'] == 123
|
for name in JOB_VARIABLE_PREFIXES:
|
||||||
assert extra_vars['tower_user_name'] == "angry-spud"
|
assert extra_vars['{}_user_id'.format(name)] == 123
|
||||||
assert extra_vars['awx_user_id'] == 123
|
assert extra_vars['{}_user_name'.format(name)] == "angry-spud"
|
||||||
assert extra_vars['awx_user_name'] == "angry-spud"
|
|
||||||
|
|
||||||
def test_survey_extra_vars(self, mock_me):
|
def test_survey_extra_vars(self, mock_me):
|
||||||
job = Job()
|
job = Job()
|
||||||
@@ -640,10 +622,9 @@ class TestAdhocRun(TestJobExecution):
|
|||||||
call_args, _ = task._write_extra_vars_file.call_args_list[0]
|
call_args, _ = task._write_extra_vars_file.call_args_list[0]
|
||||||
|
|
||||||
private_data_dir, extra_vars = call_args
|
private_data_dir, extra_vars = call_args
|
||||||
assert extra_vars['tower_user_id'] == 123
|
for name in JOB_VARIABLE_PREFIXES:
|
||||||
assert extra_vars['tower_user_name'] == "angry-spud"
|
assert extra_vars['{}_user_id'.format(name)] == 123
|
||||||
assert extra_vars['awx_user_id'] == 123
|
assert extra_vars['{}_user_name'.format(name)] == "angry-spud"
|
||||||
assert extra_vars['awx_user_name'] == "angry-spud"
|
|
||||||
|
|
||||||
|
|
||||||
class TestJobCredentials(TestJobExecution):
|
class TestJobCredentials(TestJobExecution):
|
||||||
|
|||||||
136
tools/scripts/post_webhook.py
Executable file
136
tools/scripts/post_webhook.py
Executable file
@@ -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()
|
||||||
156
tools/scripts/webhook_examples/github_push.json
Normal file
156
tools/scripts/webhook_examples/github_push.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
71
tools/scripts/webhook_examples/gitlab_push.json
Normal file
71
tools/scripts/webhook_examples/gitlab_push.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user