diff --git a/CHANGELOG.md b/CHANGELOG.md index e75d781d7..9be81a5ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.5.1] - 2024-03-11 + +### Fixed + +- Fixed gslbservice idempotency issues ([#377]) + ## [2.5.0] - 2024-02-22 ### Added @@ -104,7 +110,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial Release -[unreleased]: https://github.com/netscaler/ansible-collection-netscaleradc/compare/2.5.0...HEAD +[unreleased]: https://github.com/netscaler/ansible-collection-netscaleradc/compare/2.5.1...HEAD +[2.5.1]: https://github.com/netscaler/ansible-collection-netscaleradc/compare/2.5.0...2.5.1 [2.5.0]: https://github.com/netscaler/ansible-collection-netscaleradc/compare/2.4.0...2.5.0 [2.4.0]: https://github.com/netscaler/ansible-collection-netscaleradc/compare/2.3.0...2.4.0 [2.3.0]: https://github.com/netscaler/ansible-collection-netscaleradc/compare/2.2.0...2.3.0 @@ -137,4 +144,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#360]: https://github.com/netscaler/ansible-collection-netscaleradc/issues/360 [#362]: https://github.com/netscaler/ansible-collection-netscaleradc/issues/362 [#365]: https://github.com/netscaler/ansible-collection-netscaleradc/issues/365 -[#367]: https://github.com/netscaler/ansible-collection-netscaleradc/issues/367 \ No newline at end of file +[#367]: https://github.com/netscaler/ansible-collection-netscaleradc/issues/367 +[#377]: https://github.com/netscaler/ansible-collection-netscaleradc/issues/377 diff --git a/galaxy.yml b/galaxy.yml index 2a93c35ba..b5ff2afdf 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -7,7 +7,7 @@ namespace: netscaler # The name of the collection. Has the same character restrictions as 'namespace' name: adc # The version of the collection. Must be compatible with semantic versioning -version: 2.5.0 +version: 2.5.1 # The path to the Markdown (.md) readme file. This path is relative to the root of the collection readme: README.md # A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) diff --git a/plugins/module_utils/constants.py b/plugins/module_utils/constants.py index 206ca5db3..f95e9d3c8 100644 --- a/plugins/module_utils/constants.py +++ b/plugins/module_utils/constants.py @@ -26,6 +26,19 @@ "sslcertkey": {"password"}, } +# NITRO accepts some attributes with a name and responsds with a different name in its GET reponse. +# Eg: For "gslbservice" resource, NITRO expects "ip" in POST request +# but expects "ipaddress" in PUT payload and returns "ipaddress" in GET response. +NITRO_ATTRIBUTES_ALIASES = { + # "resource": { + # "attribute": "attribute_alias", + # } + "gslbservice": { + "ip": "ipaddress", + "ipaddress": "ip", # For PUT payloads and GET responses + } +} + # https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#argument-spec NETSCALER_COMMON_ARGUMENTS = dict( nsip=dict( diff --git a/plugins/module_utils/module_executor.py b/plugins/module_utils/module_executor.py index 98f956474..e78898051 100644 --- a/plugins/module_utils/module_executor.py +++ b/plugins/module_utils/module_executor.py @@ -37,6 +37,7 @@ ATTRIBUTES_NOT_PRESENT_IN_GET_RESPONSE, HTTP_RESOURCE_ALREADY_EXISTS, NETSCALER_COMMON_ARGUMENTS, + NITRO_ATTRIBUTES_ALIASES, ) from .decorators import trace from .logger import log, loglines @@ -175,6 +176,18 @@ def _filter_desired_bindings(self): % (self.resource_name, self.desired_bindings) ) + @trace + def _add_nitro_attributes_aliases(self, payload): + for k, v in payload.copy().items(): + if k in NITRO_ATTRIBUTES_ALIASES[self.resource_name]: + alias_key = NITRO_ATTRIBUTES_ALIASES[self.resource_name][k] + log( + "DEBUG: Found alias key `%s` for `%s`. Adding the alias key to resource_module_params" + % (alias_key, k) + ) + payload[alias_key] = v + return payload + @trace def _filter_resource_module_params(self): log("DEBUG: self.module.params: %s" % self.module.params) @@ -188,6 +201,11 @@ def _filter_resource_module_params(self): # Also, filter out attributes ending with `_binding` as they are handled separately if v is not None: self.resource_module_params[k] = v + + if self.resource_name in NITRO_ATTRIBUTES_ALIASES: + self.resource_module_params = self._add_nitro_attributes_aliases( + self.resource_module_params + ) log( "DEBUG: Desired `%s` module specific params are: %s" % (self.resource_name, self.resource_module_params) @@ -216,6 +234,11 @@ def get_existing_resource(self): self.existing_resource = existing_resource[0] + if self.resource_name in NITRO_ATTRIBUTES_ALIASES: + self.existing_resource = self._add_nitro_attributes_aliases( + self.existing_resource + ) + # The below return is not mandatory. However, required for debugging purpose return self.existing_resource diff --git a/tests/integration/targets/gslbservice/aliases b/tests/integration/targets/gslbservice/aliases new file mode 100644 index 000000000..28a231e46 --- /dev/null +++ b/tests/integration/targets/gslbservice/aliases @@ -0,0 +1,3 @@ +gather_facts/no +netscaler/cpx/ +netscaler/vpx/ diff --git a/tests/integration/targets/gslbservice/tasks/main.yaml b/tests/integration/targets/gslbservice/tasks/main.yaml new file mode 100644 index 000000000..f61928451 --- /dev/null +++ b/tests/integration/targets/gslbservice/tasks/main.yaml @@ -0,0 +1,147 @@ +--- +- name: Include prerequisite tasks + ansible.builtin.include_tasks: setup.yaml +- name: GSLBSERVICE | ADD | --check + delegate_to: localhost + register: result + check_mode: true + tags: test + netscaler.adc.gslbservice: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + nitro_protocol: "{{ nitro_protocol }}" + validate_certs: "{{ validate_certs }}" + save_config: "{{ save_config }}" + state: present + ip: 10.10.10.10 + port: 65530 + servicename: test-gslb-service + servicetype: TCP + sitename: test-gslbsite +- name: Assert | GSLBSERVICE | ADD | --check + tags: test + ansible.builtin.assert: + that: + - "result.failed==false" + - "result.changed==true" +- name: GSLBSERVICE | ADD + delegate_to: localhost + register: result + check_mode: false + tags: test + netscaler.adc.gslbservice: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + nitro_protocol: "{{ nitro_protocol }}" + validate_certs: "{{ validate_certs }}" + save_config: "{{ save_config }}" + state: present + ip: 10.10.10.10 + port: 65530 + servicename: test-gslb-service + servicetype: TCP + sitename: test-gslbsite +- name: Assert | GSLBSERVICE | ADD + ansible.builtin.assert: + that: + - "result.failed==false" + - "result.changed==true" +- name: GSLBSERVICE | ADD | idempotent + delegate_to: localhost + register: result + check_mode: false + tags: test + netscaler.adc.gslbservice: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + nitro_protocol: "{{ nitro_protocol }}" + validate_certs: "{{ validate_certs }}" + save_config: "{{ save_config }}" + state: present + ip: 10.10.10.10 + port: 65530 + servicename: test-gslb-service + servicetype: TCP + sitename: test-gslbsite +- name: Assert | GSLBSERVICE | ADD | idempotent + tags: test + ansible.builtin.assert: + that: + - "result.failed==false" + - "result.changed==false" +- name: GSLBSERVICE | DELETE | --check + delegate_to: localhost + register: result + check_mode: true + tags: test + netscaler.adc.gslbservice: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + nitro_protocol: "{{ nitro_protocol }}" + validate_certs: "{{ validate_certs }}" + save_config: "{{ save_config }}" + state: absent + ip: 10.10.10.10 + port: 65530 + servicename: test-gslb-service + servicetype: TCP + sitename: test-gslbsite +- name: Assert | GSLBSERVICE | DELETE | --check + tags: test + ansible.builtin.assert: + that: + - "result.failed==false" + - "result.changed==true" +- name: GSLBSERVICE | DELETE + delegate_to: localhost + register: result + check_mode: false + tags: test + netscaler.adc.gslbservice: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + nitro_protocol: "{{ nitro_protocol }}" + validate_certs: "{{ validate_certs }}" + save_config: "{{ save_config }}" + state: absent + ip: 10.10.10.10 + port: 65530 + servicename: test-gslb-service + servicetype: TCP + sitename: test-gslbsite +- name: Assert | GSLBSERVICE | DELETE + ansible.builtin.assert: + that: + - "result.failed==false" + - "result.changed==true" +- name: GSLBSERVICE | DELETE | idempotent + delegate_to: localhost + register: result + check_mode: false + tags: test + netscaler.adc.gslbservice: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + nitro_protocol: "{{ nitro_protocol }}" + validate_certs: "{{ validate_certs }}" + save_config: "{{ save_config }}" + state: absent + ip: 10.10.10.10 + port: 65530 + servicename: test-gslb-service + servicetype: TCP + sitename: test-gslbsite +- name: Assert | GSLBSERVICE | DELETE | idempotent + tags: test + ansible.builtin.assert: + that: + - "result.failed==false" + - "result.changed==false" +- name: Include prerequisite tasks + ansible.builtin.include_tasks: teardown.yaml diff --git a/tests/integration/targets/gslbservice/tasks/setup.yaml b/tests/integration/targets/gslbservice/tasks/setup.yaml new file mode 100644 index 000000000..d53e267a4 --- /dev/null +++ b/tests/integration/targets/gslbservice/tasks/setup.yaml @@ -0,0 +1,13 @@ +--- +- name: Sample Task | service + delegate_to: localhost + netscaler.adc.gslbsite: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + nitro_protocol: "{{ nitro_protocol }}" + validate_certs: "{{ validate_certs }}" + save_config: "{{ save_config }}" + state: present + sitename: test-gslbsite + siteipaddress: 10.10.10.10 diff --git a/tests/integration/targets/gslbservice/tasks/teardown.yaml b/tests/integration/targets/gslbservice/tasks/teardown.yaml new file mode 100644 index 000000000..b6d1b614e --- /dev/null +++ b/tests/integration/targets/gslbservice/tasks/teardown.yaml @@ -0,0 +1,13 @@ +--- +- name: Sample Task | service + delegate_to: localhost + netscaler.adc.gslbsite: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + nitro_protocol: "{{ nitro_protocol }}" + validate_certs: "{{ validate_certs }}" + save_config: "{{ save_config }}" + state: absent + sitename: test-gslbsite + siteipaddress: 10.10.10.10 diff --git a/tests/integration/targets/gslbsite/aliases b/tests/integration/targets/gslbsite/aliases new file mode 100644 index 000000000..28a231e46 --- /dev/null +++ b/tests/integration/targets/gslbsite/aliases @@ -0,0 +1,3 @@ +gather_facts/no +netscaler/cpx/ +netscaler/vpx/ diff --git a/tests/integration/targets/gslbsite/tasks/main.yaml b/tests/integration/targets/gslbsite/tasks/main.yaml new file mode 100644 index 000000000..558ff895e --- /dev/null +++ b/tests/integration/targets/gslbsite/tasks/main.yaml @@ -0,0 +1,125 @@ +--- +- name: GSLBSITE | ADD | --check + delegate_to: localhost + register: result + check_mode: true + tags: test + netscaler.adc.gslbsite: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + nitro_protocol: "{{ nitro_protocol }}" + validate_certs: "{{ validate_certs }}" + save_config: "{{ save_config }}" + state: present + sitename: test-gslbsite + siteipaddress: 10.10.10.10 +- name: Assert | GSLBSITE | ADD | --check + tags: test + ansible.builtin.assert: + that: + - "result.failed==false" + - "result.changed==true" +- name: GSLBSITE | ADD + delegate_to: localhost + register: result + check_mode: false + tags: test + netscaler.adc.gslbsite: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + nitro_protocol: "{{ nitro_protocol }}" + validate_certs: "{{ validate_certs }}" + save_config: "{{ save_config }}" + state: present + sitename: test-gslbsite + siteipaddress: 10.10.10.10 +- name: Assert | GSLBSITE | ADD + ansible.builtin.assert: + that: + - "result.failed==false" + - "result.changed==true" +- name: GSLBSITE | ADD | idempotent + delegate_to: localhost + register: result + check_mode: false + tags: test + netscaler.adc.gslbsite: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + nitro_protocol: "{{ nitro_protocol }}" + validate_certs: "{{ validate_certs }}" + save_config: "{{ save_config }}" + state: present + sitename: test-gslbsite + siteipaddress: 10.10.10.10 +- name: Assert | GSLBSITE | ADD | idempotent + tags: test + ansible.builtin.assert: + that: + - "result.failed==false" + - "result.changed==false" +- name: GSLBSITE | DELETE | --check + delegate_to: localhost + register: result + check_mode: true + tags: test + netscaler.adc.gslbsite: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + nitro_protocol: "{{ nitro_protocol }}" + validate_certs: "{{ validate_certs }}" + save_config: "{{ save_config }}" + state: absent + sitename: test-gslbsite + siteipaddress: 10.10.10.10 +- name: Assert | GSLBSITE | DELETE | --check + tags: test + ansible.builtin.assert: + that: + - "result.failed==false" + - "result.changed==true" +- name: GSLBSITE | DELETE + delegate_to: localhost + register: result + check_mode: false + tags: test + netscaler.adc.gslbsite: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + nitro_protocol: "{{ nitro_protocol }}" + validate_certs: "{{ validate_certs }}" + save_config: "{{ save_config }}" + state: absent + sitename: test-gslbsite + siteipaddress: 10.10.10.10 +- name: Assert | GSLBSITE | DELETE + ansible.builtin.assert: + that: + - "result.failed==false" + - "result.changed==true" +- name: GSLBSITE | DELETE | idempotent + delegate_to: localhost + register: result + check_mode: false + tags: test + netscaler.adc.gslbsite: + nsip: "{{ nsip }}" + nitro_user: "{{ nitro_user }}" + nitro_pass: "{{ nitro_pass }}" + nitro_protocol: "{{ nitro_protocol }}" + validate_certs: "{{ validate_certs }}" + save_config: "{{ save_config }}" + state: absent + sitename: test-gslbsite + siteipaddress: 10.10.10.10 +- name: Assert | GSLBSITE | DELETE | idempotent + tags: test + ansible.builtin.assert: + that: + - "result.failed==false" + - "result.changed==false"