From 2ac3dae3b7a6e2a76154898ab05271c4a0b3d358 Mon Sep 17 00:00:00 2001 From: Alex Welsh Date: Thu, 20 Nov 2025 11:51:59 +0000 Subject: [PATCH 1/5] Bump Ansible to 13 --- .github/workflows/pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ac4afa4..704326a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -27,7 +27,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install ansible==5.* jmespath pulp-glue==0.21.* + pip install ansible==13.* jmespath pulp-glue==0.21.* ansible-galaxy collection install git+file://$(pwd) - name: Run Pulp in one From b8643b336d52bc6343b8bddf6e54d703fb0539f5 Mon Sep 17 00:00:00 2001 From: Alex Welsh Date: Thu, 20 Nov 2025 11:56:03 +0000 Subject: [PATCH 2/5] Update Readme --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e3d63b..756f022 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,15 @@ Note: Pulp server installation is out of this collection's scope - for this purp ## Tested with Ansible -Tested with the current Ansible 2.9-2.10 releases. +Tested with the current Ansible 13 releases. ## Included content pulp_contentguard role pulp_repository role +pulp_distribution role +pulp_django_user role +pulp_group role ## Using this collection From ef62109a9d925c6ef73acaeeeadc9f290ef9c93b Mon Sep 17 00:00:00 2001 From: Alex Welsh Date: Thu, 20 Nov 2025 11:57:40 +0000 Subject: [PATCH 3/5] Bump tested pulp-in-one version --- .github/workflows/pull_request.yml | 2 +- tests/pulp-in-one.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 704326a..a9bd194 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -16,8 +16,8 @@ jobs: fail-fast: false matrix: pulp: - - "3.21" - "3.45" + - "3.81" steps: # Checks-out the repository under $GITHUB_WORKSPACE, so it's accessible to the job - uses: actions/checkout@v3 diff --git a/tests/pulp-in-one.sh b/tests/pulp-in-one.sh index be985c0..abc2db8 100755 --- a/tests/pulp-in-one.sh +++ b/tests/pulp-in-one.sh @@ -8,7 +8,7 @@ set -o pipefail mkdir -p settings -PULP_TAG=${PULP_TAG:-"3.45"} +PULP_TAG=${PULP_TAG:-"3.81"} cat << EOF > settings/settings.py CONTENT_ORIGIN='http://$(hostname):8080' From 766703e7595ad42f4aa91cb0699cfd8fd75abe09 Mon Sep 17 00:00:00 2001 From: Alex Welsh Date: Fri, 21 Nov 2025 14:13:40 +0000 Subject: [PATCH 4/5] dnm: test new lint version --- .github/workflows/lint-collection.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-collection.yml b/.github/workflows/lint-collection.yml index 1cda5e6..1d32579 100644 --- a/.github/workflows/lint-collection.yml +++ b/.github/workflows/lint-collection.yml @@ -4,4 +4,4 @@ name: Ansible collection linters pull_request: jobs: lint: - uses: stackhpc/.github/.github/workflows/lint-collection.yml@main + uses: stackhpc/.github/.github/workflows/lint-collection.yml@bump-ansible From d75bde112c807192d9d6883da625684a0a7b3a83 Mon Sep 17 00:00:00 2001 From: Alex Welsh Date: Fri, 21 Nov 2025 16:15:00 +0000 Subject: [PATCH 5/5] try bump --- galaxy.yml | 5 +- plugins/modules/pulp_container_content.py | 223 ------------------ roles/pulp_container_content/tasks/main.yml | 34 +-- .../tasks/process_content.yml | 127 ++++++++++ 4 files changed, 132 insertions(+), 257 deletions(-) delete mode 100644 plugins/modules/pulp_container_content.py create mode 100644 roles/pulp_container_content/tasks/process_content.yml diff --git a/galaxy.yml b/galaxy.yml index f69ae2a..0cc7dd6 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,14 +2,15 @@ namespace: stackhpc name: pulp description: > Roles and plugins Pulp repository server configuration -version: "0.5.5" +version: "0.6.0" readme: "README.md" authors: - "Piotr Parczewski" - "MichaƂ Nasiadka" - "Mark Goddard" + - "Alex Welsh" dependencies: - "pulp.squeezer": "*" + "pulp.squeezer": ">=0.20.0" license: - "Apache-2.0" tags: diff --git a/plugins/modules/pulp_container_content.py b/plugins/modules/pulp_container_content.py deleted file mode 100644 index 9bf1887..0000000 --- a/plugins/modules/pulp_container_content.py +++ /dev/null @@ -1,223 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - - -DOCUMENTATION = r""" ---- -module: pulp_container_content -short_description: Manage container content of a pulp api server instance -description: - - "This performs CRUD operations on container content in a pulp api server instance." -options: - allow_missing: - description: - - Whether to allow missing tags when state is present. - type: bool - default: false - is_push: - description: - - Whether repository is a container-push repository. - type: bool - default: false - src_repo: - description: - - Name of the repository to copy content from when state is present. - type: str - src_is_push: - description: - - Whether src_repo is a container-push repository. - type: bool - default: false - repository: - description: - - Name of the repository to add or remove content - type: str - required: true - state: - description: - - State the entity should be in - type: str - default: present - choices: - - present - - absent - - read - tags: - description: - - List of tags to add or remove - type: list - elements: str - required: true - wait: - description: - - Whether to wait for completion of the operation - type: bool - default: true -extends_documentation_fragment: - - pulp.squeezer.pulp - - pulp.squeezer.pulp.entity_state -author: - - Mark Goddard (@markgoddard) -""" - -EXAMPLES = r""" -- name: Copy tag1 and tag2 from repo1 to repo2 - pulp_container_content: - pulp_url: https://pulp.example.org - username: admin - password: password - repository: repo2 - src_repo: repo1 - tags: - - tag1 - - tag2 - -- name: Remove tag3 from repo3 - pulp_container_content: - pulp_url: https://pulp.example.org - username: admin - password: password - repository: repo3 - tags: - - tag3 - state: absent -""" - -RETURN = r""" - repository_version: - description: Created container repository version - type: dict - returned: when content is added or removed -""" - - -from ansible_collections.pulp.squeezer.plugins.module_utils.pulp import ( - PAGE_LIMIT, - PulpContainerRepository, - PulpEntityAnsibleModule, - PulpTask, - SqueezerException, -) - - -class PulpContainerRepositoryContent(PulpContainerRepository): - _add_id = "repositories_container_container_add" - _remove_id = "repositories_container_container_remove" - _container_tags_list_id = "content_container_tags_list" - - _name_singular = "repository_version" - - def get_src_repo(self): - # Query source repository. - natural_key = {"name": self.module.params["src_repo"]} - repo = PulpContainerRepository(self.module, natural_key) - if self.module.params["state"] == "present" and self.module.params["src_is_push"]: - repo._list_id = "repositories_container_container_push_list" - # find populates repo.entity. - repo.find(failsafe=False) - return repo - - def get_content_units(self, repo): - # Query container tags with matching names in repo. - # Pagination code adapted from PulpEntity.list(). - tags = [] - offset = 0 - search_result = {"next": True} - while search_result["next"]: - parameters = { - "limit": PAGE_LIMIT, - "offset": offset, - "name__in": ",".join(self.module.params["tags"]), - "repository_version": repo.entity["latest_version_href"] - } - search_result = self.module.pulp_api.call( - self._container_tags_list_id, parameters=parameters - ) - tags.extend(search_result["results"]) - offset += PAGE_LIMIT - - tag_names = [tag["name"] for tag in tags] - if (self.module.params["state"] in ["present", "read"] and - not self.module.params["allow_missing"] and - len(tag_names) != len(self.module.params["tags"])): - missing = ", ".join(set(self.module.params["tags"]) - set(tag_names)) - raise SqueezerException(f"Some tags not found in source repository: {missing}") - return [result["pulp_href"] for result in tags] - - def add_or_remove(self, add_or_remove_id, content_units): - body = {"content_units": content_units} - if not self.module.check_mode: - parameters = {"container_container_repository_href": self.entity["pulp_href"]} - response = self.module.pulp_api.call( - add_or_remove_id, body=body, uploads=self.uploads, parameters=parameters - ) - if response and "task" in response: - if self.module.params["wait"]: - task = PulpTask(self.module, {"pulp_href": response["task"]}).wait_for() - # Adding or removing content results in creation of a new repository version - if task["created_resources"]: - self.entity = {"pulp_href": task["created_resources"][0]} - self.module.set_changed() - else: - self.entity = None - else: - self._name_singular = "task" - self.entity = {"pulp_href": response["task"]} - else: - self.entity = response - else: - # Assume changed in check mode - self.module.set_changed() - - def add(self): - src_repo = self.get_src_repo() - self.add_or_remove(self._add_id, self.get_content_units(src_repo)) - - def remove(self): - self.add_or_remove(self._remove_id, self.get_content_units(self)) - - def read(self): - self.get_content_units(self) - - def process(self): - if self.module.params["state"] == "read" and self.module.params["is_push"]: - self._list_id = "repositories_container_container_push_list" - # Populate self.entity. - self.find(failsafe=False) - if self.module.params["state"] == "present": - self.add() - elif self.module.params["state"] == "absent": - self.remove() - elif self.module.params["state"] == "read": - self.read() - else: - raise SqueezerException("Unexpected state") - self.module.set_result(self._name_singular, self.presentation(self.entity)) - - -def main(): - with PulpEntityAnsibleModule( - argument_spec=dict( - allow_missing={"type": "bool", "default": False}, - is_push={"type": "bool", "default": False}, - repository={"required": True}, - src_repo={}, - src_is_push={"type": "bool", "default": False}, - state={"default": "present", "choices": ["present", "absent", "read"]}, - tags={"type": "list", "elements": "str", "required": True}, - wait={"type": "bool", "default": True}, - ), - required_if=[("state", "present", ["src_repo"])], - ) as module: - natural_key = {"name": module.params["repository"]} - PulpContainerRepositoryContent(module, natural_key).process() - - -if __name__ == "__main__": - main() diff --git a/roles/pulp_container_content/tasks/main.yml b/roles/pulp_container_content/tasks/main.yml index 6f91868..88a89a0 100644 --- a/roles/pulp_container_content/tasks/main.yml +++ b/roles/pulp_container_content/tasks/main.yml @@ -1,36 +1,6 @@ --- - name: Add or remove content units - stackhpc.pulp.pulp_container_content: - pulp_url: "{{ pulp_url }}" - username: "{{ pulp_username }}" - password: "{{ pulp_password }}" - validate_certs: "{{ pulp_validate_certs | bool }}" - allow_missing: "{{ item.allow_missing | default(omit) }}" - is_push: "{{ item.is_push | default(omit) }}" - src_repo: "{{ item.src_repo | default(omit) }}" - src_is_push: "{{ item.src_is_push | default(omit) }}" - repository: "{{ item.repository }}" - tags: "{{ item.tags }}" - state: "{{ item.state | default(omit) }}" - wait: "{{ pulp_container_content_wait | bool }}" + include_tasks: process_content.yml loop: "{{ pulp_container_content }}" - register: pulp_container_content_result - -- name: Wait for tasks to complete - pulp.squeezer.task: - pulp_url: "{{ pulp_url }}" - username: "{{ pulp_username }}" - password: "{{ pulp_password }}" - validate_certs: "{{ pulp_validate_certs | bool }}" - pulp_href: "{{ content_result.task.pulp_href }}" - state: "completed" - loop: "{{ pulp_container_content }}" - when: - - not pulp_container_content_wait | bool - - "'task' in content_result" - changed_when: pulp_container_content_wait_result.task.created_resources | default([]) | length > 0 - register: pulp_container_content_wait_result loop_control: - index_var: result_index - vars: - content_result: "{{ pulp_container_content_result.results[result_index] }}" + loop_var: content_item diff --git a/roles/pulp_container_content/tasks/process_content.yml b/roles/pulp_container_content/tasks/process_content.yml new file mode 100644 index 0000000..c500483 --- /dev/null +++ b/roles/pulp_container_content/tasks/process_content.yml @@ -0,0 +1,127 @@ +--- +- name: Get destination repository href + pulp.squeezer.api_call: + pulp_url: "{{ pulp_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + validate_certs: "{{ pulp_validate_certs | bool }}" + method: GET + url: "/pulp/api/v3/repositories/container/container/?name={{ content_item.repository }}" + register: dest_repo_result + +- name: Fail if destination repository not found + fail: + msg: "Destination repository '{{ content_item.repository }}' not found." + when: dest_repo_result.json.count == 0 + +- name: Set destination repo href + set_fact: + dest_repo_href: "{{ dest_repo_result.json.results[0].pulp_href }}" + +- name: Get source repository info (if needed) + pulp.squeezer.api_call: + pulp_url: "{{ pulp_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + validate_certs: "{{ pulp_validate_certs | bool }}" + method: GET + url: "/pulp/api/v3/repositories/container/{{ 'push' if content_item.src_is_push | default(false) else 'container' }}/?name={{ content_item.src_repo }}" + register: src_repo_result + when: + - content_item.state | default('present') in ['present', 'read'] + - content_item.src_repo is defined + +- name: Fail if source repository not found + fail: + msg: "Source repository '{{ content_item.src_repo }}' not found." + when: + - content_item.state | default('present') in ['present', 'read'] + - content_item.src_repo is defined + - src_repo_result.json.count == 0 + +- name: Resolve tags from source repository + block: + - name: List tags from source repository + pulp.squeezer.api_call: + pulp_url: "{{ pulp_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + validate_certs: "{{ pulp_validate_certs | bool }}" + method: GET + url: "/pulp/api/v3/content/container/tags/" + parameters: + name__in: "{{ content_item.tags | join(',') }}" + repository_version: "{{ src_repo_result.json.results[0].latest_version_href }}" + register: tags_result + + - name: Check for missing tags + vars: + found_tags: "{{ tags_result.json.results | map(attribute='name') | list }}" + missing_tags: "{{ content_item.tags | difference(found_tags) }}" + fail: + msg: "Some tags not found in source repository: {{ missing_tags | join(', ') }}" + when: + - not content_item.allow_missing | default(false) + - missing_tags | length > 0 + + - name: Set content units to process (from source) + set_fact: + content_units: "{{ tags_result.json.results | map(attribute='pulp_href') | list }}" + when: + - content_item.state | default('present') in ['present', 'read'] + - content_item.src_repo is defined + +- name: Resolve tags to remove (from destination) + block: + - name: List tags to remove from destination repository + pulp.squeezer.api_call: + pulp_url: "{{ pulp_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + validate_certs: "{{ pulp_validate_certs | bool }}" + method: GET + url: "/pulp/api/v3/content/container/tags/" + parameters: + name__in: "{{ content_item.tags | join(',') }}" + repository_version: "{{ dest_repo_result.json.results[0].latest_version_href }}" + register: remove_tags_result + + - name: Set content units to process (for removal) + set_fact: + content_units: "{{ remove_tags_result.json.results | map(attribute='pulp_href') | list }}" + when: + - content_item.state | default('present') == 'absent' + +- name: Modify repository content + pulp.squeezer.api_call: + pulp_url: "{{ pulp_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + validate_certs: "{{ pulp_validate_certs | bool }}" + method: POST + url: "{{ dest_repo_href }}modify/" + body: "{{ modify_body }}" + vars: + modify_body: >- + {% if content_item.state | default('present') == 'present' %} + { "add_content_units": {{ content_units }} } + {% else %} + { "remove_content_units": {{ content_units }} } + {% endif %} + register: modify_result + when: + - content_item.state | default('present') != 'read' + - content_units | length > 0 + changed_when: true + +- name: Wait for task completion + pulp.squeezer.task: + pulp_url: "{{ pulp_url }}" + username: "{{ pulp_username }}" + password: "{{ pulp_password }}" + validate_certs: "{{ pulp_validate_certs | bool }}" + pulp_href: "{{ modify_result.json.task }}" + state: completed + when: + - modify_result is changed + - pulp_container_content_wait | bool \ No newline at end of file