From 6eb3dd319c665497d6f0a6177b93b63eb84cff21 Mon Sep 17 00:00:00 2001 From: Evan Kaufman Date: Thu, 31 Oct 2013 13:36:13 -0500 Subject: [PATCH 1/6] Set server hostname to {stage}.{domain}, as necessary Note this involves first passing stage into provisioning script (and subsequently into playbook), then updating /etc/host{s,name} and running `hostname` --- deployment/lib/provision.rb | 2 +- provisioning/roles/common/tasks/main.yml | 25 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/deployment/lib/provision.rb b/deployment/lib/provision.rb index b1f01ca..fe955b1 100644 --- a/deployment/lib/provision.rb +++ b/deployment/lib/provision.rb @@ -10,7 +10,7 @@ upload "./provisioning", "#{tmp}/provisioning", :via => :scp, :recursive => true upload "./bower_components/genesis-wordpress/provisioning", "#{tmp}/bower_components/genesis-wordpress/provisioning", :via => :scp, :recursive => true - sudo "#{tmp}/bin/provision" + sudo "#{tmp}/bin/provision -e stage=#{stage}" rescue puts "\n" diff --git a/provisioning/roles/common/tasks/main.yml b/provisioning/roles/common/tasks/main.yml index 6a8df1f..621b5f2 100644 --- a/provisioning/roles/common/tasks/main.yml +++ b/provisioning/roles/common/tasks/main.yml @@ -1,4 +1,29 @@ --- +- name: Determine optimal hostname + command: echo ${stage}.${domain} + register: new_host + ignore_errors: True + +- name: Determine current hostname + command: hostname + register: old_host + ignore_errors: True + +- name: Update current hostname + command: hostname ${new_host.stdout} + sudo: yes + when: old_host.stdout != new_host.stdout + +- name: Update canonical hostname + copy: content=${new_host.stdout} dest=/etc/hostname + sudo: yes + when: old_host.stdout != new_host.stdout + +- name: Update /etc/hosts for new hostname + command: perl -p -i.bak -e s/${old_host.stdout}/${new_host.stdout}/ig /etc/hosts + sudo: yes + when: old_host.stdout != new_host.stdout + - name: Add Node apt-repository apt_repository: repo='ppa:chris-lea/node.js' state=present sudo: yes From 0396e0dc14aacfc243fb3368ee453a7178f3ec76 Mon Sep 17 00:00:00 2001 From: Evan Kaufman Date: Fri, 8 Nov 2013 09:27:25 -0600 Subject: [PATCH 2/6] Bypass hostname checks if stage is not defined (eg, running `vagrant provision` directly) --- provisioning/roles/common/tasks/main.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/provisioning/roles/common/tasks/main.yml b/provisioning/roles/common/tasks/main.yml index 621b5f2..02d2419 100644 --- a/provisioning/roles/common/tasks/main.yml +++ b/provisioning/roles/common/tasks/main.yml @@ -3,26 +3,28 @@ command: echo ${stage}.${domain} register: new_host ignore_errors: True + when: stage is defined - name: Determine current hostname command: hostname register: old_host ignore_errors: True + when: stage is defined - name: Update current hostname command: hostname ${new_host.stdout} sudo: yes - when: old_host.stdout != new_host.stdout + when: stage is defined and old_host.stdout != new_host.stdout - name: Update canonical hostname copy: content=${new_host.stdout} dest=/etc/hostname sudo: yes - when: old_host.stdout != new_host.stdout + when: stage is defined and old_host.stdout != new_host.stdout - name: Update /etc/hosts for new hostname command: perl -p -i.bak -e s/${old_host.stdout}/${new_host.stdout}/ig /etc/hosts sudo: yes - when: old_host.stdout != new_host.stdout + when: stage is defined and old_host.stdout != new_host.stdout - name: Add Node apt-repository apt_repository: repo='ppa:chris-lea/node.js' state=present From a7946e8e836aac2718b10e931095421784ecd568 Mon Sep 17 00:00:00 2001 From: Evan Kaufman Date: Wed, 13 Nov 2013 12:23:19 -0600 Subject: [PATCH 3/6] Updated to use `hostname` and `replace` modules Both modules should be (hopefully) in ansible's 1.4 release, until then providing them here as custom lib modules: * hostname module, see ansible/ansible#3940 * replace module, see ansible/ansible#4889 --- provisioning/roles/common/library/hostname | 319 +++++++++++++++++++++ provisioning/roles/common/library/replace | 157 ++++++++++ provisioning/roles/common/tasks/main.yml | 11 +- 3 files changed, 479 insertions(+), 8 deletions(-) create mode 100644 provisioning/roles/common/library/hostname create mode 100644 provisioning/roles/common/library/replace diff --git a/provisioning/roles/common/library/hostname b/provisioning/roles/common/library/hostname new file mode 100644 index 0000000..9326d0f --- /dev/null +++ b/provisioning/roles/common/library/hostname @@ -0,0 +1,319 @@ +#!/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 . + +DOCUMENTATION = ''' +--- +module: hostname +author: Hiroaki Nakamura +version_added: "1.4" +short_description: Manage hostname +requirements: [ hostname ] +description: + - Set system's hostname + - Currently implemented on only Debian, Ubuntu, RedHat and CentOS. +options: + name: + required: true + description: + - Name of the host +''' + +EXAMPLES = ''' +- hostname: name=web01 +''' + +class UnimplementedStrategy(object): + def __init__(self, module): + self.module = module + + 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'] + self.strategy = self.strategy_class(module) + + 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 + + HOSTNAME_CMD = '/bin/hostname' + + 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 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): + try: + f = open(self.HOSTNAME_FILE) + try: + return f.read().strip() + finally: + f.close() + except Exception, err: + 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: + self.module.fail_json(msg="failed to update hostname: %s" % + str(err)) + +class DebianHostname(Hostname): + platform = 'Linux' + distribution = 'Debian' + strategy_class = DebianStrategy + +class UbuntuHostname(Hostname): + platform = 'Linux' + distribution = 'Ubuntu' + strategy_class = DebianStrategy + +# =========================================== + +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: + self.module.fail_json(msg="failed to read hostname: %s" % + str(err)) + + def set_permanent_hostname(self, name): + try: + lines = [] + f = open(self.NETWORK_FILE, 'rb') + try: + for line in f.readlines(): + if line.startswith('HOSTNAME'): + lines.append("HOSTNAME=%s\n" % name) + else: + lines.append(line) + finally: + f.close() + f = open(self.NETWORK_FILE, 'w+') + try: + f.writelines(lines) + finally: + f.close() + except Exception, err: + self.module.fail_json(msg="failed to update hostname: %s" % + str(err)) + +class RedHatHostname(Hostname): + platform = 'Linux' + distribution = 'Red hat enterprise linux server' + strategy_class = RedHatStrategy + +class CentOSHostname(Hostname): + platform = 'Linux' + distribution = 'Centos' + strategy_class = RedHatStrategy + +# =========================================== + +class FedoraStrategy(GenericStrategy): + """ + This is a Fedora family 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 out.strip() + + def set_current_hostname(self, 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 status | awk \'/^ *Static hostname:/{printf("%s", $3)}\'' + 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 out + + def set_permanent_hostname(self, 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 FedoraHostname(Hostname): + platform = 'Linux' + distribution = 'Fedora' + strategy_class = FedoraStrategy + +class OpenSUSEHostname(Hostname): + platform = 'Linux' + distribution = 'Opensuse ' + strategy_class = FedoraStrategy + +class ArchHostname(Hostname): + platform = 'Linux' + distribution = 'Arch' + strategy_class = FedoraStrategy + +# =========================================== + +def main(): + module = AnsibleModule( + argument_spec = dict( + name=dict(required=True, type='str') + ) + ) + + hostname = Hostname(module) + + changed = False + name = module.params['name'] + current_name = hostname.get_current_hostname() + if current_name != name: + hostname.set_current_hostname(name) + changed = True + + permanent_name = hostname.get_permanent_hostname() + if permanent_name != name: + hostname.set_permanent_hostname(name) + changed = True + + module.exit_json(changed=changed, name=name) + +# include magic from lib/ansible/module_common.py +#<> +main() diff --git a/provisioning/roles/common/library/replace b/provisioning/roles/common/library/replace new file mode 100644 index 0000000..aba935c --- /dev/null +++ b/provisioning/roles/common/library/replace @@ -0,0 +1,157 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2013, Evan Kaufman . + +import re +import os +import tempfile + +DOCUMENTATION = """ +--- +module: replace +author: Evan Kaufman +short_description: Replace all instances of a particular string in a + file using a back-referenced regular expression. +description: + - This module will replace all instances of a pattern within a file. + - It is up to the user to maintain idempotence by ensuring that the + same pattern would never match any replacements made. +options: + dest: + required: true + aliases: [ name, destfile ] + description: + - The file to modify. + regexp: + required: true + description: + - The regular expression to look for in the contents of the file. + Uses Python regular expressions; see + U(http://docs.python.org/2/library/re.html). + replace: + required: false + description: + - The string to replace regexp matches. May contain backreferences + that will get expanded with the regexp capture groups if the regexp + matches. If not set, matches are removed entirely. + backup: + required: false + default: "no" + choices: [ "yes", "no" ] + description: + - Create a backup file including the timestamp information so you can + get the original file back if you somehow clobbered it incorrectly. + validate: + required: false + description: + - validation to run before copying into place + required: false + default: None + others: + description: + - All arguments accepted by the M(file) module also work here. + required: false +""" + +EXAMPLES = r""" +- replace: dest=/etc/hosts regexp='(\s+)old\.host\.name(\s+.*)?$' replace='\1new.host.name\2' backup=yes + +- replace: dest=/home/jdoe/.ssh/known_hosts regexp='^old\.host\.name[^\n]*\n' owner=jdoe group=jdoe mode=644 + +- replace: dest=/etc/apache/ports regexp='^(NameVirtualHost|Listen)\s+80\s*$' replace='\1 127.0.0.1:8080' validate='/usr/sbin/apache2ctl -f %s -t' +""" + +def write_changes(module,contents,dest): + + tmpfd, tmpfile = tempfile.mkstemp() + f = os.fdopen(tmpfd,'wb') + f.write(contents) + f.close() + + validate = module.params.get('validate', None) + valid = not validate + if validate: + (rc, out, err) = module.run_command(validate % tmpfile) + valid = rc == 0 + if rc != 0: + module.fail_json(msg='failed to validate: ' + 'rc:%s error:%s' % (rc,err)) + if valid: + module.atomic_move(tmpfile, dest) + +def check_file_attrs(module, changed, message): + + file_args = module.load_file_common_arguments(module.params) + if module.set_file_attributes_if_different(file_args, False): + + if changed: + message += " and " + changed = True + message += "ownership, perms or SE linux context changed" + + return message, changed + +def main(): + module = AnsibleModule( + argument_spec=dict( + dest=dict(required=True, aliases=['name', 'destfile']), + regexp=dict(required=True), + replace=dict(default='', type='str'), + backup=dict(default=False, type='bool'), + validate=dict(default=None, type='str'), + ), + add_file_common_args=True, + supports_check_mode=True + ) + + params = module.params + dest = os.path.expanduser(params['dest']) + + if os.path.isdir(dest): + module.fail_json(rc=256, msg='Destination %s is a directory !' % dest) + + if not os.path.exists(dest): + module.fail_json(rc=257, msg='Destination %s does not exist !' % dest) + else: + f = open(dest, 'rb') + contents = f.read() + f.close() + + mre = re.compile(params['regexp'], re.MULTILINE) + result = re.subn(mre, params['replace'], contents, 0) + + if result[1] > 0: + msg = '%s replacements made' % result[1] + changed = True + else: + msg = '' + changed = False + + if changed and not module.check_mode: + if params['backup'] and os.path.exists(dest): + module.backup_local(dest) + write_changes(module, result[0], dest) + + msg, changed = check_file_attrs(module, changed, msg) + module.exit_json(changed=changed, msg=msg) + +# this is magic, see lib/ansible/module_common.py +#<> + +main() diff --git a/provisioning/roles/common/tasks/main.yml b/provisioning/roles/common/tasks/main.yml index 02d2419..1835255 100644 --- a/provisioning/roles/common/tasks/main.yml +++ b/provisioning/roles/common/tasks/main.yml @@ -11,18 +11,13 @@ ignore_errors: True when: stage is defined -- name: Update current hostname - command: hostname ${new_host.stdout} - sudo: yes - when: stage is defined and old_host.stdout != new_host.stdout - -- name: Update canonical hostname - copy: content=${new_host.stdout} dest=/etc/hostname +- name: Update hostname + hostname: name=${new_host.stdout} sudo: yes when: stage is defined and old_host.stdout != new_host.stdout - name: Update /etc/hosts for new hostname - command: perl -p -i.bak -e s/${old_host.stdout}/${new_host.stdout}/ig /etc/hosts + replace: dest=/etc/hosts backup=yes regexp='{{ old_host.stdout | replace(".","[.]") }}' replace='{{new_host.stdout}}' sudo: yes when: stage is defined and old_host.stdout != new_host.stdout From 09cf623b0f43e4224fea20823e87b12604d9d1a8 Mon Sep 17 00:00:00 2001 From: Evan Kaufman Date: Thu, 21 Nov 2013 13:17:49 -0600 Subject: [PATCH 4/6] Replace manual passing of stage with use of ansible_fqdn fact --- deployment/lib/provision.rb | 2 +- provisioning/roles/common/tasks/main.yml | 15 ++++----------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/deployment/lib/provision.rb b/deployment/lib/provision.rb index fe955b1..b1f01ca 100644 --- a/deployment/lib/provision.rb +++ b/deployment/lib/provision.rb @@ -10,7 +10,7 @@ upload "./provisioning", "#{tmp}/provisioning", :via => :scp, :recursive => true upload "./bower_components/genesis-wordpress/provisioning", "#{tmp}/bower_components/genesis-wordpress/provisioning", :via => :scp, :recursive => true - sudo "#{tmp}/bin/provision -e stage=#{stage}" + sudo "#{tmp}/bin/provision" rescue puts "\n" diff --git a/provisioning/roles/common/tasks/main.yml b/provisioning/roles/common/tasks/main.yml index eb12283..310fd07 100644 --- a/provisioning/roles/common/tasks/main.yml +++ b/provisioning/roles/common/tasks/main.yml @@ -1,25 +1,18 @@ --- -- name: Determine optimal hostname - command: echo ${stage}.${domain} - register: new_host - ignore_errors: True - when: stage is defined - - name: Determine current hostname command: hostname register: old_host ignore_errors: True - when: stage is defined - name: Update hostname - hostname: name=${new_host.stdout} + hostname: name={{ansible_fqdn}} sudo: yes - when: stage is defined and old_host.stdout != new_host.stdout + when: old_host.stdout != ansible_fqdn - name: Update /etc/hosts for new hostname - replace: dest=/etc/hosts backup=yes regexp='{{ old_host.stdout | replace(".","[.]") }}' replace='{{new_host.stdout}}' + replace: dest=/etc/hosts backup=yes regexp='{{ old_host.stdout | replace(".","[.]") }}' replace='{{ansible_fqdn}}' sudo: yes - when: stage is defined and old_host.stdout != new_host.stdout + when: old_host.stdout != ansible_fqdn - name: Add Node apt-repository apt_repository: repo='ppa:chris-lea/node.js' state=present From cc50fcbda34a541a51a8cc65ca295ede82491f85 Mon Sep 17 00:00:00 2001 From: Evan Kaufman Date: Fri, 22 Nov 2013 21:18:14 -0600 Subject: [PATCH 5/6] Revert "Replace manual passing of stage with use of ansible_fqdn fact" This reverts commit 09cf623b0f43e4224fea20823e87b12604d9d1a8. --- deployment/lib/provision.rb | 2 +- provisioning/roles/common/tasks/main.yml | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/deployment/lib/provision.rb b/deployment/lib/provision.rb index b1f01ca..fe955b1 100644 --- a/deployment/lib/provision.rb +++ b/deployment/lib/provision.rb @@ -10,7 +10,7 @@ upload "./provisioning", "#{tmp}/provisioning", :via => :scp, :recursive => true upload "./bower_components/genesis-wordpress/provisioning", "#{tmp}/bower_components/genesis-wordpress/provisioning", :via => :scp, :recursive => true - sudo "#{tmp}/bin/provision" + sudo "#{tmp}/bin/provision -e stage=#{stage}" rescue puts "\n" diff --git a/provisioning/roles/common/tasks/main.yml b/provisioning/roles/common/tasks/main.yml index 310fd07..eb12283 100644 --- a/provisioning/roles/common/tasks/main.yml +++ b/provisioning/roles/common/tasks/main.yml @@ -1,18 +1,25 @@ --- +- name: Determine optimal hostname + command: echo ${stage}.${domain} + register: new_host + ignore_errors: True + when: stage is defined + - name: Determine current hostname command: hostname register: old_host ignore_errors: True + when: stage is defined - name: Update hostname - hostname: name={{ansible_fqdn}} + hostname: name=${new_host.stdout} sudo: yes - when: old_host.stdout != ansible_fqdn + when: stage is defined and old_host.stdout != new_host.stdout - name: Update /etc/hosts for new hostname - replace: dest=/etc/hosts backup=yes regexp='{{ old_host.stdout | replace(".","[.]") }}' replace='{{ansible_fqdn}}' + replace: dest=/etc/hosts backup=yes regexp='{{ old_host.stdout | replace(".","[.]") }}' replace='{{new_host.stdout}}' sudo: yes - when: old_host.stdout != ansible_fqdn + when: stage is defined and old_host.stdout != new_host.stdout - name: Add Node apt-repository apt_repository: repo='ppa:chris-lea/node.js' state=present From 97d470721de37dec6307f5dfd6338aab158e6d70 Mon Sep 17 00:00:00 2001 From: Evan Kaufman Date: Sat, 23 Nov 2013 12:55:05 -0600 Subject: [PATCH 6/6] Removed custom hostname module (after 1.4 release), misc cleanup of common tasks --- provisioning/roles/common/library/hostname | 319 --------------------- provisioning/roles/common/tasks/main.yml | 6 +- 2 files changed, 3 insertions(+), 322 deletions(-) delete mode 100644 provisioning/roles/common/library/hostname diff --git a/provisioning/roles/common/library/hostname b/provisioning/roles/common/library/hostname deleted file mode 100644 index 9326d0f..0000000 --- a/provisioning/roles/common/library/hostname +++ /dev/null @@ -1,319 +0,0 @@ -#!/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 . - -DOCUMENTATION = ''' ---- -module: hostname -author: Hiroaki Nakamura -version_added: "1.4" -short_description: Manage hostname -requirements: [ hostname ] -description: - - Set system's hostname - - Currently implemented on only Debian, Ubuntu, RedHat and CentOS. -options: - name: - required: true - description: - - Name of the host -''' - -EXAMPLES = ''' -- hostname: name=web01 -''' - -class UnimplementedStrategy(object): - def __init__(self, module): - self.module = module - - 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'] - self.strategy = self.strategy_class(module) - - 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 - - HOSTNAME_CMD = '/bin/hostname' - - 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 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): - try: - f = open(self.HOSTNAME_FILE) - try: - return f.read().strip() - finally: - f.close() - except Exception, err: - 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: - self.module.fail_json(msg="failed to update hostname: %s" % - str(err)) - -class DebianHostname(Hostname): - platform = 'Linux' - distribution = 'Debian' - strategy_class = DebianStrategy - -class UbuntuHostname(Hostname): - platform = 'Linux' - distribution = 'Ubuntu' - strategy_class = DebianStrategy - -# =========================================== - -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: - self.module.fail_json(msg="failed to read hostname: %s" % - str(err)) - - def set_permanent_hostname(self, name): - try: - lines = [] - f = open(self.NETWORK_FILE, 'rb') - try: - for line in f.readlines(): - if line.startswith('HOSTNAME'): - lines.append("HOSTNAME=%s\n" % name) - else: - lines.append(line) - finally: - f.close() - f = open(self.NETWORK_FILE, 'w+') - try: - f.writelines(lines) - finally: - f.close() - except Exception, err: - self.module.fail_json(msg="failed to update hostname: %s" % - str(err)) - -class RedHatHostname(Hostname): - platform = 'Linux' - distribution = 'Red hat enterprise linux server' - strategy_class = RedHatStrategy - -class CentOSHostname(Hostname): - platform = 'Linux' - distribution = 'Centos' - strategy_class = RedHatStrategy - -# =========================================== - -class FedoraStrategy(GenericStrategy): - """ - This is a Fedora family 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 out.strip() - - def set_current_hostname(self, 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 status | awk \'/^ *Static hostname:/{printf("%s", $3)}\'' - 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 out - - def set_permanent_hostname(self, 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 FedoraHostname(Hostname): - platform = 'Linux' - distribution = 'Fedora' - strategy_class = FedoraStrategy - -class OpenSUSEHostname(Hostname): - platform = 'Linux' - distribution = 'Opensuse ' - strategy_class = FedoraStrategy - -class ArchHostname(Hostname): - platform = 'Linux' - distribution = 'Arch' - strategy_class = FedoraStrategy - -# =========================================== - -def main(): - module = AnsibleModule( - argument_spec = dict( - name=dict(required=True, type='str') - ) - ) - - hostname = Hostname(module) - - changed = False - name = module.params['name'] - current_name = hostname.get_current_hostname() - if current_name != name: - hostname.set_current_hostname(name) - changed = True - - permanent_name = hostname.get_permanent_hostname() - if permanent_name != name: - hostname.set_permanent_hostname(name) - changed = True - - module.exit_json(changed=changed, name=name) - -# include magic from lib/ansible/module_common.py -#<> -main() diff --git a/provisioning/roles/common/tasks/main.yml b/provisioning/roles/common/tasks/main.yml index eb12283..3d4812a 100644 --- a/provisioning/roles/common/tasks/main.yml +++ b/provisioning/roles/common/tasks/main.yml @@ -1,6 +1,6 @@ --- - name: Determine optimal hostname - command: echo ${stage}.${domain} + command: echo {{stage}}.{{domain}} register: new_host ignore_errors: True when: stage is defined @@ -12,12 +12,12 @@ when: stage is defined - name: Update hostname - hostname: name=${new_host.stdout} + hostname: name={{new_host.stdout}} sudo: yes when: stage is defined and old_host.stdout != new_host.stdout - name: Update /etc/hosts for new hostname - replace: dest=/etc/hosts backup=yes regexp='{{ old_host.stdout | replace(".","[.]") }}' replace='{{new_host.stdout}}' + replace: dest=/etc/hosts backup=yes regexp={{ old_host.stdout | replace(".","[.]") }} replace={{new_host.stdout}} sudo: yes when: stage is defined and old_host.stdout != new_host.stdout