#!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2013, Hiroaki Nakamura # # This file is part of Ansible # # Ansible 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. # # Ansible 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 Ansible. If not, see . ANSIBLE_METADATA = {'status': ['preview'], 'supported_by': 'committer', 'version': '1.0'} DOCUMENTATION = ''' --- module: hostname author: - "Adrian Likins (@alikins)" - "Hideki Saito (@saito-hideki)" version_added: "1.4" short_description: Manage hostname requirements: [ hostname ] description: - Set system's hostname. - Currently implemented on Debian, Ubuntu, Fedora, RedHat, openSUSE, Linaro, ScientificLinux, Arch, CentOS, AMI, Alpine Linux. - Any distribution that uses systemd as their init system. - Note, this module does *NOT* modify /etc/hosts. You need to modify it yourself using other modules like template or replace. options: name: required: true description: - Name of the host ''' EXAMPLES = ''' - hostname: name: web01 ''' import socket from distutils.version import LooseVersion # import module snippets from ansible.module_utils.basic import * from ansible.module_utils.facts import * from ansible.module_utils._text import to_bytes, to_native class UnimplementedStrategy(object): def __init__(self, module): self.module = module def update_current_and_permanent_hostname(self): self.unimplemented_error() def update_current_hostname(self): self.unimplemented_error() def update_permanent_hostname(self): self.unimplemented_error() def get_current_hostname(self): self.unimplemented_error() def set_current_hostname(self, name): self.unimplemented_error() def get_permanent_hostname(self): self.unimplemented_error() def set_permanent_hostname(self, name): self.unimplemented_error() def unimplemented_error(self): platform = get_platform() distribution = get_distribution() if distribution is not None: msg_platform = '%s (%s)' % (platform, distribution) else: msg_platform = platform self.module.fail_json( msg='hostname module cannot be used on platform %s' % msg_platform) class Hostname(object): """ This is a generic Hostname manipulation class that is subclassed based on platform. A subclass may wish to set different strategy instance to self.strategy. All subclasses MUST define platform and distribution (which may be None). """ platform = 'Generic' distribution = None strategy_class = UnimplementedStrategy def __new__(cls, *args, **kwargs): return load_platform_subclass(Hostname, args, kwargs) def __init__(self, module): self.module = module self.name = module.params['name'] if self.platform == 'Linux' and Facts(module).is_systemd_managed(): self.strategy = SystemdStrategy(module) else: self.strategy = self.strategy_class(module) def update_current_and_permanent_hostname(self): return self.strategy.update_current_and_permanent_hostname() def get_current_hostname(self): return self.strategy.get_current_hostname() def set_current_hostname(self, name): self.strategy.set_current_hostname(name) def get_permanent_hostname(self): return self.strategy.get_permanent_hostname() def set_permanent_hostname(self, name): self.strategy.set_permanent_hostname(name) class GenericStrategy(object): """ This is a generic Hostname manipulation strategy class. A subclass may wish to override some or all of these methods. - get_current_hostname() - get_permanent_hostname() - set_current_hostname(name) - set_permanent_hostname(name) """ def __init__(self, module): self.module = module self.hostname_cmd = self.module.get_bin_path('hostname', True) self.changed = False def update_current_and_permanent_hostname(self): self.update_current_hostname() self.update_permanent_hostname() return self.changed def update_current_hostname(self): name = self.module.params['name'] current_name = self.get_current_hostname() if current_name != name: self.set_current_hostname(name) self.changed = True def update_permanent_hostname(self): name = self.module.params['name'] permanent_name = self.get_permanent_hostname() if permanent_name != name: self.set_permanent_hostname(name) self.changed = True def get_current_hostname(self): cmd = [self.hostname_cmd] rc, out, err = self.module.run_command(cmd) if rc != 0: self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % (rc, out, err)) return to_native(out).strip() def set_current_hostname(self, name): cmd = [self.hostname_cmd, name] rc, out, err = self.module.run_command(cmd) if rc != 0: self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % (rc, out, err)) def get_permanent_hostname(self): return None def set_permanent_hostname(self, name): pass # =========================================== class DebianStrategy(GenericStrategy): """ This is a Debian family Hostname manipulation strategy class - it edits the /etc/hostname file. """ HOSTNAME_FILE = '/etc/hostname' def get_permanent_hostname(self): if not os.path.isfile(self.HOSTNAME_FILE): try: open(self.HOSTNAME_FILE, "a").write("") except IOError: err = get_exception() self.module.fail_json(msg="failed to write file: %s" % str(err)) try: f = open(self.HOSTNAME_FILE) try: return f.read().strip() finally: f.close() except Exception: err = get_exception() self.module.fail_json(msg="failed to read hostname: %s" % str(err)) def set_permanent_hostname(self, name): try: f = open(self.HOSTNAME_FILE, 'w+') try: f.write("%s\n" % name) finally: f.close() except Exception: err = get_exception() self.module.fail_json(msg="failed to update hostname: %s" % str(err)) # =========================================== class SLESStrategy(GenericStrategy): """ This is a SLES Hostname strategy class - it edits the /etc/HOSTNAME file. """ HOSTNAME_FILE = '/etc/HOSTNAME' def get_permanent_hostname(self): if not os.path.isfile(self.HOSTNAME_FILE): try: open(self.HOSTNAME_FILE, "a").write("") except IOError: err = get_exception() self.module.fail_json(msg="failed to write file: %s" % str(err)) try: f = open(self.HOSTNAME_FILE) try: return f.read().strip() finally: f.close() except Exception: err = get_exception() self.module.fail_json(msg="failed to read hostname: %s" % str(err)) def set_permanent_hostname(self, name): try: f = open(self.HOSTNAME_FILE, 'w+') try: f.write("%s\n" % name) finally: f.close() except Exception: err = get_exception() self.module.fail_json(msg="failed to update hostname: %s" % str(err)) # =========================================== class RedHatStrategy(GenericStrategy): """ This is a Redhat Hostname strategy class - it edits the /etc/sysconfig/network file. """ NETWORK_FILE = '/etc/sysconfig/network' def get_permanent_hostname(self): try: f = open(self.NETWORK_FILE, 'rb') try: for line in f.readlines(): if line.startswith('HOSTNAME'): k, v = line.split('=') return v.strip() finally: f.close() except Exception: err = get_exception() self.module.fail_json(msg="failed to read hostname: %s" % str(err)) def set_permanent_hostname(self, name): try: lines = [] found = False f = open(self.NETWORK_FILE, 'rb') try: for line in f.readlines(): if line.startswith('HOSTNAME'): lines.append("HOSTNAME=%s\n" % name) found = True else: lines.append(line) finally: f.close() if not found: lines.append("HOSTNAME=%s\n" % name) f = open(self.NETWORK_FILE, 'w+') try: f.writelines(lines) finally: f.close() except Exception: err = get_exception() self.module.fail_json(msg="failed to update hostname: %s" % str(err)) # =========================================== class AlpineStrategy(GenericStrategy): """ This is a Alpine Linux Hostname manipulation strategy class - it edits the /etc/hostname file then run hostname -F /etc/hostname. """ HOSTNAME_FILE = '/etc/hostname' def update_current_and_permanent_hostname(self): self.update_permanent_hostname() self.update_current_hostname() return self.changed def get_permanent_hostname(self): if not os.path.isfile(self.HOSTNAME_FILE): try: open(self.HOSTNAME_FILE, "a").write("") except IOError: err = get_exception() self.module.fail_json(msg="failed to write file: %s" % str(err)) try: f = open(self.HOSTNAME_FILE) try: return f.read().strip() finally: f.close() except Exception: err = get_exception() self.module.fail_json(msg="failed to read hostname: %s" % str(err)) def set_permanent_hostname(self, name): try: f = open(self.HOSTNAME_FILE, 'w+') try: f.write("%s\n" % name) finally: f.close() except Exception: err = get_exception() self.module.fail_json(msg="failed to update hostname: %s" % str(err)) def set_current_hostname(self, name): cmd = [self.hostname_cmd, '-F', self.HOSTNAME_FILE] rc, out, err = self.module.run_command(cmd) if rc != 0: self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % (rc, out, err)) # =========================================== class SystemdStrategy(GenericStrategy): """ This is a Systemd hostname manipulation strategy class - it uses the hostnamectl command. """ def get_current_hostname(self): cmd = ['hostname'] rc, out, err = self.module.run_command(cmd) if rc != 0: self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % (rc, out, err)) return to_native(out).strip() def set_current_hostname(self, name): if len(name) > 64: self.module.fail_json(msg="name cannot be longer than 64 characters on systemd servers, try a shorter name") cmd = ['hostnamectl', '--transient', 'set-hostname', name] rc, out, err = self.module.run_command(cmd) if rc != 0: self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % (rc, out, err)) def get_permanent_hostname(self): cmd = ['hostnamectl', '--static', 'status'] rc, out, err = self.module.run_command(cmd) if rc != 0: self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % (rc, out, err)) return to_native(out).strip() def set_permanent_hostname(self, name): if len(name) > 64: self.module.fail_json(msg="name cannot be longer than 64 characters on systemd servers, try a shorter name") cmd = ['hostnamectl', '--pretty', 'set-hostname', name] rc, out, err = self.module.run_command(cmd) if rc != 0: self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % (rc, out, err)) cmd = ['hostnamectl', '--static', 'set-hostname', name] rc, out, err = self.module.run_command(cmd) if rc != 0: self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % (rc, out, err)) # =========================================== class OpenRCStrategy(GenericStrategy): """ This is a Gentoo (OpenRC) Hostname manipulation strategy class - it edits the /etc/conf.d/hostname file. """ HOSTNAME_FILE = '/etc/conf.d/hostname' def get_permanent_hostname(self): try: try: f = open(self.HOSTNAME_FILE, 'r') for line in f: line = line.strip() if line.startswith('hostname='): return line[10:].strip('"') except Exception: err = get_exception() self.module.fail_json(msg="failed to read hostname: %s" % str(err)) finally: f.close() return None def set_permanent_hostname(self, name): try: try: f = open(self.HOSTNAME_FILE, 'r') lines = [x.strip() for x in f] for i, line in enumerate(lines): if line.startswith('hostname='): lines[i] = 'hostname="%s"' % name break f.close() f = open(self.HOSTNAME_FILE, 'w') f.write('\n'.join(lines) + '\n') except Exception: err = get_exception() self.module.fail_json(msg="failed to update hostname: %s" % str(err)) finally: f.close() # =========================================== class OpenBSDStrategy(GenericStrategy): """ This is a OpenBSD family Hostname manipulation strategy class - it edits the /etc/myname file. """ HOSTNAME_FILE = '/etc/myname' def get_permanent_hostname(self): if not os.path.isfile(self.HOSTNAME_FILE): try: open(self.HOSTNAME_FILE, "a").write("") except IOError: err = get_exception() self.module.fail_json(msg="failed to write file: %s" % str(err)) try: f = open(self.HOSTNAME_FILE) try: return f.read().strip() finally: f.close() except Exception: err = get_exception() self.module.fail_json(msg="failed to read hostname: %s" % str(err)) def set_permanent_hostname(self, name): try: f = open(self.HOSTNAME_FILE, 'w+') try: f.write("%s\n" % name) finally: f.close() except Exception: err = get_exception() self.module.fail_json(msg="failed to update hostname: %s" % str(err)) # =========================================== class SolarisStrategy(GenericStrategy): """ This is a Solaris11 or later Hostname manipulation strategy class - it execute hostname command. """ def set_current_hostname(self, name): cmd_option = '-t' cmd = [self.hostname_cmd, cmd_option, name] rc, out, err = self.module.run_command(cmd) if rc != 0: self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % (rc, out, err)) def get_permanent_hostname(self): fmri = 'svc:/system/identity:node' pattern = 'config/nodename' cmd = '/usr/sbin/svccfg -s %s listprop -o value %s' % (fmri, pattern) rc, out, err = self.module.run_command(cmd, use_unsafe_shell=True) if rc != 0: self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % (rc, out, err)) return to_native(out).strip() def set_permanent_hostname(self, name): cmd = [self.hostname_cmd, name] rc, out, err = self.module.run_command(cmd) if rc != 0: self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" % (rc, out, err)) # =========================================== class FreeBSDStrategy(GenericStrategy): """ This is a FreeBSD hostname manipulation strategy class - it edits the /etc/rc.conf.d/hostname file. """ HOSTNAME_FILE = '/etc/rc.conf.d/hostname' def get_permanent_hostname(self): if not os.path.isfile(self.HOSTNAME_FILE): try: open(self.HOSTNAME_FILE, "a").write("hostname=temporarystub\n") except IOError: err = get_exception() self.module.fail_json(msg="failed to write file: %s" % str(err)) try: try: f = open(self.HOSTNAME_FILE, 'r') for line in f: line = line.strip() if line.startswith('hostname='): return line[10:].strip('"') except Exception: err = get_exception() self.module.fail_json(msg="failed to read hostname: %s" % str(err)) finally: f.close() return None def set_permanent_hostname(self, name): try: try: f = open(self.HOSTNAME_FILE, 'r') lines = [x.strip() for x in f] for i, line in enumerate(lines): if line.startswith('hostname='): lines[i] = 'hostname="%s"' % name break f.close() f = open(self.HOSTNAME_FILE, 'w') f.write('\n'.join(lines) + '\n') except Exception: err = get_exception() self.module.fail_json(msg="failed to update hostname: %s" % str(err)) finally: f.close() # =========================================== class FedoraHostname(Hostname): platform = 'Linux' distribution = 'Fedora' strategy_class = SystemdStrategy class SLESHostname(Hostname): platform = 'Linux' distribution = 'Suse linux enterprise server ' distribution_version = get_distribution_version() if distribution_version and LooseVersion("10") <= LooseVersion(distribution_version) <= LooseVersion("12"): strategy_class = SLESStrategy else: strategy_class = UnimplementedStrategy class OpenSUSEHostname(Hostname): platform = 'Linux' distribution = 'Opensuse ' strategy_class = SystemdStrategy class ArchHostname(Hostname): platform = 'Linux' distribution = 'Arch' strategy_class = SystemdStrategy class RedHat5Hostname(Hostname): platform = 'Linux' distribution = 'Redhat' strategy_class = RedHatStrategy class RedHatServerHostname(Hostname): platform = 'Linux' distribution = 'Red hat enterprise linux server' strategy_class = RedHatStrategy class RedHatWorkstationHostname(Hostname): platform = 'Linux' distribution = 'Red hat enterprise linux workstation' strategy_class = RedHatStrategy class CentOSHostname(Hostname): platform = 'Linux' distribution = 'Centos' strategy_class = RedHatStrategy class CentOSLinuxHostname(Hostname): platform = 'Linux' distribution = 'Centos linux' strategy_class = RedHatStrategy class ScientificHostname(Hostname): platform = 'Linux' distribution = 'Scientific' strategy_class = RedHatStrategy class ScientificLinuxHostname(Hostname): platform = 'Linux' distribution = 'Scientific linux' strategy_class = RedHatStrategy class ScientificLinuxCERNHostname(Hostname): platform = 'Linux' distribution = 'Scientific linux cern slc' strategy_class = RedHatStrategy class OracleLinuxHostname(Hostname): platform = 'Linux' distribution = 'Oracle linux server' strategy_class = RedHatStrategy class AmazonLinuxHostname(Hostname): platform = 'Linux' distribution = 'Amazon' strategy_class = RedHatStrategy class DebianHostname(Hostname): platform = 'Linux' distribution = 'Debian' strategy_class = DebianStrategy class KaliHostname(Hostname): platform = 'Linux' distribution = 'Kali' strategy_class = DebianStrategy class UbuntuHostname(Hostname): platform = 'Linux' distribution = 'Ubuntu' strategy_class = DebianStrategy class LinuxmintHostname(Hostname): platform = 'Linux' distribution = 'Linuxmint' strategy_class = DebianStrategy class LinaroHostname(Hostname): platform = 'Linux' distribution = 'Linaro' strategy_class = DebianStrategy class GentooHostname(Hostname): platform = 'Linux' distribution = 'Gentoo base system' strategy_class = OpenRCStrategy class ALTLinuxHostname(Hostname): platform = 'Linux' distribution = 'Altlinux' strategy_class = RedHatStrategy class AlpineLinuxHostname(Hostname): platform = 'Linux' distribution = 'Alpine' strategy_class = AlpineStrategy class OpenBSDHostname(Hostname): platform = 'OpenBSD' distribution = None strategy_class = OpenBSDStrategy class SolarisHostname(Hostname): platform = 'SunOS' distribution = None strategy_class = SolarisStrategy class FreeBSDHostname(Hostname): platform = 'FreeBSD' distribution = None strategy_class = FreeBSDStrategy # =========================================== def main(): module = AnsibleModule( argument_spec = dict( name=dict(required=True) ) ) hostname = Hostname(module) name = module.params['name'] changed = hostname.update_current_and_permanent_hostname() module.exit_json(changed=changed, name=name, ansible_facts=dict(ansible_hostname=name.split('.')[0], ansible_nodename=name, ansible_fqdn=socket.getfqdn(), ansible_domain='.'.join(socket.getfqdn().split('.')[1:]))) if __name__ == '__main__': main()