Skip to content
Open
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
50 changes: 35 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ env:
DEV_DOCKER_OWNER: ${{ github.repository_owner }}
COMPOSE_TAG: ${{ github.base_ref || github.ref_name || 'devel' }}
UPSTREAM_REPOSITORY_ID: 91594105
COLLECTION_NAMESPACE: ${{ github.repository == 'ansible/tower' && 'ansible' || 'awx' }}
COLLECTION_PACKAGE: ${{ github.repository == 'ansible/tower' && 'controller' || 'awx' }}
on:
pull_request:
push:
Expand Down Expand Up @@ -245,6 +247,13 @@ jobs:
DEV_DOCKER_TAG_BASE: local
HEADLESS: yes

- name: Convert awx-operator playbooks to ansible.controller FQCN
if: github.repository == 'ansible/tower'
working-directory: awx-operator
run: |
find molecule/ -type f \( -name '*.yml' -o -name '*.yaml' \) \
-exec sed -i 's/awx\.awx/ansible.controller/g' {} +

- name: Run test deployment with awx-operator
working-directory: awx-operator
id: awx_operator_test
Expand All @@ -271,6 +280,7 @@ jobs:
AWX_TEST_VERSION: ci
AWX_EE_TEST_IMAGE: quay.io/ansible/awx-ee:latest
STORE_DEBUG_OUTPUT: true
CONTROLLER_OPTIONAL_API_URLPATTERN_PREFIX: /api/

- name: Collect awx-operator logs on timeout
# Only run on timeout; normal failures should use molecule's built-in log collection.
Expand Down Expand Up @@ -311,20 +321,28 @@ jobs:
- stable-2.17
# - devel
steps:
- name: Verify FQCN conversion was applied
if: github.repository == 'ansible/tower'
uses: actions/checkout@v4
with:
persist-credentials: false
show-progress: false

- name: Check for unconverted references
if: github.repository == 'ansible/tower'
run: |
if grep -rq 'awx\.awx' awx_collection/; then
echo "::error::Found unconverted awx.awx references. Run: ./awx_collection/tools/replace_fqcn.sh"
grep -rn 'awx\.awx' awx_collection/
exit 1
fi

- name: Perform sanity testing
uses: ansible-community/ansible-test-gh-action@release/v1
with:
ansible-core-version: ${{ matrix.ansible }}
codecov-token: ${{ secrets.CODECOV_TOKEN }}
collection-root: awx_collection
pre-test-cmd: >-
ansible-playbook
-i localhost,
tools/template_galaxy.yml
-e collection_package=awx
-e collection_namespace=awx
-e collection_version=1.0.0
-e '{"awx_template_version": false}'
testing-type: sanity

- name: Upload awx jUnit test reports to the unified dashboard
Expand Down Expand Up @@ -394,11 +412,13 @@ jobs:
echo 'username = admin' >> ~/.tower_cli.cfg
echo 'password = password' >> ~/.tower_cli.cfg
echo 'verify_ssl = false' >> ~/.tower_cli.cfg
echo 'api_prefix = /api/' >> ~/.tower_cli.cfg
TARGETS="$(ls awx_collection/tests/integration/targets | grep '${{ matrix.target-regex.regex }}' | tr '\n' ' ')"
export PYTHONPATH="$(python -c 'import site; print(":".join(site.getsitepackages()))')${PYTHONPATH:+:$PYTHONPATH}"
make COLLECTION_VERSION=100.100.100-git COLLECTION_TEST_TARGET="--requirements $TARGETS" test_collection_integration
make COLLECTION_VERSION=100.100.100 COLLECTION_TEST_TARGET="--requirements $TARGETS" test_collection_integration
env:
ANSIBLE_TEST_PREFER_PODMAN: 1
CONTROLLER_OPTIONAL_API_URLPATTERN_PREFIX: /api/

- name: Upload test coverage to Codecov
if: >-
Expand Down Expand Up @@ -426,7 +446,7 @@ jobs:
if: always()
with:
name: coverage-${{ matrix.target-regex.name }}
path: ~/.ansible/collections/ansible_collections/awx/awx/tests/output/coverage/
path: ~/.ansible/collections/ansible_collections/${{ env.COLLECTION_NAMESPACE }}/${{ env.COLLECTION_PACKAGE }}/tests/output/coverage/
retention-days: 1

