diff --git a/plugins/module_utils/ah_pulp_object.py b/plugins/module_utils/ah_pulp_object.py index 1204600c..84c3fbf3 100644 --- a/plugins/module_utils/ah_pulp_object.py +++ b/plugins/module_utils/ah_pulp_object.py @@ -233,7 +233,7 @@ def create(self, new_item, auto_exit=True): except AHAPIModuleError as e: self.api.fail_json(msg="Create error: {error}, url: {url}".format(error=e, url=url.geturl())) - if response["status_code"] in [200, 201]: + if response["status_code"] in [200, 201, 202]: self.exists = True self.data = response["json"] # Make sure the object name is available in the response @@ -815,6 +815,159 @@ def create_tag(self, digest, tag, auto_exit=True): ) +class AHPulpAnsibleRepository(AHPulpObject): + """Manage the ansible repository with the Pulp API. + + TODO: add description + + Getting the details of a repository: + ``GET /pulp/api/v3/repositories/ansible/ansible/?name=`` :: + + { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "pulp_href": "/api/automation-hub/pulp/api/v3/repositories/ansible/ansible/018983f5-c249-7bd1-9e68-47a07b03d11b/", + "pulp_created": "2023-07-23T18:14:28.682316Z", + "versions_href": "/api/automation-hub/pulp/api/v3/repositories/ansible/ansible/018983f5-c249-7bd1-9e68-47a07b03d11b/versions/", + "pulp_labels": {}, + "latest_version_href": "/api/automation-hub/pulp/api/v3/repositories/ansible/ansible/018983f5-c249-7bd1-9e68-47a07b03d11b/versions/0/", + "name": "alpine", + "description": null, + "retain_repo_versions": 1, + "remote": null, + "last_synced_metadata_time": null, + "gpgkey": null, + "last_sync_task": null, + "private": false + } + ] + } + """ + + def __init__(self, API_object, data=None): + """Initialize the object.""" + super(AHPulpAnsibleRepository, self).__init__(API_object, data) + self.endpoint = "repositories/ansible/ansible" + self.object_type = "repository" + self.name_field = "name" + + +class AHPulpAnsibleDistribution(AHPulpObject): + """Manage the ansible distribution with the Pulp API. + + TODO: add description + + Getting the details of a repository: + ``GET /pulp/api/v3/distributions/ansible/ansible/?name=`` :: + + { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "pulp_href": "/api/automation-hub/pulp/api/v3/distributions/ansible/ansible/018983f5-5afb-7272-9ce3-a825f11c1f7d/", + "pulp_created": "2023-07-23T18:14:02.236595Z", + "base_path": "alpine", + "content_guard": "/api/automation-hub/pulp/api/v3/contentguards/core/content_redirect/01898355-81e9-7ed6-9a49-2fcc84754196/", + "name": "alpine", + "repository": "/api/automation-hub/pulp/api/v3/repositories/ansible/ansible/018983f5-5986-7da9-b42c-a0534d7a9524/", + "repository_version": null, + "client_url": "http://localhost:5001/pulp_ansible/galaxy/alpine/", + "pulp_labels": {} + } + ] + } + """ + + def __init__(self, API_object, data=None): + """Initialize the object.""" + super(AHPulpAnsibleDistribution, self).__init__(API_object, data) + self.endpoint = "distributions/ansible/ansible" + self.object_type = "distribution" + self.name_field = "name" + + +class AHPulpAnsibleRemote(AHPulpObject): + """Manage the ansible remote with the Pulp API. + + TODO: add description + + Getting the details of a repository: + ``GET /pulp/api/v3/remotes/ansible/collection/?name=`` :: + + { + "count": 2, + "next": "http://localhost:5001/api/automation-hub/pulp/api/v3/remotes/ansible/collection/?limit=1&offset=1", + "previous": null, + "results": [ + { + "pulp_href": "/api/automation-hub/pulp/api/v3/remotes/ansible/collection/01898d7c-8342-7689-9daa-36aa574cb186/", + "pulp_created": "2023-07-25T14:38:14.850795Z", + "name": "rh-certified", + "url": "https://console.redhat.com/api/automation-hub/", + "ca_cert": null, + "client_cert": null, + "tls_validation": true, + "proxy_url": null, + "pulp_labels": {}, + "pulp_last_updated": "2023-07-25T14:38:18.034451Z", + "download_concurrency": null, + "max_retries": null, + "policy": "immediate", + "total_timeout": null, + "connect_timeout": null, + "sock_connect_timeout": null, + "sock_read_timeout": null, + "headers": null, + "rate_limit": 8, + "hidden_fields": [ + { + "name": "client_key", + "is_set": false + }, + { + "name": "proxy_username", + "is_set": false + }, + { + "name": "proxy_password", + "is_set": false + }, + { + "name": "username", + "is_set": false + }, + { + "name": "password", + "is_set": false + }, + { + "name": "token", + "is_set": false + } + ], + "requirements_file": null, + "auth_url": "https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token", + "sync_dependencies": true, + "signed_only": false, + "last_sync_task": null + } + ] + } + """ + + def __init__(self, API_object, data=None): + """Initialize the object.""" + super(AHPulpAnsibleRemote, self).__init__(API_object, data) + self.endpoint = "remotes/ansible/collection" + self.object_type = "remote" + self.name_field = "name" + + class AHPulpTask(AHPulpObject): """Manage a task with the Pulp API. diff --git a/plugins/modules/collection_repository.py b/plugins/modules/collection_repository.py new file mode 100644 index 00000000..ab437357 --- /dev/null +++ b/plugins/modules/collection_repository.py @@ -0,0 +1,249 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + + +DOCUMENTATION = """ +--- +module: collection_repository +author: "tbd" +short_description: Create, Update, Delete repository. +description: + - Configure an Automation Hub repository. See + U(https://www.ansible.com/) for an overview. +options: + state: + description: + - If C(absent), then the module deletes the repository and its distribution. + - If C(present), then the module updates the repository. + type: str + choices: ["present", "absent"] + default: present + name: + description: + - Repository name. + required: True + type: str + description: + description: + - Description for the repository. + required: False + type: str + pulp_labels: + description: An Ansible Fact variable representing a Tower token object which can be used for auth in subsequent modules. See examples for usage. + contains: + pipeline: + description: Pipeline adds repository labels with pre-defined meanings. + type: str + choices: [None, "approved", "staging", "rejected"] + hide_from_search: + description: Prevent collections in this repository from showing up on the home page. + type: str + distribution: + description: + - Content in repositories without a distribution will not be visible to clients for sync, download or search. + contains: + name: + description: + - Distribution name and base_path. + - If not set, repository name is used. + type: str + state: + description: + - If C(absent), then the module deletes the distribution. + - If C(present), then the module creates or updates the distribution. + type: str + choices: ["present", "absent"] + private: + description: + - Make the repository private. + type: str + default: False + remote: + description: + - Existing remote name. + type: str + +extends_documentation_fragment: ansible.automation_hub.auth +""" + + +EXAMPLES = """ +- name: Create "foobar" repository with distribution and remote + ansible.automation_hub.collection_repository: + state: present + name: "foobar" + description: "description of foobar repository" + pulp_labels: + pipeline: "approved" + distribution: + name: "foobar" + state: present + remote: community + +- name: Create rejected "foobar" repository with + ansible.automation_hub.collection_repository: + state: present + name: "foobar" + description: "description of foobar repository" + pulp_labels: + pipeline: "rejected" + hide_from_search: "" + +- name: Delete "foobar" repository + ansible.automation_hub.collection_repository: + state: absent + name: "foobar" +""" + +from ..module_utils.ah_api_module import AHAPIModule +from ..module_utils.ah_pulp_object import ( + AHPulpAnsibleRepository, + AHPulpAnsibleDistribution, + AHPulpAnsibleRemote +) + + +def main(): + # Any additional arguments that are not fields of the item can be added here + argument_spec = dict( + name=dict(required=True), + description=dict(), + retain_repo_versions=dict(type="int", default=1), + distribution=dict(type="dict"), + pulp_labels=dict(type="dict"), + private=dict(type="bool", default=False), + remote=dict(), + state=dict(choices=["present", "absent"], default="present"), + ) + + module = AHAPIModule(argument_spec=argument_spec, supports_check_mode=True) + + # Extract our parameters + name = module.params.get("name") + module.fail_on_missing_params(["name"]) + new_fields = {} + repo_fields = {} + + # Authenticate + module.authenticate() + + repo_keys = [ + "name", + "description", + "retain_repo_versions", + "pulp_labels", + "private", + "remote" + ] + + for field_name in ( + *repo_keys, + "distribution", + "state" + ): + field_val = module.params.get(field_name) + if field_val is not None: + new_fields[field_name] = field_val + + if field_name in repo_keys: + repo_fields[field_name] = field_val + + ansible_repository = AHPulpAnsibleRepository(module) + ansible_repository.get_object(name=name) + + if distro := new_fields.get("distribution"): + # if "distribution" is set, but "state" is missing, set "present" + distro_state = distro.get("state", None) + + if distro_state not in [None, "present", "absent"]: + module.fail_json( + msg="value of state must be one of: present, absent, got: {}".format(distro_state) + ) + + if not distro_state: + distro_state = "present" + + # if distro name isn't specified, use repo name as distro + distro_name = distro.get("name", None) + if not distro_name: + distro_name = name + + ansible_distro = AHPulpAnsibleDistribution(module) + ansible_distro.get_object(name=distro_name) + + + if new_fields.get("state") == "absent": + if distro: + ansible_distro.delete(auto_exit=False) + + ansible_repository.delete(auto_exit=True) + + + if remote := new_fields.get("remote"): + ansible_remote = AHPulpAnsibleRemote(module) + ansible_remote.get_object(name=remote) + + if not ansible_remote.exists: + module.fail_json(msg="Remote {0} doesn\'t exist.".format(remote)) + + repo_fields["remote"] = ansible_remote.data.get("pulp_href") + else: + repo_fields["remote"] = None + + if ansible_repository.exists: + ansible_repository.update( + new_item=repo_fields, + auto_exit=False + ) + else: + ansible_repository.create( + new_item=repo_fields, + auto_exit=False + ) + + repo_href = ansible_repository.data.get('pulp_href') + + if distro: + if not ansible_distro.exists: + if distro_state == "present": + ansible_distro.create( + new_item={ + "base_path": distro_name, + "name": distro_name, + "repository": repo_href + }, + auto_exit=False + ) + else: + if distro_state == "absent": + ansible_distro.delete(auto_exit=False) + elif distro_state == "present": + ansible_distro.update( + new_item={ + "base_path": distro_name, + "repository": repo_href + }, + auto_exit=False + ) + + json_output = { + "name": name, + "type": ansible_repository.object_type, + "changed": True, + } + module.exit_json(**json_output) + + +if __name__ == "__main__": + main()