diff --git a/galaxy.yml b/galaxy.yml index e0110566..f159ba9b 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -8,7 +8,7 @@ namespace: ansible name: eda # The version of the collection. Must be compatible with semantic versioning -version: 2.1.0 +version: 2.2.0 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md diff --git a/plugins/module_utils/client.py b/plugins/module_utils/client.py index bd7f46ab..764f0772 100644 --- a/plugins/module_utils/client.py +++ b/plugins/module_utils/client.py @@ -173,7 +173,7 @@ def get(self, path: str, **kwargs: str) -> Response: def post(self, path: str, **kwargs: Any) -> Response: resp = self.request("POST", path, **kwargs) - if resp.status == 201: + if resp.status in [201, 202]: return resp raise EDAHTTPError(f"HTTP error {resp.json}") diff --git a/plugins/module_utils/controller.py b/plugins/module_utils/controller.py index aa83a0a4..4406e5b2 100644 --- a/plugins/module_utils/controller.py +++ b/plugins/module_utils/controller.py @@ -90,12 +90,12 @@ def get_exactly_one( return {} if len(result) > 1: if name: - # Since we did a name or ID search and got > 1 return - # something if the id matches + # Since we did a name search and got > 1 return + # something if the name matches for asset in result: - if str(asset["id"]) == name: + if str(asset["name"]) == name: return asset - # We got > 1 and either didn't find something by ID (which means + # We got > 1 and either didn't find something by name (which means # multiple names) # Or we weren't running with a or search and just got back too # many to begin with. @@ -184,6 +184,23 @@ def create_if_needed( msg = f"Unable to create {item_type} {item_name}: {response.status}" raise EDAError(msg) + def create( + self, new_item: dict[str, Any], endpoint: str, item_type: str = "unknown" + ) -> dict[str, bool]: + """Run a create (post) operation unconditionally and return the result.""" + + response = self.post_endpoint(endpoint, data=new_item) + + if response.status in [201, 202]: + self.result["changed"] = True + return self.result + + error_msg = f"Unable to create {item_type} {new_item}: {response.status} {response.data}" + if response.json: + error_msg = f"Unable to create {item_type} {new_item}: {response.json}" + + raise EDAError(error_msg) + def _encrypted_changed_warning( self, field: str, old: dict[str, Any], warning: bool = False ) -> None: diff --git a/plugins/modules/project.py b/plugins/modules/project.py index 4e6ca8a9..9eb4b51e 100644 --- a/plugins/modules/project.py +++ b/plugins/modules/project.py @@ -53,6 +53,13 @@ default: "present" choices: ["present", "absent"] type: str + sync: + description: + - Triggers the synchronization of the project. + - This only takes effect when the project already exists. + type: bool + default: False + version_added: 2.2.0 extends_documentation_fragment: - ansible.eda.eda_controller.auths """ @@ -110,15 +117,12 @@ def main() -> None: credential=dict(), organization_name=dict(type="str", aliases=["organization"]), state=dict(choices=["present", "absent"], default="present"), + sync=dict(type="bool", default=False), ) argument_spec.update(AUTH_ARGSPEC) - required_if = [("state", "present", ("name", "url"))] - - module = AnsibleModule( - argument_spec=argument_spec, required_if=required_if, supports_check_mode=True - ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) client = Client( host=module.params.get("controller_host"), @@ -136,24 +140,35 @@ def main() -> None: ) state = module.params.get("state") organization_name = module.params.get("organization_name") + project_name = module.params.get("name") + url = module.params.get("url") + sync_enabled = module.params.get("sync") + project_type = {} + + try: + project_type = controller.get_exactly_one(project_endpoint, name=project_name) + except EDAError as eda_err: + module.fail_json(msg=str(eda_err)) + if state == "present": - if config_endpoint_avail.status not in (404,) and organization_name is None: + if not project_type and not url: + module.fail_json( + msg="Parameter url is required when state is present and project does not exist" + ) + if ( + config_endpoint_avail.status not in (404,) + and organization_name is None + and not project_type + ): module.fail_json( - msg="Parameter organization_name is required when state is present" + msg="Parameter organization_name is required when state is present and project does not exist" ) - project_name = module.params.get("name") new_name = module.params.get("new_name") description = module.params.get("description") - url = module.params.get("url") credential = module.params.get("credential") ret = {} - try: - project_type = controller.get_exactly_one(project_endpoint, name=project_name) - except EDAError as eda_err: - module.fail_json(msg=str(eda_err)) - if state == "absent": # If the state was absent we can let the module delete it if needed, the module will handle exiting from this try: @@ -208,6 +223,17 @@ def main() -> None: except EDAError as eda_err: module.fail_json(msg=str(eda_err)) + if sync_enabled and project_type: + sync_endpoint = f"{project_endpoint}/{ret['id']}/sync" + try: + controller.create( + {"name": project_type_params["name"]}, + endpoint=sync_endpoint, + item_type="sync", + ) + except EDAError as eda_err: + module.fail_json(msg=str(eda_err)) + module.exit_json(**ret) diff --git a/tests/integration/targets/project/tasks/create.yml b/tests/integration/targets/project/tasks/create.yml index ffda25f9..6335b4ed 100644 --- a/tests/integration/targets/project/tasks/create.yml +++ b/tests/integration/targets/project/tasks/create.yml @@ -67,3 +67,37 @@ assert: that: - r.changed + +- name: Check create project with partial name match + loop: + - "{{ project_name }}_test_partial" + - "{{ project_name }}" + ansible.eda.project: + name: "{{ item }}" + url: "{{ url }}" + description: "Example project description" + organization_name: Default + state: present + +- name: Delete project with partial name match + loop: + - "{{ project_name }}" + - "{{ project_name }}_test_partial" + ansible.eda.project: + name: "{{ item }}" + state: absent + +- name: Check create project with missing url + ansible.eda.project: + name: "{{ project_name }}" + description: "Example project description" + organization_name: Default + state: present + register: r + ignore_errors: true + +- name: Check if missing url is required + assert: + that: + - r.failed + - "'Parameter url is required' in r.msg" diff --git a/tests/integration/targets/project/tasks/delete.yml b/tests/integration/targets/project/tasks/delete.yml index fbcf1a68..cee95bf2 100644 --- a/tests/integration/targets/project/tasks/delete.yml +++ b/tests/integration/targets/project/tasks/delete.yml @@ -4,9 +4,6 @@ - name: Delete operation without required name parameter ansible.eda.project: - controller_host: "{{ controller_host }}" - controller_username: "{{ controller_username }}" - controller_password: "{{ controller_password }}" state: absent ignore_errors: true register: r @@ -19,10 +16,6 @@ - name: Delete non-existing project in check mode ansible.eda.project: - controller_host: "{{ controller_host }}" - controller_username: "{{ controller_username }}" - controller_password: "{{ controller_password }}" - validate_certs: "{{ controller_verify_ssl }}" name: "{{ project_name }}" state: absent check_mode: true diff --git a/tests/integration/targets/project/tasks/main.yml b/tests/integration/targets/project/tasks/main.yml index a7fc5193..7f6e57b0 100644 --- a/tests/integration/targets/project/tasks/main.yml +++ b/tests/integration/targets/project/tasks/main.yml @@ -22,11 +22,12 @@ - name: Define variables for credential and decision environment set_fact: project_name: "test_project_{{ test_id }}" - url: "https://example.com/ansible/eda-server" + url: "https://github.com/ansible/eda-sample-project" - include_tasks: create.yml - include_tasks: delete.yml - include_tasks: update.yml + - include_tasks: sync.yml always: - name: Clean up - project ansible.eda.project: diff --git a/tests/integration/targets/project/tasks/sync.yml b/tests/integration/targets/project/tasks/sync.yml new file mode 100644 index 00000000..4ee4fa9b --- /dev/null +++ b/tests/integration/targets/project/tasks/sync.yml @@ -0,0 +1,86 @@ +--- +# Copyright: Contributors to the Ansible project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Create non existing project with sync + block: + - name: Create non existing project with sync + ansible.eda.project: + name: "{{ project_name }}_test_sync" + url: "{{ url }}" + description: "Example project description" + organization_name: Default + state: present + sync: true + register: r + + - name: Check project creation + assert: + that: + - r.changed + always: + - name: Clean up - project + ansible.eda.project: + name: "{{ project_name }}_test_sync" + state: absent + ignore_errors: true + +- name: Sync existing project + block: + - name: Create project before sync + ansible.eda.project: + name: "{{ project_name }}_test_sync" + url: "{{ url }}" + description: "Example project description" + organization_name: Default + state: present + register: r + + # need to wait for project creation otherwise sync can fail + - name: Wait for project creation + pause: + seconds: 5 + + - name: Sync project + ansible.eda.project: + name: "{{ project_name }}_test_sync" + sync: true + register: r + + - name: Get info project after sync + ansible.eda.project_info: + name: "{{ project_name }}_test_sync" + register: r_info + + - name: Check project sync + assert: + that: + - r.changed + - r_info.projects[0].modified_at != r_info.projects[0].created_at + always: + - name: Clean up - project + ansible.eda.project: + name: "{{ project_name }}_test_sync" + state: absent + ignore_errors: true + +- name: Check wrong parameters with sync enabled + block: + - name: Try to sync non existing project without url + ansible.eda.project: + name: "{{ project_name }}_test_sync" + sync: true + register: r + ignore_errors: true + + - name: Check if sync non existing project without url + assert: + that: + - r.failed + - "'Parameter url is required' in r.msg" + always: + - name: Clean up - project + ansible.eda.project: + name: "{{ project_name }}_test_sync" + state: absent + ignore_errors: true