rename_host instruction

This commit is contained in:
Emmanuel BENOîT 2022-11-06 13:54:13 +01:00
parent 84cc28b60f
commit 381970f626
No known key found for this signature in database
GPG key ID: 2356DC6956CF54EF
2 changed files with 95 additions and 2 deletions

View file

@ -118,6 +118,14 @@ used:
to write to the output. By default the message will be `fail requested`
followed by the name of the current host.
#### rename_host
This action changes the name of the current host. It can only be executed once
for each host. The actual renaming will occur after the script has completed
its execution. It requires the following field:
* `name`: the new name of the host.
#### set_fact
This action sets an Ansible fact associated to the current host. The following

View file

@ -7,6 +7,7 @@ from ansible.errors import AnsibleParserError, AnsibleRuntimeError, AnsibleError
from ansible.inventory.helpers import get_group_vars
from ansible.module_utils.six import string_types
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.parsing.utils import addresses
from ansible.plugins.inventory import BaseInventoryPlugin
from ansible.utils.vars import isidentifier, combine_vars
@ -82,6 +83,8 @@ DOCUMENTATION = """
Jinja templates.
- C(stop) stops processing the list of instructions for the current
host.
- C(rename_host) changes a host's name. It can only be executed once per
host.
type: list
elements: dict
required: True
@ -104,6 +107,7 @@ INSTR_OWN_FIELDS = {
"block": ("block", "rescue", "always", "locals"),
"create_group": ("group", "parent", "add_host"),
"fail": ("msg",),
"rename_host": ("name",),
"set_fact": ("name", "value"),
"set_var": ("name", "value"),
"stop": (),
@ -915,6 +919,46 @@ class RciFail(RcInstruction):
raise AnsibleRuntimeError(message)
class RciRenameHost(RcInstruction):
"""``rename_host`` instruction implementation."""
def __init__(self, inventory, templar, display):
super().__init__(inventory, templar, display, "rename_host")
self._name = None
self._name_may_be_template = None
def parse_action(self, record):
assert self._name is None and self._name_may_be_template is None
if "name" not in record:
raise AnsibleParserError("%s: missing 'name' field" % (self._action,))
name = record["name"]
if not isinstance(name, string_types):
raise AnsibleParserError("%s: 'name' must be a string" % (self._action,))
nmbt = self._templar.is_possibly_template(name)
if not (nmbt or addresses.patterns['hostname'].match(name)):
raise AnsibleParserError(
"%s: '%s' is not a valid host name" % (self._action, name)
)
self._name_may_be_template = nmbt
self._name = name
def execute_action(self, host_name, context):
if context.new_name is not None:
raise AnsibleRuntimeError("Host has already been renamed")
if self._name_may_be_template:
self._templar.available_variables = context.variables
name = self._templar.template(self._name)
if not addresses.patterns['hostname'].match(name):
raise AnsibleRuntimeError(
"%s: '%s' is not a valid host name" % (self._action, name)
)
else:
name = self._name
self._display.vvv("- renaming host %s to %s" % (host_name, name))
context.new_name = name
return True
class RciBlock(RcInstruction):
"""``block`` instruction implementation."""
@ -1042,6 +1086,7 @@ INSTRUCTIONS = {
"block": RciBlock,
"create_group": RciCreateGroup,
"fail": RciFail,
"rename_host": RciRenameHost,
"set_fact": lambda i, t, d: RciSetVarOrFact(i, t, d, True),
"set_var": lambda i, t, d: RciSetVarOrFact(i, t, d, False),
"stop": RciStop,
@ -1077,16 +1122,25 @@ class InventoryModule(BaseInventoryPlugin):
)
self.dump_program(instructions)
# Execute it for each host
rename = []
for host in inventory.hosts:
self.display.vvv("executing reconstructed script for %s" % (host,))
try:
self.exec_for_host(host, instructions)
new_name = self.exec_for_host(host, instructions)
except AnsibleError as e:
if self.get_option("strictness") == "full":
raise
self.display.warning(
"reconstructed - error on host %s: %s" % (host, repr(e))
)
continue
if new_name is not None:
rename.append((host, new_name))
# Rename hosts
for old_name, new_name in rename:
if old_name.lower() == new_name.lower():
continue
self.rename_host(old_name, new_name)
def exec_for_host(self, host, instructions):
"""Execute the program for a single host.
@ -1107,7 +1161,8 @@ class InventoryModule(BaseInventoryPlugin):
context = Context(combine_vars(group_vars, host_vars))
for instruction in instructions:
if not instruction.run_for(host, context):
return
break
return context.new_name
def dump_program(self, instructions):
"""Dump the whole program to the log, depending on verbosity level.
@ -1129,3 +1184,33 @@ class InventoryModule(BaseInventoryPlugin):
output.append("")
output.extend(instr.dump())
self.display.vvvv("parsed program:\n\n" + "\n".join(" " + s for s in output))
def rename_host(self, old_name, new_name):
"""Renames a host.
This method "renames" a host by removing the host's current inventory
record and creating a new one with the same data, except for the name.
Args:
old_name: the host's old name
new_name: the host's new name
"""
if self.inventory.get_host(new_name) is not None:
raise AnsibleRuntimeError("duplicate host name %s" % (new_name,))
self.display.vvv("renaming host %s to %s" % (old_name, new_name))
host = self.inventory.get_host(old_name)
group_names = []
for g in host.get_groups():
if g.name == 'all':
continue
if old_name in g.host_names:
group_names.append(g.name)
self.inventory.remove_host(host)
self.inventory.add_host(new_name)
new_host = self.inventory.get_host(new_name)
new_host._uuid = host._uuid
if host.address == host.name:
new_host.address = new_name
new_host.vars = host.vars
for group in group_names:
self.inventory.add_child(group, new_name)