Skip to content
Snippets Groups Projects
Unverified Commit 065e9021 authored by Rafael Guterres Jeffman's avatar Rafael Guterres Jeffman Committed by GitHub
Browse files

Merge pull request #1068 from t-woerner/replica_server_uninstall_cleanup

ipareplica/server: Enable removal from domain with undeployment
parents 96f5f5c8 476d9d50
No related branches found
No related tags found
No related merge requests found
......@@ -114,6 +114,50 @@ Example playbook to setup the IPA client(s) using principal and password from in
state: present
```
Example inventory file to remove a replica from the domain:
```ini
[ipareplicas]
ipareplica1.example.com
[ipareplicas:vars]
ipaadmin_password=MySecretPassword123
ipareplica_remove_from_domain=true
```
Example playbook to remove an IPA replica using admin passwords from the domain:
```yaml
---
- name: Playbook to remove IPA replica
hosts: ipareplica
become: true
roles:
- role: ipareplica
state: absent
```
The inventory will enable the removal of the replica (also a replica) from the domain. Additional options are needed if the removal of the replica is resulting in a topology disconnect or if the replica is the last that has a role.
To continue with the removal with a topology disconnect it is needed to set these parameters:
```ini
ipareplica_ignore_topology_disconnect=true
ipareplica_remove_on_server=ipareplica2.example.com
```
To continue with the removal for a replica that is the last that has a role:
```ini
ipareplica_ignore_last_of_role=true
```
Be careful with enabling the `ipareplica_ignore_topology_disconnect` and especially `ipareplica_ignore_last_of_role`, the change can not be reverted easily.
The parameters `ipaserver_ignore_topology_disconnect`, `ipaserver_ignore_last_of_role`, `ipaserver_remove_on_server` and `ipaserver_remove_from_domain` can be used instead.
Playbooks
=========
......@@ -255,6 +299,19 @@ Variable | Description | Required
`ipareplica_setup_firewalld` | The value defines if the needed services will automatically be openen in the firewall managed by firewalld. (bool, default: true) | no
`ipareplica_firewalld_zone` | The value defines the firewall zone that will be used. This needs to be an existing runtime and permanent zone. (string) | no
Undeploy Variables (`state`: absent)
------------------------------------
These settings should only be used if the result is really wanted. The change might not be revertable easily.
Variable | Description | Required
-------- | ----------- | --------
`ipareplica_ignore_topology_disconnect` \| `ipaserver_ignore_topology_disconnect` | If enabled this enforces the removal of the replica even if it results in a topology disconnect. Be careful with this setting. (bool) | false
`ipareplica_ignore_last_of_role` \| `ipaserver_ignore_last_of_role` | If enabled this enforces the removal of the replica even if the replica is the last with one that has a role. Be careful, this might not be revered easily. (bool) | false
`ipareplica_remove_from_domain` \| `ipaserver_remove_from_domain` | This enables the removal of the replica from the domain additionally to the undeployment. (bool) | false
`ipareplica_remove_on_server` \| `ipaserver_remove_on_server` | The value defines the replica in the domain that will to be used to remove the replica from the domain if `ipareplica_ignore_topology_disconnect` and `ipareplica_remove_from_domain` are enabled. Without the need to enable `ipareplica_ignore_topology_disconnect`, the value will be automatically detected using the replication agreements of the replica. (string) | false
Authors
=======
......
---
# tasks to uninstall IPA replica
- name: Uninstall - Uninstall IPA replica
ansible.builtin.command: >
/usr/sbin/ipa-server-install
--uninstall
-U
{{ "--ignore-topology-disconnect" if
ipareplica_ignore_topology_disconnect | bool else "" }}
{{ "--ignore-last-of-role" if ipareplica_ignore_last_of_role | bool
else "" }}
register: result_uninstall
# 2 means that uninstall failed because IPA replica was not configured
failed_when: result_uninstall.rc != 0 and "'Env' object
has no attribute 'basedn'" not in result_uninstall.stderr
# IPA server is not configured on this system" not in
# result_uninstall.stdout_lines
changed_when: result_uninstall.rc == 0
# until: result_uninstall.rc == 0
retries: 2
delay: 1
- name: Set parameters
ansible.builtin.set_fact:
_ignore_topology_disconnect: "{{ ipaserver_ignore_topology_disconnect | default(ipareplica_ignore_topology_disconnect) | default(omit) }}"
_ignore_last_of_role: "{{ ipaserver_ignore_last_of_role | default(ipareplica_ignore_last_of_role) | default(omit) }}"
_remove_from_domain: "{{ ipaserver_remove_from_domain | default(ipareplica_remove_from_domain) | default(omit) }}"
_remove_on_server: "{{ ipaserver_remove_on_server | default(ipareplica_remove_on_server) | default(omit) }}"
#- name: Uninstall - Remove all replication agreements and data about replica
# ansible.builtin.command: >
# /usr/sbin/ipa-replica-manage
# del
# {{ ipareplica_hostname | default(ansible_facts['fqdn']) }}
# --force
# --password={{ ipadm_password }}
# failed_when: False
# delegate_to: "{{ groups.ipaserver[0] | default(fail) }}"
#- name: Remove IPA replica packages
# ansible.builtin.package:
# name: "{{ ipareplica_packages }}"
# state: absent
- name: Uninstall - Uninstall replica
ansible.builtin.include_role:
name: ipaserver
vars:
state: absent
ipaserver_ignore_topology_disconnect: "{{ _ignore_topology_disconnect | default(false) }}"
ipaserver_ignore_last_of_role: "{{ _ignore_last_of_role | default(false) }}"
ipaserver_remove_from_domain: "{{ _remove_from_domain | default(false) }}"
ipaserver_remove_on_server: "{{ _remove_on_server | default(NULL) }}"
......@@ -79,7 +79,7 @@ Example playbook to setup the IPA server using admin and dirman passwords from a
state: present
```
Example playbook to unconfigure the IPA client(s) using principal and password from inventory file:
Example playbook to unconfigure the IPA server using principal and password from inventory file:
```yaml
---
......@@ -169,6 +169,48 @@ Server installation step 2: Copy `<ipaserver hostname>-chain.crt` to the IPA ser
The files can also be copied automatically: Set `ipaserver_copy_csr_to_controller` to true in the server installation step 1 and set `ipaserver_external_cert_files_from_controller` to point to the `chain.crt` file in the server installation step 2.
Example inventory file to remove a server from the domain:
```ini
[ipaserver]
ipaserver.example.com
[ipaserver:vars]
ipaadmin_password=MySecretPassword123
ipaserver_remove_from_domain=true
```
Example playbook to remove an IPA server using admin passwords from the domain:
```yaml
---
- name: Playbook to remove IPA server
hosts: ipaserver
become: true
roles:
- role: ipaserver
state: absent
```
The inventory will enable the removal of the server (also a replica) from the domain. Additional options are needed if the removal of the server/replica is resulting in a topology disconnect or if the server/replica is the last that has a role.
To continue with the removal with a topology disconnect it is needed to set these parameters:
```ini
ipaserver_ignore_topology_disconnect=true
ipaserver_remove_on_server=ipaserver2.example.com
```
To continue with the removal for a server that is the last that has a role:
```ini
ipaserver_ignore_last_of_role=true
```
Be careful with enabling the `ipaserver_ignore_topology_disconnect` and especially `ipaserver_ignore_last_of_role`, the change can not be reverted easily.
Playbooks
=========
......@@ -305,6 +347,19 @@ Variable | Description | Required
`ipaserver_external_cert_files_from_controller` | Files containing the IPA CA certificates and the external CA certificate chains on the controller that will be copied to the ipaserver host to `/root` folder. (list of string) | no
`ipaserver_copy_csr_to_controller` | Copy the generated CSR from the ipaserver to the controller as `"{{ inventory_hostname }}-ipa.csr"`. (bool) | no
Undeploy Variables (`state`: absent)
------------------------------------
These settings should only be used if the result is really wanted. The change might not be revertable easily.
Variable | Description | Required
-------- | ----------- | --------
`ipaserver_ignore_topology_disconnect` | If enabled this enforces the removal of the server even if it results in a topology disconnect. Be careful with this setting. (bool) | false
`ipaserver_ignore_last_of_role` | If enabled this enforces the removal of the server even if the server is the last with one that has a role. Be careful, this might not be revered easily. (bool) | false
`ipaserver_remove_from_domain` | This enables the removal of the server from the domain additionally to the undeployment. (bool) | false
`ipaserver_remove_on_server` | The value defines the server/replica in the domain that will to be used to remove the server/replica from the domain if `ipaserver_ignore_topology_disconnect` and `ipaserver_remove_from_domain` are enabled. Without the need to enable `ipaserver_ignore_topology_disconnect`, the value will be automatically detected using the replication agreements of the server/replica. (string) | false
Authors
=======
......
......@@ -42,3 +42,4 @@ ipaserver_copy_csr_to_controller: no
### uninstall ###
ipaserver_ignore_topology_disconnect: no
ipaserver_ignore_last_of_role: no
ipaserver_remove_from_domain: false
# -*- coding: utf-8 -*-
# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# Copyright (C) 2019-2022 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_get_connected_server
short_description: Get connected servers for server
description: Get connected servers for server
options:
ipaadmin_principal:
description: The admin principal.
default: admin
type: str
ipaadmin_password:
description: The admin password.
required: true
type: str
hostname:
description: The FQDN server name.
type: str
required: true
author:
- Thomas Woerner (@t-woerner)
"""
EXAMPLES = """
"""
RETURN = """
server:
description: Connected server name
returned: always
type: str
"""
import os
import tempfile
import shutil
from contextlib import contextmanager
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_text
from ansible.module_utils import six
try:
from ipalib import api
from ipalib import errors as ipalib_errors # noqa
from ipalib.config import Env
from ipaplatform.paths import paths
from ipapython.ipautil import run
from ipalib.constants import DEFAULT_CONFIG
try:
from ipalib.install.kinit import kinit_password
except ImportError:
from ipapython.ipautil import kinit_password
except ImportError as _err:
MODULE_IMPORT_ERROR = str(_err)
else:
MODULE_IMPORT_ERROR = None
if six.PY3:
unicode = str
def temp_kinit(principal, password):
"""Kinit with password using a temporary ccache."""
ccache_dir = tempfile.mkdtemp(prefix='krbcc')
ccache_name = os.path.join(ccache_dir, 'ccache')
try:
kinit_password(principal, password, ccache_name)
except RuntimeError as e:
raise RuntimeError("Kerberos authentication failed: %s" % str(e))
os.environ["KRB5CCNAME"] = ccache_name
return ccache_dir, ccache_name
def temp_kdestroy(ccache_dir, ccache_name):
"""Destroy temporary ticket and remove temporary ccache."""
if ccache_name is not None:
run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
del os.environ['KRB5CCNAME']
if ccache_dir is not None:
shutil.rmtree(ccache_dir, ignore_errors=True)
@contextmanager
def ipa_connect(module, principal=None, password=None):
"""
Create a context with a connection to IPA API.
Parameters
----------
module: AnsibleModule
The AnsibleModule to use
principal: string
The optional principal name
password: string
The admin password.
"""
if not password:
module.fail_json(msg="Password is required.")
if not principal:
principal = "admin"
ccache_dir = None
ccache_name = None
try:
ccache_dir, ccache_name = temp_kinit(principal, password)
# api_connect start
env = Env()
env._bootstrap()
env._finalize_core(**dict(DEFAULT_CONFIG))
api.bootstrap(context="server", debug=env.debug, log=None)
api.finalize()
if api.env.in_server:
backend = api.Backend.ldap2
else:
backend = api.Backend.rpcclient
if not backend.isconnected():
backend.connect(ccache=ccache_name)
# api_connect end
except Exception as e:
module.fail_json(msg=str(e))
else:
try:
yield ccache_name
except Exception as e:
module.fail_json(msg=str(e))
finally:
temp_kdestroy(ccache_dir, ccache_name)
def ipa_command(command, name, args):
"""
Execute an IPA API command with a required `name` argument.
Parameters
----------
command: string
The IPA API command to execute.
name: string
The name parameter to pass to the command.
args: dict
The parameters to pass to the command.
"""
return api.Command[command](name, **args)
def _afm_convert(value):
if value is not None:
if isinstance(value, list):
return [_afm_convert(x) for x in value]
if isinstance(value, dict):
return {_afm_convert(k): _afm_convert(v)
for k, v in value.items()}
if isinstance(value, str):
return to_text(value)
return value
def module_params_get(module, name):
return _afm_convert(module.params.get(name))
def host_show(module, name):
_args = {
"all": True,
}
try:
_result = ipa_command("host_show", name, _args)
except ipalib_errors.NotFound as e:
msg = str(e)
if "host not found" in msg:
return None
module.fail_json(msg="host_show failed: %s" % msg)
return _result["result"]
def main():
module = AnsibleModule(
argument_spec=dict(
ipaadmin_principal=dict(type="str", default="admin"),
ipaadmin_password=dict(type="str", required=True, no_log=True),
hostname=dict(type="str", required=True),
),
supports_check_mode=True,
)
if MODULE_IMPORT_ERROR is not None:
module.fail_json(msg=MODULE_IMPORT_ERROR)
# In check mode always return changed.
if module.check_mode:
module.exit_json(changed=False)
ipaadmin_principal = module_params_get(module, "ipaadmin_principal")
ipaadmin_password = module_params_get(module, "ipaadmin_password")
hostname = module_params_get(module, "hostname")
server = None
right_left = ["iparepltoposegmentrightnode", "iparepltoposegmentleftnode"]
with ipa_connect(module, ipaadmin_principal, ipaadmin_password):
# At first search in the domain, then ca suffix:
# Search for the first iparepltoposegmentleftnode (node 2), where
# iparepltoposegmentrightnode is hostname (node 1), then for the
# first iparepltoposegmentrightnode (node 2) where
# iparepltoposegmentleftnode is hostname (node 1).
for suffix_name in ["domain", "ca"]:
for node1, node2 in [[right_left[0], right_left[1]],
[right_left[1], right_left[0]]]:
args = {node1: hostname}
result = api.Command.topologysegment_find(
suffix_name, **args)
if result and "result" in result and len(result["result"]) > 0:
res = result["result"][0]
if node2 in res:
if len(res[node2]) > 0:
server = res[node2][0]
break
if server is not None:
module.exit_json(changed=False, server=server)
module.exit_json(changed=False)
if __name__ == "__main__":
main()
---
# tasks to uninstall IPA server
- name: Uninstall - Set server hostname for removal
ansible.builtin.set_fact:
_remove_hostname: "{{ ansible_facts['fqdn'] }}"
- name: Uninstall - Remove server
when: ipaserver_remove_from_domain
block:
- name: Uninstall - Fail on missing ipaadmin_password for server removal
ansible.builtin.fail:
msg: "'ipaadmin_password' is needed for 'ipaserver_remove_from_domain'"
when: ipaadmin_password is not defined
- name: Uninstall - Fail on missing ipaserver_remove_on_server with ipaserver_ignore_topology_disconnect
ansible.builtin.fail:
msg: "'ipaserver_remove_on_server' is needed for 'ipaserver_remove_from_domain' with 'ipaserver_ignore_topology_disconnect'"
when: ipaserver_ignore_topology_disconnect | bool
and ipaserver_remove_on_server is not defined
- name: Uninstall - Get connected server
ipaserver_get_connected_server:
ipaadmin_principal: "{{ ipaadmin_principal | default('admin') }}"
ipaadmin_password: "{{ ipaadmin_password }}"
hostname: "{{ _remove_hostname }}"
register: result_get_connected_server
when: ipaserver_remove_on_server is not defined
# REMOVE SERVER FROM DOMAIN
- name: Uninstall - Server del "{{ _remove_hostname }}"
ipaserver:
ipaadmin_principal: "{{ ipaadmin_principal | default('admin') }}"
ipaadmin_password: "{{ ipaadmin_password }}"
name: "{{ _remove_hostname }}"
ignore_last_of_role: "{{ ipaserver_ignore_last_of_role }}"
ignore_topology_disconnect: "{{ ipaserver_ignore_topology_disconnect }}"
# delete_continue: "{{ ipaserver_delete_continue }}"
state: absent
delegate_to: "{{ ipaserver_remove_on_server | default(result_get_connected_server.server) }}"
when: ipaserver_remove_on_server is defined or
result_get_connected_server.server is defined
- name: Uninstall - Uninstall IPA server
ansible.builtin.command: >
/usr/sbin/ipa-server-install
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment