diff --git a/library/ceph_orch_apply.py b/library/ceph_orch_apply.py index f110618..a498a57 100644 --- a/library/ceph_orch_apply.py +++ b/library/ceph_orch_apply.py @@ -15,15 +15,17 @@ # limitations under the License. from __future__ import absolute_import, division, print_function -from typing import List, Tuple +from typing import List, Tuple, Dict __metaclass__ = type +import datetime +import yaml + from ansible.module_utils.basic import AnsibleModule # type: ignore try: from ansible.module_utils.ceph_common import exit_module, build_base_cmd_orch # type: ignore except ImportError: from module_utils.ceph_common import exit_module, build_base_cmd_orch -import datetime ANSIBLE_METADATA = { @@ -70,6 +72,40 @@ ''' +def parse_spec(spec: str) -> Dict: + """ parse spec string to yaml """ + yaml_spec = yaml.safe_load(spec) + return yaml_spec + + +def retrieve_current_spec(module: AnsibleModule, expected_spec: Dict) -> Dict: + """ retrieve current config of the service """ + service: str = expected_spec["service_type"] + cmd = build_base_cmd_orch(module) + cmd.extend(['ls', service, '--format=yaml']) + out = module.run_command(cmd) + if isinstance(out, str): + # if there is no existing service, cephadm returns the string 'No services reported' + return {} + else: + return yaml.safe_load(out[1]) + + +def change_required(current: Dict, expected: Dict) -> bool: + """ checks if the current config differs from what is expected """ + if not current: + return True + + for key, value in expected.items(): + if key in current: + if current[key] != value: + return True + continue + else: + return True + return False + + def apply_spec(module: "AnsibleModule", data: str) -> Tuple[int, List[str], str, str]: cmd = build_base_cmd_orch(module) @@ -82,23 +118,24 @@ def apply_spec(module: "AnsibleModule", return rc, cmd, out, err -def main() -> None: +def run_module() -> None: + + module_args = dict( + spec=dict(type='str', required=True), + fsid=dict(type='str', required=False), + docker=dict(type=bool, + required=False, + default=False), + image=dict(type='str', required=False) + ) + module = AnsibleModule( - argument_spec=dict( - fsid=dict(type='str', required=False), - spec=dict(type='str', required=True), - docker=dict(type=bool, - required=False, - default=False), - image=dict(type='str', required=False) - ), + argument_spec=module_args, supports_check_mode=True ) - spec = module.params.get('spec') - startd = datetime.datetime.now() - changed = False + spec = module.params.get('spec') if module.check_mode: exit_module( @@ -111,8 +148,19 @@ def main() -> None: changed=False ) - rc, cmd, out, err = apply_spec(module, spec) - changed = True + # Idempotency check + expected = parse_spec(module.params.get('spec')) + current_spec = retrieve_current_spec(module, expected) + + if change_required(current_spec, expected): + rc, cmd, out, err = apply_spec(module, spec) + changed = True + else: + rc = 0 + cmd = [] + out = '' + err = '' + changed = False exit_module( module=module, @@ -125,5 +173,9 @@ def main() -> None: ) +def main() -> None: + run_module() + + if __name__ == '__main__': main() diff --git a/tox.ini b/tox.ini index f5aa3f0..ee42f76 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,7 @@ skipsdist = True basepython = python3 deps = mypy + types-PyYAML commands = mypy --config-file ./mypy.ini {toxinidir}/library {toxinidir}/module_utils [testenv:flake8]