- uses: ./.github/actions/upload_awx_devel_logs
Expand Down Expand Up @@ -468,10 +488,10 @@ jobs:

- name: Combine coverage
run: |
make COLLECTION_VERSION=100.100.100-git install_collection
mkdir -p ~/.ansible/collections/ansible_collections/awx/awx/tests/output/coverage
cp -rv coverage/* ~/.ansible/collections/ansible_collections/awx/awx/tests/output/coverage/
cd ~/.ansible/collections/ansible_collections/awx/awx
make COLLECTION_VERSION=100.100.100 install_collection
mkdir -p ~/.ansible/collections/ansible_collections/${{ env.COLLECTION_NAMESPACE }}/${{ env.COLLECTION_PACKAGE }}/tests/output/coverage
cp -rv coverage/* ~/.ansible/collections/ansible_collections/${{ env.COLLECTION_NAMESPACE }}/${{ env.COLLECTION_PACKAGE }}/tests/output/coverage/
cd ~/.ansible/collections/ansible_collections/${{ env.COLLECTION_NAMESPACE }}/${{ env.COLLECTION_PACKAGE }}
hash -r # Rehash to pick up newly installed scripts
PATH="$(python -c 'import sys; import os; print(os.path.dirname(sys.executable))'):$PATH" ansible-test coverage combine --requirements
PATH="$(python -c 'import sys; import os; print(os.path.dirname(sys.executable))'):$PATH" ansible-test coverage html
Expand All @@ -487,4 +507,4 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: awx-collection-integration-coverage-html
path: ~/.ansible/collections/ansible_collections/awx/awx/tests/output/reports/coverage
path: ~/.ansible/collections/ansible_collections/${{ env.COLLECTION_NAMESPACE }}/${{ env.COLLECTION_PACKAGE }}/tests/output/reports/coverage
28 changes: 6 additions & 22 deletions .github/workflows/promote.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name: Promote Release

env:
LC_ALL: "C.UTF-8" # prevent ERROR: Ansible could not initialize the preferred locale: unsupported locale setting
COLLECTION_NAMESPACE: ${{ github.repository == 'ansible/tower' && 'ansible' || 'awx' }}
COLLECTION_PACKAGE: ${{ github.repository == 'ansible/tower' && 'controller' || 'awx' }}

on:
release:
Expand All @@ -17,7 +19,7 @@ permissions:

jobs:
promote:
if: endsWith(github.repository, '/awx')
if: github.repository_owner == 'ansible'
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
Expand All @@ -42,50 +44,32 @@ jobs:
run: |
python${{ env.py_version }} -m pip install wheel twine setuptools-scm

- name: Set official collection namespace
run: echo collection_namespace=awx >> $GITHUB_ENV
if: ${{ github.repository_owner == 'ansible' }}

- name: Set unofficial collection namespace
run: echo collection_namespace=${{ github.repository_owner }} >> $GITHUB_ENV
if: ${{ github.repository_owner != 'ansible' }}

- name: Build collection and publish to galaxy
env:
COLLECTION_NAMESPACE: ${{ env.collection_namespace }}
COLLECTION_VERSION: ${{ env.TAG_NAME }}
COLLECTION_TEMPLATE_VERSION: true
run: |
sudo apt-get install jq
make build_collection
count=$(curl -s https://galaxy.ansible.com/api/v3/plugin/ansible/search/collection-versions/\?namespace\=${COLLECTION_NAMESPACE}\&name\=awx\&version\=${COLLECTION_VERSION} | jq .meta.count)
count=$(curl -s https://galaxy.ansible.com/api/v3/plugin/ansible/search/collection-versions/\?namespace\=${COLLECTION_NAMESPACE}\&name\=${COLLECTION_PACKAGE}\&version\=${COLLECTION_VERSION} | jq .meta.count)
if [[ "$count" == "1" ]]; then
echo "Galaxy release already done";
elif [[ "$count" == "0" ]]; then
ansible-galaxy collection publish \
--token=${{ secrets.GALAXY_TOKEN }} \
awx_collection_build/${COLLECTION_NAMESPACE}-awx-${COLLECTION_VERSION}.tar.gz;
awx_collection_build/${COLLECTION_NAMESPACE}-${COLLECTION_PACKAGE}-${COLLECTION_VERSION}.tar.gz;
else
echo "Unexpected count from galaxy search: $count";
exit 1;
fi

- name: Set official pypi info
run: echo pypi_repo=pypi >> $GITHUB_ENV
if: ${{ github.repository_owner == 'ansible' }}

- name: Set unofficial pypi info
run: echo pypi_repo=testpypi >> $GITHUB_ENV
if: ${{ github.repository_owner != 'ansible' }}

- name: Build awxkit and upload to pypi
env:
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ env.TAG_NAME }}
run: |
git reset --hard
cd awxkit && python3 setup.py sdist bdist_wheel
twine upload \
-r ${{ env.pypi_repo }} \
-r pypi \
-u ${{ secrets.PYPI_USERNAME }} \
-p ${{ secrets.PYPI_PASSWORD }} \
dist/*
Expand Down
14 changes: 5 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,10 @@ PARALLEL_TESTS ?= -n auto
COLLECTION_TEST_TARGET ?=
# Python version for ansible-test (must be 3.11, 3.12, or 3.13)
ANSIBLE_TEST_PYTHON_VERSION ?= 3.13
# args for collection install
COLLECTION_PACKAGE ?= awx
COLLECTION_NAMESPACE ?= awx
COLLECTION_INSTALL = $(HOME)/.ansible/collections/ansible_collections/$(COLLECTION_NAMESPACE)/$(COLLECTION_PACKAGE)
COLLECTION_TEMPLATE_VERSION ?= false
# args for collection install - read namespace and name from galaxy.yml
COLLECTION_NAMESPACE ?= $(shell grep '^namespace:' awx_collection/galaxy.yml | awk '{print $$2}')
COLLECTION_PACKAGE ?= $(shell grep '^name:' awx_collection/galaxy.yml | awk '{print $$2}')
COLLECTION_INSTALL = ~/.ansible/collections/ansible_collections/$(COLLECTION_NAMESPACE)/$(COLLECTION_PACKAGE)

# NOTE: This defaults the container image version to the branch that's active
COMPOSE_TAG ?= $(GIT_BRANCH)
Expand Down Expand Up @@ -433,10 +432,7 @@ symlink_collection:

awx_collection_build: $(shell find awx_collection -type f)
$(ANSIBLE_PLAYBOOK) -i localhost, awx_collection/tools/template_galaxy.yml \
-e collection_package=$(COLLECTION_PACKAGE) \
-e collection_namespace=$(COLLECTION_NAMESPACE) \
-e collection_version=$(COLLECTION_VERSION) \
-e '{"awx_template_version": $(COLLECTION_TEMPLATE_VERSION)}'
-e collection_version=$(COLLECTION_VERSION)
ansible-galaxy collection build awx_collection_build --force --output-path=awx_collection_build

build_collection: awx_collection_build
Expand Down
25 changes: 11 additions & 14 deletions awx_collection/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
# AWX Ansible Collection

[comment]: # (*******************************************************)
[comment]: # (* *)
[comment]: # (* WARNING *)
[comment]: # (* *)
[comment]: # (* This file is templated and not to be *)
[comment]: # (* edited directly! Instead modify: *)
[comment]: # (* tools/roles/template_galaxy/templates/README.md.j2 *)
[comment]: # (* *)
[comment]: # (* Changes to the base README.md file are refreshed *)
[comment]: # (* upon build of the collection *)
[comment]: # (*******************************************************)

This Ansible collection allows for easy interaction with an AWX server via Ansible playbooks.

This source for this collection lives in the `awx_collection` folder inside of the
Expand All @@ -32,7 +20,7 @@ Installing the `tar.gz` involves no special instructions.
## Running

Non-deprecated modules in this collection have no Python requirements, but
may require the AWX CLI
may require the official [AWX CLI](https://pypi.org/project/awxkit/)
in the future. The `DOCUMENTATION` for each module will report this.

You can specify authentication by host, username, and password.
Expand All @@ -42,6 +30,7 @@ These can be specified via (from highest to lowest precedence):
- direct module parameters
- environment variables (most useful when running against localhost)
- a config file path specified by the `tower_config_file` parameter
- a config file at `./tower_cli.cfg`, i.e. in the current directory
- a config file at `~/.tower_cli.cfg`
- a config file at `/etc/tower/tower_cli.cfg`

Expand All @@ -55,9 +44,17 @@ username = foo
password = bar
```

or like this:

```
host: https://localhost:8043
verify_ssl: true
oauth_token: <token>
```
Comment on lines +47 to +53
Copy link

@coderabbitai coderabbitai bot Mar 11, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language specifier to fenced code block.

The code block on line 49 is missing a language specifier. For consistency with other code blocks in this file (which use no language but contain shell/ini), consider adding yaml since this example shows YAML format.

📝 Suggested fix
 or like this:
 
-```
+```yaml
 host: https://localhost:8043
 verify_ssl: true
 oauth_token: <token>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
or like this:
```
host: https://localhost:8043
verify_ssl: true
oauth_token: <token>
```
or like this:
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 49-49: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@awx_collection/README.md` around lines 47 - 53, The fenced code block showing
the YAML-like example (the block containing "host: https://localhost:8043",
"verify_ssl: true", "oauth_token: <token>") should include a language specifier;
update the opening fence from ``` to ```yaml so the block becomes ```yaml ...
``` to match other examples and enable proper syntax highlighting.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is templated from the prior README.md.j2

This is not in the scope of the PR. All this does it materialize content already in the template to the duplicated file. This is the content already being used when building the collection.

I didn't start this to fix this problem, and it's noise.

Copy link

Choose a reason for hiding this comment

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

@AlanCoding, understood — this is pre-existing content from the template that the PR simply materializes, and fixing the missing language specifier is out of scope here. I'll note this for future reference.


📝 Learnings were identified and not saved (knowledge base disabled). Enable


## Release and Upgrade Notes

Notable releases of the `awx.awx` collection:
Notable releases of the Galaxy vendored collection:

- 7.0.0 is intended to be identical to the content prior to the migration, aside from changes necessary to function as a collection.
- 11.0.0 has no non-deprecated modules that depend on the deprecated `tower-cli` [PyPI](https://pypi.org/project/ansible-tower-cli/).
Expand Down
13 changes: 0 additions & 13 deletions awx_collection/galaxy.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
# (********************************************************)
# (* *)
# (* WARNING *)
# (* *)
# (* This file is managed by Ansible and not to be *)
# (* edited directly! Instead modify: *)
# (* tools/roles/template_galaxy/templates/galaxy.yml.j2 *)
# (* *)
# (* Changes to the base galaxy.yml file are refreshed *)
# (* upon build of the collection *)
# (********************************************************)
---
authors:
- AWX Project Contributors <awx-project@googlegroups.com>
Expand All @@ -33,6 +22,4 @@ version: 0.0.1-devel
build_ignore:
- tools
- setup.cfg
- galaxy.yml.j2
- template_galaxy.yml
- '*.tar.gz'
7 changes: 7 additions & 0 deletions awx_collection/plugins/doc_fragments/auth_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ class ModuleDocFragment(object):
why: Support for AAP variables
alternatives: 'AAP_REQUEST_TIMEOUT'
aliases: [ aap_request_timeout ]
api_prefix:
description:
- Override the API URL prefix for this request.
- Defaults to /api/ for AWX or /api/controller/ for Automation Controller.
type: str
env:
- name: CONTROLLER_OPTIONAL_API_URLPATTERN_PREFIX
notes:
- If no I(config_file) is provided we will attempt to use the tower-cli library
defaults to find your host information.
Expand Down
6 changes: 3 additions & 3 deletions awx_collection/plugins/lookup/controller_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@

- name: Report the usernames of all users with admin privs
debug:
msg: "Admin users: {{ query('awx.awx.controller_api', 'users', query_params={ 'is_superuser': true }) | map(attribute='username') | join(', ') }}"
msg: "Admins: {{ query('awx.awx.controller_api', 'users', query_params={ 'is_superuser': true }) | map(attribute='username') | join(', ') }}"

- name: debug all organizations in a loop # use query to return a list
debug:
Expand All @@ -89,14 +89,14 @@
label: "{{ item['name'] }}"

- name: Make sure user 'john' is an org admin of the default org if the user exists
role:
awx.awx.role:
organization: Default
role: admin
user: john
when: "lookup('awx.awx.controller_api', 'users', query_params={ 'username': 'john' }) | length == 1"

- name: Create an inventory group with all 'foo' hosts
group:
awx.awx.group:
name: "Foo Group"
inventory: "Demo Inventory"
hosts: >-
Expand Down
17 changes: 15 additions & 2 deletions awx_collection/plugins/module_utils/controller_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@

class ControllerModule(AnsibleModule):
url = None
AUTH_ARGSPEC = dict(

Check warning on line 51 in awx_collection/plugins/module_utils/controller_api.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this constructor call with a literal.

See more on https://sonarcloud.io/project/issues?id=ansible_awx&issues=AZzdlC4oLoKQwZWp0yqG&open=AZzdlC4oLoKQwZWp0yqG&pullRequest=16347
controller_host=dict(
required=False,
aliases=['tower_host', 'aap_hostname'],
fallback=(env_fallback, ['CONTROLLER_HOST', 'TOWER_HOST', 'AAP_HOSTNAME'])),
controller_username=dict(

Check warning on line 56 in awx_collection/plugins/module_utils/controller_api.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this constructor call with a literal.

See more on https://sonarcloud.io/project/issues?id=ansible_awx&issues=AZzdlC4oLoKQwZWp0yqI&open=AZzdlC4oLoKQwZWp0yqI&pullRequest=16347
required=False,
aliases=['tower_username', 'aap_username'],
fallback=(env_fallback, ['CONTROLLER_USERNAME', 'TOWER_USERNAME', 'AAP_USERNAME'])),
Expand Down Expand Up @@ -92,12 +92,14 @@
'password': 'controller_password',
'verify_ssl': 'validate_certs',
'request_timeout': 'request_timeout',
'api_prefix': 'api_prefix',
}
host = '127.0.0.1'
username = None
password = None
verify_ssl = True
request_timeout = 10
api_prefix = None
authenticated = False
config_name = 'tower_cli.cfg'
version_checked = False
Expand Down Expand Up @@ -178,7 +180,7 @@

return url

def load_config_files(self):

Check failure on line 183 in awx_collection/plugins/module_utils/controller_api.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=ansible_awx&issues=AZzdlC4oLoKQwZWp0yqO&open=AZzdlC4oLoKQwZWp0yqO&pullRequest=16347
# Load configs like TowerCLI would have from least import to most
config_files = ['/etc/tower/tower_cli.cfg', join(expanduser("~"), ".{0}".format(self.config_name))]
local_dir = getcwd()
Expand Down Expand Up @@ -308,13 +310,22 @@
# TODO: Move the collection version check into controller_module.py
# This gets set by the make process so whatever is in here is irrelevant
_COLLECTION_VERSION = "0.0.1-devel"
_COLLECTION_TYPE = "awx"
# This maps the collections type (awx/tower) to the values returned by the API
# The FQCN is the canonical identifier for this collection.
# On upstream (awx) this is "awx.awx"; on downstream (tower) the
# replace_fqcn.sh script converts it to "ansible.controller".
# _COLLECTION_TYPE is derived from the FQCN automatically.
_COLLECTION_FQCN = "awx.awx"
Copy link
Member Author

Choose a reason for hiding this comment

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

This syntax change allows this line to get swept up in the general find+replace. It's dumb logic that takes all "awx.awx" and turns it into "ansible.controller".

This still leaves a special case for the version when packaging a collection.

So the collection FQCN is bulk-replaced by the script and it must go in a commit. Then the version is inserted in before building, every time.

# This maps the collections type (awx/controller) to the values returned by the API
# Those values can be found in awx/api/generics.py line 204
collection_to_version = {
'awx': 'AWX',
'controller': 'Red Hat Ansible Automation Platform',
}

@property
def _COLLECTION_TYPE(self):
return self._COLLECTION_FQCN.split('.')[1]

session = None
IDENTITY_FIELDS = {'users': 'username', 'workflow_job_template_nodes': 'identifier', 'instances': 'hostname'}
ENCRYPTED_STRING = "$encrypted$"
Expand Down Expand Up @@ -488,7 +499,7 @@
def resolve_name_to_id(self, endpoint, name_or_id):
return self.get_exactly_one(endpoint, name_or_id)['id']

def make_request(self, method, endpoint, *args, **kwargs):

Check failure on line 502 in awx_collection/plugins/module_utils/controller_api.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 53 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=ansible_awx&issues=AZzdlC4oLoKQwZWp0yqT&open=AZzdlC4oLoKQwZWp0yqT&pullRequest=16347
# In case someone is calling us directly; make sure we were given a method, let's not just assume a GET
if not method:
raise Exception("The HTTP method must be defined")
Expand Down Expand Up @@ -630,6 +641,8 @@

def api_path(self, app_key=None):

if self.api_prefix:
return self.api_prefix
Copy link

Choose a reason for hiding this comment

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

api_prefix bypasses URL path normalization in api_path

Medium Severity

When self.api_prefix is set, api_path() returns it immediately, bypassing the normalization that ensures the path starts with / and ends with / (lines 664–668). A value like "api" or "api/controller" from a config file would produce a malformed URL in build_url(), while the same value set via CONTROLLER_OPTIONAL_API_URLPATTERN_PREFIX env var through the old code path would be properly normalized.

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

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

I would rather not mess with it and let the user figure it out via the value of CONTROLLER_OPTIONAL_API_URLPATTERN_PREFIX. The more we try to automatically fix things, the more we own those problems.

default_api_path = "/api/"
Comment on lines +644 to 646
Copy link

@coderabbitai coderabbitai bot Mar 11, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how api_prefix is used in tests and whether normalization is expected
rg -n "api_prefix" awx_collection/ -A3 -B3

Repository: ansible/awx

Length of output: 2622


🏁 Script executed:

#!/bin/bash
# Get the complete api_path method to see the trailing slash normalization
sed -n '642,680p' awx_collection/plugins/module_utils/controller_api.py | cat -n

Repository: ansible/awx

Length of output: 1857


🏁 Script executed:

#!/bin/bash
# Also search for how api_path and api_prefix are used in the codebase
rg -n "\.api_path\(|api_prefix" awx_collection/ --type py -A2 -B1 | head -100

Repository: ansible/awx

Length of output: 2038


🏁 Script executed:

#!/bin/bash
# Search for test cases and usage of api_prefix to understand expected format
rg -n "api_prefix\s*=" awx_collection/ --type py -A2 -B2 | head -60

Repository: ansible/awx

Length of output: 473


🏁 Script executed:

#!/bin/bash
# Check if there are any tests that demonstrate the issue or expected behavior
find awx_collection -name "*test*.py" -type f -exec grep -l "api_prefix" {} \;

Repository: ansible/awx

Length of output: 37


Add trailing slash normalization to api_prefix for consistency.

When api_prefix is set, the method returns it directly without the leading/trailing slash normalization applied to other paths (lines 23-27). If a caller passes api_prefix without a trailing slash (e.g., "/api" instead of "/api/"), URL construction will be inconsistent and may break at line 170 where endpoint matching occurs.

Either normalize api_prefix like the default paths, or document and validate that api_prefix must include trailing slashes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@awx_collection/plugins/module_utils/controller_api.py` around lines 644 -
646, The method currently returns self.api_prefix verbatim, causing
inconsistency with default_api_path normalization; update the logic so when
self.api_prefix is set it is normalized to include a single trailing slash
(e.g., ensure it ends with "/" similar to how other paths are normalized) before
returning, preserving existing behavior when already correct; reference the
symbols self.api_prefix and default_api_path and make the change where the
api_prefix return occurs so endpoint matching code that relies on a trailing
slash receives a normalized value.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't wanna

Copy link

Choose a reason for hiding this comment

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

@AlanCoding, understood — no worries, acknowledged!

if self._COLLECTION_TYPE != "awx" or app_key is not None:
if app_key is None:
Expand Down Expand Up @@ -717,7 +730,7 @@
return self.json_output
else:
if 'json' in response and '__all__' in response['json']:
self.fail_json(msg="Unable to delete {0} {1}: {2}".format(item_type, item_name, response['json']['__all__'][0]))

Check failure on line 733 in awx_collection/plugins/module_utils/controller_api.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "Unable to delete {0} {1}: {2}" 4 times.

See more on https://sonarcloud.io/project/issues?id=ansible_awx&issues=AZzdlC4oLoKQwZWp0yqF&open=AZzdlC4oLoKQwZWp0yqF&pullRequest=16347
elif 'json' in response:
# This is from a project delete (if there is an active job against it)
if 'error' in response['json']:
Expand Down
2 changes: 1 addition & 1 deletion awx_collection/plugins/modules/ad_hoc_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@

EXAMPLES = '''
- name: Launch an Ad Hoc Command waiting for it to finish
ad_hoc_command:
awx.awx.ad_hoc_command:
inventory: Demo Inventory
credential: Demo Credential
module_name: command
Expand All @@ -123,22 +123,22 @@

def main():
# Any additional arguments that are not fields of the item can be added here
argument_spec = dict(

Check warning on line 126 in awx_collection/plugins/modules/ad_hoc_command.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this constructor call with a literal.

See more on https://sonarcloud.io/project/issues?id=ansible_awx&issues=AZzdlC3MLoKQwZWp0yow&open=AZzdlC3MLoKQwZWp0yow&pullRequest=16347
job_type=dict(choices=['run', 'check']),
inventory=dict(required=True),

Check warning on line 128 in awx_collection/plugins/modules/ad_hoc_command.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this constructor call with a literal.

See more on https://sonarcloud.io/project/issues?id=ansible_awx&issues=AZzdlC3MLoKQwZWp0yoz&open=AZzdlC3MLoKQwZWp0yoz&pullRequest=16347
limit=dict(),
credential=dict(required=True),
module_name=dict(required=True),

Check warning on line 131 in awx_collection/plugins/modules/ad_hoc_command.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this constructor call with a literal.

See more on https://sonarcloud.io/project/issues?id=ansible_awx&issues=AZzdlC3MLoKQwZWp0yo2&open=AZzdlC3MLoKQwZWp0yo2&pullRequest=16347
module_args=dict(),
forks=dict(type='int'),
verbosity=dict(type='int', choices=[0, 1, 2, 3, 4, 5]),
extra_vars=dict(type='dict'),
become_enabled=dict(type='bool'),

Check warning on line 136 in awx_collection/plugins/modules/ad_hoc_command.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this constructor call with a literal.

See more on https://sonarcloud.io/project/issues?id=ansible_awx&issues=AZzdlC3MLoKQwZWp0yo7&open=AZzdlC3MLoKQwZWp0yo7&pullRequest=16347
diff_mode=dict(type='bool'),
wait=dict(default=False, type='bool'),
interval=dict(default=2.0, type='float'),
timeout=dict(type='int'),
execution_environment=dict(),

Check warning on line 141 in awx_collection/plugins/modules/ad_hoc_command.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this constructor call with a literal.

See more on https://sonarcloud.io/project/issues?id=ansible_awx&issues=AZzdlC3MLoKQwZWp0ypA&open=AZzdlC3MLoKQwZWp0ypA&pullRequest=16347
)

# Create a module for ourselves
Expand Down
2 changes: 1 addition & 1 deletion awx_collection/plugins/modules/ad_hoc_command_cancel.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

EXAMPLES = '''
- name: Cancel command
ad_hoc_command_cancel:
awx.awx.ad_hoc_command_cancel:
command_id: command.id
'''

Expand Down
Loading
Loading