# -*- coding: utf-8 -*- # Authors: # Thomas Woerner <twoerner@redhat.com> # # Copyright (C) 2021 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. from __future__ import (absolute_import, division, print_function) __metaclass__ = type ANSIBLE_METADATA = { "metadata_version": "1.0", "supported_by": "community", "status": ["preview"], } DOCUMENTATION = """ --- module: ipaserver short description: Manage FreeIPA server description: Manage FreeIPA server extends_documentation_fragment: - ipamodule_base_docs options: name: description: The list of server name strings. required: true aliases: ["cn"] location: description: | The server location string. "" for location reset. Only in state: present. required: false aliases: ["ipalocation_location"] service_weight: description: | Weight for server services Values 0 to 65535, -1 for weight reset. Only in state: present. required: false type: int aliases: ["ipaserviceweight"] hidden: description: | Set hidden state of a server. Only in state: present. required: false type: bool no_members: description: | Suppress processing of membership attributes Only in state: present. required: false type: bool delete_continue: description: | Continuous mode: Don't stop on errors. Only in state: absent. required: false type: bool aliases: ["continue"] ignore_last_of_role: description: | Skip a check whether the last CA master or DNS server is removed. Only in state: absent. required: false type: bool ignore_topology_disconnect: description: | Ignore topology connectivity problems after removal. Only in state: absent. required: false type: bool force: description: | Force server removal even if it does not exist. Will always result in changed. Only in state: absent. required: false type: bool state: description: The state to ensure. choices: ["present", "absent"] default: present required: true """ EXAMPLES = """ # Ensure server server.example.com is already present in the topology - ipaserver: ipaadmin_password: SomeADMINpassword name: server.example.com # Ensure server server.example.com is absent from the topology - ipaserver: ipaadmin_password: SomeADMINpassword name: server.example.com state: absent # Ensure server server.example.com has location mylocation - ipaserver: ipaadmin_password: SomeADMINpassword name: server.example.com location: mylocation # Ensure server server.example.com does not have a location - ipaserver: ipaadmin_password: SomeADMINpassword name: server.example.com location: "" # Ensure server server.example.com has service weight 1 - ipaserver: ipaadmin_password: SomeADMINpassword name: server.example.com service_weight: 1 # Ensure server server.example.com does not have a service weight - ipaserver: ipaadmin_password: SomeADMINpassword name: server.example.com service_weight: -1 # Ensure server server.example.com is hidden - ipaserver: ipaadmin_password: SomeADMINpassword name: server.example.com hidden: yes # Ensure server server.example.com is not hidden - ipaserver: ipaadmin_password: SomeADMINpassword name: server.example.com hidden: no # Ensure server server.example.com is absent from the topology in continuous # mode to ignore errors - ipaserver: ipaadmin_password: SomeADMINpassword name: server.example.com continue: yes state: absent # Ensure server "server.example.com" is absent from the topology with skipping # the last of role check - ipaserver: ipaadmin_password: SomeADMINpassword name: server.example.com ignore_last_of_role: yes state: absent # Ensure server server "server.example.com" is absent from the topology with # skipping the topology disconnect check - ipaserver: ipaadmin_password: SomeADMINpassword name: server.example.com ignore_topology_disconnect: yes state: absent # Ensure server server.example.com is absent in force mode - ipaserver: ipaadmin_password: SomeADMINpassword name: server.example.com force: yes state: absent """ RETURN = """ """ from ansible.module_utils.ansible_freeipa_module import \ IPAAnsibleModule, compare_args_ipa, DNSName def find_server(module, name): """Find if a server with the given name already exist.""" try: _result = module.ipa_command("server_show", name, {"all": True}) except Exception: # pylint: disable=broad-except # An exception is raised if server name is not found. return None else: return _result["result"] def server_role_status(module, name): """Get server role of a hidden server with the given name.""" try: _result = module.ipa_command_no_name("server_role_find", {"server_server": name, "role_servrole": 'IPA master', "include_master": True, "raw": True, "all": True}) except Exception: # pylint: disable=broad-except # An exception is raised if server name is not found. return None else: return _result["result"][0] def gen_args(location, service_weight, no_members, delete_continue, ignore_topology_disconnect, ignore_last_of_role, force): _args = {} if location is not None: if location != "": _args["ipalocation_location"] = DNSName(location) else: _args["ipalocation_location"] = None if service_weight is not None: _args["ipaserviceweight"] = service_weight if no_members is not None: _args["no_members"] = no_members if delete_continue is not None: _args["continue"] = delete_continue if ignore_topology_disconnect is not None: _args["ignore_topology_disconnect"] = ignore_topology_disconnect if ignore_last_of_role is not None: _args["ignore_last_of_role"] = ignore_last_of_role if force is not None: _args["force"] = force return _args def main(): ansible_module = IPAAnsibleModule( argument_spec=dict( # general name=dict(type="list", aliases=["cn"], default=None, required=True), # present location=dict(required=False, type='str', aliases=["ipalocation_location"], default=None), service_weight=dict(required=False, type='int', aliases=["ipaserviceweight"], default=None), hidden=dict(required=False, type='bool', default=None), no_members=dict(required=False, type='bool', default=None), # absent delete_continue=dict(required=False, type='bool', aliases=["continue"], default=None), ignore_topology_disconnect=dict(required=False, type='bool', default=None), ignore_last_of_role=dict(required=False, type='bool', default=None), force=dict(required=False, type='bool', default=None), # state state=dict(type="str", default="present", choices=["present", "absent"]), ), supports_check_mode=True, ) ansible_module._ansible_debug = True # Get parameters # general names = ansible_module.params_get("name") # present location = ansible_module.params_get("location") service_weight = ansible_module.params_get("service_weight") # Service weight smaller than 0 leads to resetting service weight if service_weight is not None and \ (service_weight < -1 or service_weight > 65535): ansible_module.fail_json( msg="service_weight %d is out of range [-1 .. 65535]" % service_weight) if service_weight == -1: service_weight = "" hidden = ansible_module.params_get("hidden") no_members = ansible_module.params_get("no_members") # absent delete_continue = ansible_module.params_get("delete_continue") ignore_topology_disconnect = ansible_module.params_get( "ignore_topology_disconnect") ignore_last_of_role = ansible_module.params_get("ignore_last_of_role") force = ansible_module.params_get("force") # state state = ansible_module.params_get("state") # Check parameters invalid = [] if state == "present": if len(names) != 1: ansible_module.fail_json( msg="Only one server can be ensured at a time.") invalid = ["delete_continue", "ignore_topology_disconnect", "ignore_last_of_role", "force"] if state == "absent": if len(names) < 1: ansible_module.fail_json(msg="No name given.") invalid = ["location", "service_weight", "hidden", "no_members"] ansible_module.params_fail_used_invalid(invalid, state) # Init changed = False exit_args = {} # Connect to IPA API with ansible_module.ipa_connect(): commands = [] for name in names: # Make sure server exists res_find = find_server(ansible_module, name) # Generate args args = gen_args(location, service_weight, no_members, delete_continue, ignore_topology_disconnect, ignore_last_of_role, force) # Create command if state == "present": # Server not found if res_find is None: ansible_module.fail_json( msg="Server '%s' not found" % name) # Remove location from args if "" (transformed to None) # and "ipalocation_location" not in res_find for idempotency if "ipalocation_location" in args and \ args["ipalocation_location"] is None and \ "ipalocation_location" not in res_find: del args["ipalocation_location"] # Remove service weight from args if "" # and "ipaserviceweight" not in res_find for idempotency if "ipaserviceweight" in args and \ args["ipaserviceweight"] == "" and \ "ipaserviceweight" not in res_find: del args["ipaserviceweight"] # For all settings is args, check if there are # different settings in the find result. # If yes: modify if not compare_args_ipa(ansible_module, args, res_find): commands.append([name, "server_mod", args]) # hidden handling if hidden is not None: res_role_status = server_role_status(ansible_module, name) if "status" in res_role_status: # Fail if status is configured, it should be done # only in the installer if res_role_status["status"] == "configured": ansible_module.fail_json( msg="'%s' in configured state, " "unable to change state" % state) if hidden and res_role_status["status"] == "enabled": commands.append([name, "server_state", {"state": "hidden"}]) if not hidden and \ res_role_status["status"] == "hidden": commands.append([name, "server_state", {"state": "enabled"}]) elif state == "absent": if res_find is not None or force: commands.append([name, "server_del", args]) else: ansible_module.fail_json(msg="Unkown state '%s'" % state) # Execute commands changed = ansible_module.execute_ipa_commands(commands) # Done ansible_module.exit_json(changed=changed, **exit_args) if __name__ == "__main__": main()