From a8afbd1ca35dee89f57ecc35d0db29a291b3738b Mon Sep 17 00:00:00 2001 From: Peter Braun Date: Tue, 14 Apr 2026 16:00:59 +0200 Subject: [PATCH] Aap 45980 (#16395) * support bitbucket_dc webhooks * add test * update docs --- .../plugins/modules/job_template.py | 3 +- .../plugins/modules/workflow_job_template.py | 3 +- awx_collection/test/awx/test_webhooks.py | 124 ++++++++++++++++++ 3 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 awx_collection/test/awx/test_webhooks.py diff --git a/awx_collection/plugins/modules/job_template.py b/awx_collection/plugins/modules/job_template.py index 3ba9883f29..7482d8a5a9 100644 --- a/awx_collection/plugins/modules/job_template.py +++ b/awx_collection/plugins/modules/job_template.py @@ -276,6 +276,7 @@ options: - '' - 'github' - 'gitlab' + - 'bitbucket_dc' webhook_credential: description: - Personal Access Token for posting back the status to the service API @@ -436,7 +437,7 @@ def main(): scm_branch=dict(), ask_scm_branch_on_launch=dict(type='bool'), job_slice_count=dict(type='int'), - webhook_service=dict(choices=['github', 'gitlab', '']), + webhook_service=dict(choices=['github', 'gitlab', 'bitbucket_dc', '']), webhook_credential=dict(), labels=dict(type="list", elements='str'), notification_templates_started=dict(type="list", elements='str'), diff --git a/awx_collection/plugins/modules/workflow_job_template.py b/awx_collection/plugins/modules/workflow_job_template.py index 8d3792e0ac..398257df7d 100644 --- a/awx_collection/plugins/modules/workflow_job_template.py +++ b/awx_collection/plugins/modules/workflow_job_template.py @@ -117,6 +117,7 @@ options: choices: - github - gitlab + - bitbucket_dc webhook_credential: description: - Personal Access Token for posting back the status to the service API @@ -828,7 +829,7 @@ def main(): ask_inventory_on_launch=dict(type='bool'), ask_scm_branch_on_launch=dict(type='bool'), ask_limit_on_launch=dict(type='bool'), - webhook_service=dict(choices=['github', 'gitlab']), + webhook_service=dict(choices=['github', 'gitlab', 'bitbucket_dc']), webhook_credential=dict(), labels=dict(type="list", elements='str'), notification_templates_started=dict(type="list", elements='str'), diff --git a/awx_collection/test/awx/test_webhooks.py b/awx_collection/test/awx/test_webhooks.py new file mode 100644 index 0000000000..171023a948 --- /dev/null +++ b/awx_collection/test/awx/test_webhooks.py @@ -0,0 +1,124 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import pytest + +from awx.main.models import JobTemplate, WorkflowJobTemplate + + +# The backend supports these webhook services on job/workflow templates +# (see awx/main/models/mixins.py). The collection modules must accept all of +# them in their argument_spec ``choices`` list. This test guards against the +# module's choices drifting from the backend -- see AAP-45980, where +# ``bitbucket_dc`` had been supported by the API since migration 0188 but was +# still being rejected by the job_template/workflow_job_template modules. +WEBHOOK_SERVICES = ['github', 'gitlab', 'bitbucket_dc'] + + +@pytest.mark.django_db +@pytest.mark.parametrize('webhook_service', WEBHOOK_SERVICES) +def test_job_template_accepts_webhook_service(run_module, admin_user, project, inventory, webhook_service): + result = run_module( + 'job_template', + { + 'name': 'foo', + 'playbook': 'helloworld.yml', + 'project': project.name, + 'inventory': inventory.name, + 'webhook_service': webhook_service, + 'state': 'present', + }, + admin_user, + ) + + assert not result.get('failed', False), result.get('msg', result) + assert result.get('changed', False), result + + jt = JobTemplate.objects.get(name='foo') + assert jt.webhook_service == webhook_service + + # Re-running with the same args must be a no-op (idempotence). + result = run_module( + 'job_template', + { + 'name': 'foo', + 'playbook': 'helloworld.yml', + 'project': project.name, + 'inventory': inventory.name, + 'webhook_service': webhook_service, + 'state': 'present', + }, + admin_user, + ) + assert not result.get('failed', False), result.get('msg', result) + assert not result.get('changed', True), result + + +@pytest.mark.django_db +@pytest.mark.parametrize('webhook_service', WEBHOOK_SERVICES) +def test_workflow_job_template_accepts_webhook_service(run_module, admin_user, organization, webhook_service): + result = run_module( + 'workflow_job_template', + { + 'name': 'foo-workflow', + 'organization': organization.name, + 'webhook_service': webhook_service, + 'state': 'present', + }, + admin_user, + ) + + assert not result.get('failed', False), result.get('msg', result) + assert result.get('changed', False), result + + wfjt = WorkflowJobTemplate.objects.get(name='foo-workflow') + assert wfjt.webhook_service == webhook_service + + # Re-running with the same args must be a no-op (idempotence). + result = run_module( + 'workflow_job_template', + { + 'name': 'foo-workflow', + 'organization': organization.name, + 'webhook_service': webhook_service, + 'state': 'present', + }, + admin_user, + ) + assert not result.get('failed', False), result.get('msg', result) + assert not result.get('changed', True), result + + +@pytest.mark.django_db +def test_job_template_rejects_unknown_webhook_service(run_module, admin_user, project, inventory): + result = run_module( + 'job_template', + { + 'name': 'foo', + 'playbook': 'helloworld.yml', + 'project': project.name, + 'inventory': inventory.name, + 'webhook_service': 'not_a_real_service', + 'state': 'present', + }, + admin_user, + ) + assert result.get('failed', False), result + assert 'webhook_service' in result.get('msg', '') + + +@pytest.mark.django_db +def test_workflow_job_template_rejects_unknown_webhook_service(run_module, admin_user, organization): + result = run_module( + 'workflow_job_template', + { + 'name': 'foo-workflow', + 'organization': organization.name, + 'webhook_service': 'not_a_real_service', + 'state': 'present', + }, + admin_user, + ) + assert result.get('failed', False), result + assert 'webhook_service' in result.get('msg', '')