Add the 'debops.machine' Ansible role

parent 0d27e39a
......@@ -663,6 +663,13 @@ stages:
# --- m --- [[[2
'machine role':
<<: *test_role_no_deps
variables:
JANE_TEST_PLAY: '${DEBOPS_PLAYBOOKS}/service/machine.yml'
JANE_DIFF_PATTERN: '.*/debops.machine/.*'
JANE_LOG_PATTERN: '\[debops\.machine\]'
'mailman role':
<<: *test_role_3rd_deps
variables:
......
......@@ -37,6 +37,9 @@ Added
- :ref:`debops.locales`: configure localization and internationalization on
a given host or set of hosts.
- :ref:`debops.machine`: manage the :file:`/etc/machine-info` file,
the :file:`/etc/issue` file and a dynamic MOTD.
- You can now :ref:`use Vagrant <quick_start__vagrant>` to create an Ansible
Controller based on Debian Stretch and use it to manage itself or other hosts
over the network.
......
---
- name: Manage local machine information
hosts: [ 'debops_all_hosts', 'debops_service_machine' ]
become: True
environment: '{{ inventory__environment | d({})
| combine(inventory__group_environment | d({}))
| combine(inventory__host_environment | d({})) }}'
roles:
- role: debops.machine
tags: [ 'role::machine' ]
debops.machine - Manage local machine information and MOTD
Copyright (C) 2018 Maciej Delmanowski <drybjed@gmail.com>
Copyright (C) 2018 DebOps https://debops.org/
This Ansible role is part of DebOps.
DebOps is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3, as
published by the Free Software Foundation.
DebOps 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 DebOps. If not, see https://www.gnu.org/licenses/.
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# debops.machine default variables
# ================================
# .. contents:: Sections
# :local:
# General configuration [[[
# -------------------------
# .. envvar:: machine__enabled [[[
#
# Enable or disable support for managing static machine information and MOTD.
machine__enabled: True
# ]]]
# .. envvar:: machine__packages [[[
#
# List of APT packages to install with the ``debops.machine`` role enabled.
machine__packages: []
# ]]]
# ]]]
# Machine information [[[
# -----------------------
# These variables manage contents of the :file:`/etc/machine-info`
# configuration file. See :manpage:`machine-info(5)` and
# :manpage:`hostnamectl(1)` for more details.
# .. envvar:: machine__organization [[[
#
# Human-readable name of the organization that's responsible for this machine.
machine__organization: '{{ ansible_domain.split(".")[0] | capitalize }}'
# ]]]
# .. envvar:: machine__contact [[[
#
# Human-readable contact information for this machine - e-mail, phone number or
# an URL to a web page about a person or team responsible for this host.
machine__contact: ''
# ]]]
# .. envvar:: machine__pretty_hostname [[[
#
# Human-readable hostname for this machine.
machine__pretty_hostname: ''
# ]]]
# .. envvar:: machine__icon_name [[[
#
# Name of the icon to use for this machine.
machine__icon_name: ''
# ]]]
# .. envvar:: machine__chassis [[[
#
# Specify the machine type to override the autodetected value. Currently
# recognized chassis types: 'desktop', 'laptop', 'server', 'tablet', 'handset',
# 'watch', 'vm', 'container'.
machine__chassis: ''
# ]]]
# .. envvar:: machine__deployment [[[
#
# Define the system deployment environment. Suggested values:
# 'development', 'integration', 'staging', 'production'.
machine__deployment: 'production'
# ]]]
# .. envvar:: machine__location [[[
#
# Describe the physical location where a given system can be found, for example
# a city, or a rack in which the machine is located.
machine__location: ''
# ]]]
# ]]]
# Configuration of the :file:`/etc/motd` file [[[
# -----------------------------------------------
# .. envvar:: machine__motd [[[
#
# String or YAML text block with the welcome message stored in the
# :file:`/etc/motd` configuration file.
machine__motd: ''
# ]]]
# .. envvar:: machine__etc_motd_state [[[
#
# Specofy the state of the :file:`/etc/motd` file, either ``present`` (the file
# exists) or ``absent`` (the file will be removed).
machine__etc_motd_state: '{{ "present" if machine__motd|d() else "absent" }}'
# ]]]
# .. envvar:: machine__motd_update_dir [[[
#
# Directory which contains scripts that generate the dynamic MOTD.
machine__motd_update_dir: '/etc/update-motd.d'
# ]]]
# ]]]
# Configuration of the :file:`/etc/issue` file [[[
# ------------------------------------------------
# .. envvar:: machine__etc_issue_state [[[
#
# This variable controls if the role should manage the :file:`/etc/issue`
# configuration file. If ``present``, the role will divert the distribution file
# and generate a custom one; if ``absent``, the role will revert the diverted
# version and will not modify it.
machine__etc_issue_state: 'present'
# ]]]
# .. envvar:: machine__etc_issue_template [[[
#
# Path to the Jinja2 template used to generate the :file:`/etc/issue`
# configuration file, relative to the :file:`templates/` directory of this role.
machine__etc_issue_template: 'etc/issue.j2'
# ]]]
# ]]]
# Dynamic Message Of The Day [[[
# ------------------------------
# These variables control the shell scripts that generate the dynamic MOTD.
# See :ref:`machine__ref_motd_scripts` for more details.
# .. envvar:: machine__motd_default_scripts [[[
#
# The list of the default MOTD scripts managed by the role.
machine__motd_default_scripts:
# This file comes with the 'base-files' APT package on Debian
- name: 'uname'
filename: '10-uname'
divert: '{{ False
if (ansible_distribution_release in [ "wheezy", "jessie" ])
else True }}'
content: |
#!/bin/sh
uname -snrvm
state: '{{ "present"
if (ansible_distribution_release in [ "wheezy", "jessie" ])
else "init" }}'
- name: 'ansible'
weight: 50
src: 'etc/update-motd.d/ansible'
state: 'present'
- name: 'tail'
weight: 90
content: |
#!/bin/sh
if [ -f /etc/motd.tail ] ; then
cat /etc/motd.tail
fi
state: 'present'
- name: 'fortune'
weight: 95
src: 'etc/update-motd.d/fortune'
state: 'init'
# ]]]
# .. envvar:: machine__motd_scripts [[[
#
# The list of the MOTD scripts which should be present on all hosts in the
# Ansible inventory.
machine__motd_scripts: []
# ]]]
# .. envvar:: machine__motd_group_scripts [[[
#
# The list of the MOTD scripts which should be present on hosts in a specific
# Ansible inventory group.
machine__motd_group_scripts: []
# ]]]
# .. envvar:: machine__motd_host_scripts [[[
#
# The list of the MOTD scripts which should be present on specific hosts in the
# Ansible inventory.
machine__motd_host_scripts: []
# ]]]
# .. envvar:: machine__motd_combined_packages [[[
#
# The list which combines configuration of the MOTD scripts which is passed to
# the Ansible tasks.
machine__motd_combined_scripts: '{{ machine__motd_default_scripts
+ machine__motd_scripts
+ machine__motd_group_scripts
+ machine__motd_host_scripts }}'
# ]]]
# ]]]
#!/bin/sh
BLACK='\033[49;30m'
BLACKB='\033[49;90m'
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[94;49m'
MAGENTA='\033[0;35m'
CYAN='\033[36;49m'
WHITE='\033[0;37m'
BOLD='\033[1m'
RESET='\033[0m'
if [ -d "/etc/ansible/facts.d" ] ; then
printf "${BLACKB}%70s${RESET}\n" | tr ' ' -
printf " ${CYAN}This system is managed by ${BOLD}Ansible${RESET}${CYAN}, manual changes will be lost${RESET}\n"
printf "${BLACKB}%70s${RESET}\n" | tr ' ' -
fi
#!/bin/sh
. /etc/default/locale
export LANG
export PATH="/usr/local/games:/usr/games:$PATH"
if [ -x /usr/games/fortune ] ; then
/usr/games/fortune -s
fi
---
dependencies:
- role: debops.ansible_plugins
galaxy_info:
author: 'Maciej Delmanowski'
description: 'Manage local machine information and MOTD'
company: 'DebOps'
license: 'GPL-3.0'
min_ansible_version: '2.4.0'
platforms:
- name: Ubuntu
versions:
- precise
- trusty
- xenial
- name: Debian
versions:
- wheezy
- jessie
- stretch
categories:
- system
- motd
- issue
- login
---
- name: Install requested packages
package:
name: '{{ item }}'
state: 'present'
with_flattened: '{{ machine__packages }}'
when: machine__enabled|bool
- name: Generate machine-info config file
template:
src: 'etc/machine-info.j2'
dest: '/etc/machine-info'
owner: 'root'
group: 'root'
mode: '0644'
when: machine__enabled|bool
- name: Divert original /etc/issue file
command: dpkg-divert --quiet --local --divert /etc/issue.dpkg-divert --rename /etc/issue
args:
creates: '/etc/issue.dpkg-divert'
when: machine__enabled|bool and machine__etc_issue_state|d('present') != 'absent'
- name: Configure /etc/issue file
template:
src: '{{ machine__etc_issue_template }}'
dest: '/etc/issue'
owner: 'root'
group: 'root'
mode: '0644'
when: machine__enabled|bool and machine__etc_issue_state|d('present') != 'absent'
- name: Revert original /etc/issue file
shell: rm -f /etc/issue ; dpkg-divert --quiet --local --rename --remove /etc/issue
args:
warn: False
removes: '/etc/issue.dpkg-divert'
when: machine__enabled|bool and machine__etc_issue_state|d('present') == 'absent'
- name: Remove static /etc/motd file if requested
file:
path: '/etc/motd'
state: 'absent'
when: machine__enabled|bool and machine__etc_motd_state == 'absent'
- name: Generate static /etc/motd file
copy:
content: "{{ machine__motd }}"
dest: '/etc/motd'
owner: 'root'
group: 'root'
mode: '0644'
when: machine__enabled|bool and machine__etc_motd_state|d('present') != 'absent' and
machine__motd|d()
- name: Ensure that required directories exist
file:
path: '{{ machine__motd_update_dir }}'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
when: machine__enabled|bool
- name: Fix issue with dynamic MOTD on older OS releases
template:
src: 'etc/tmpfiles.d/motd-dynamic.conf.j2'
dest: '/etc/tmpfiles.d/motd-dynamic.conf'
owner: 'root'
group: 'root'
mode: '0644'
register: machine__register_tmpfiles
when: machine__enabled|bool and ansible_distribution_release in [ 'wheezy', 'jessie' ]
- name: Create /run/motd.dynamic symlink
command: systemd-tmpfiles --create
when: machine__enabled|bool and ansible_service_mgr == 'systemd' and
machine__register_tmpfiles|changed
- name: Check current MOTD diversions
environment:
LC_ALL: 'C'
shell: dpkg-divert --list '/etc/update-motd.d/*.disabled' | awk '{print $NF}'
register: machine__register_motd_diversions
check_mode: False
changed_when: False
- name: Divert packaged MOTD scripts
command: dpkg-divert --quiet --local --divert /etc/update-motd.d/{{ item.filename | d(("%02d" | format(item.weight|int))|string + "-" + item.name) }}.disabled --rename /etc/update-motd.d/{{ item.filename | d(("%02d" | format(item.weight|int))|string + "-" + item.name) }}
args:
creates: '{{ "/etc/update-motd.d/" + (item.filename | d(("%02d" | format(item.weight|int))|string + "-" + item.name)) + ".disabled" }}'
with_flattened: '{{ machine__motd_combined_scripts | parse_kv_items }}'
when: (machine__enabled|bool and
(item.filename is defined or item.name is defined) and
(item.state|d('present') not in [ 'init', 'absent', 'ignore', 'revert' ]) and
((item.divert|d())|bool and
'/etc/update-motd.d/' + (item.filename | d(item.weight|string + "-" + item.name)) + '.disabled' not in machine__register_motd_diversions.stdout_lines))
- name: Remove dynamic MOTD scripts
file:
path: '{{ machine__motd_update_dir + "/" + (item.filename | d(("%02d" | format(item.weight|int))|string + "-" + item.name)) }}'
state: 'absent'
with_flattened: '{{ machine__motd_combined_scripts | parse_kv_items }}'
register: machine__register_motd_scripts_removed
when: (machine__enabled|bool and
(item.filename is defined or item.name is defined) and
item.state|d('present') == 'absent' and not (item.divert|d()|bool))
- name: Install dynamic MOTD scripts
copy:
src: '{{ item.src | d(omit) }}'
content: '{{ item.content | d(omit) }}'
dest: '{{ machine__motd_update_dir + "/" + (item.filename | d(("%02d" | format(item.weight|int))|string + "-" + item.name)) }}'
owner: 'root'
group: 'root'
mode: '0755'
with_flattened: '{{ machine__motd_combined_scripts | parse_kv_items }}'
register: machine__register_motd_scripts_created
when: (machine__enabled|bool and
(item.filename is defined or item.name is defined) and
(item.src is defined or item.content is defined) and
item.state|d('present') not in [ 'init', 'absent', 'ignore', 'divert', 'revert' ])
- name: Remove unknown MOTD scripts
shell: find /etc/update-motd.d -maxdepth 1 -type f
-name '*-{{ item.item.name }}'
! -name '{{ ("%02d" | format((item.item.weight | d("0"))|int))|string + "-" + item.item.name }}' -exec rm -vf {} +
with_items:
- '{{ machine__register_motd_scripts_removed.results }}'
- '{{ machine__register_motd_scripts_created.results }}'
when: (item.item.name|d() and not (item.item.divert|d())|bool and
item.item.filename is undefined and item.changed|bool)
- name: Revert packaged MOTD scripts
shell: rm -f /etc/update-motd.d/{{ item.filename | d(("%02d" | format(item.weight|int))|string + "-" + item.name) }} ; dpkg-divert --quiet --local --rename --remove /etc/update-motd.d/{{ item.filename | d(("%02d" | format(item.weight|int))|string + "-" + item.name) }}
args:
warn: False
removes: '{{ "/etc/update-motd.d/" + (item.filename | d(("%02d" | format(item.weight|int))|string + "-" + item.name)) + ".disabled" }}'
with_flattened: '{{ machine__motd_combined_scripts | parse_kv_items }}'
when: ((not machine__enabled|bool and (item.divert|d())|bool) or
((item.filename is defined or item.name is defined) and
item.state|d('present') in [ 'absent', 'revert' ] and
(item.divert|d())|bool and
'/etc/update-motd.d/' + (item.filename | d(("%02d" | format(item.weight|int))|string + "-" + item.name)) + '.disabled' in machine__register_motd_diversions.stdout_lines))
- name: Make sure that Ansible local facts directory exists
file:
path: '/etc/ansible/facts.d'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
- name: Save machine local facts
template:
src: 'etc/ansible/facts.d/machine.fact.j2'
dest: '/etc/ansible/facts.d/machine.fact'
owner: 'root'
group: 'root'
mode: '0755'
register: machine__register_facts
- name: Update Ansible facts if they were modified
action: setup
when: machine__register_facts|changed
#!/usr/bin/env python
# {{ ansible_managed }}
from __future__ import print_function
from json import loads, dumps
from sys import exit
import os
output = loads('''{{ {'configured': True,
'enabled': machine__enabled|bool
} | to_nice_json }}''')
machine_info = '/etc/machine-info'
if os.path.exists(machine_info) and os.path.isfile(machine_info):
with open(machine_info, "r") as f:
for line in f:
if not line.startswith('#') and '=' in line:
line = line.strip().split('=')
output.update({str(line[0]).lower(): str(line[1]).strip('"')})
print(dumps(output, sort_keys=True, indent=4))
 /\\ \n.\O
 / \\ {{ machine__organization }}
 DebOps
 \\ / \l / \U
 \\/ \s \r
# {{ ansible_managed }}
{% if machine__organization|d() %}
ORGANIZATION="{{ machine__organization }}"
{% endif %}
{% if machine__contact|d() %}
CONTACT="{{ machine__contact }}"
{% endif %}
{% if machine__pretty_hostname|d() %}
PRETTY_HOSTNAME="{{ machine__pretty_hostname }}"
{% endif %}
{% if machine__icon_name|d() %}
ICON_NAME="{{ machine__icon_name }}"
{% endif %}
{% if machine__chassis|d() %}
CHASSIS="{{ machine__chassis }}"
{% endif %}
{% if machine__deployment|d() %}
DEPLOYMENT="{{ machine__deployment }}"
{% endif %}
{% if machine__location|d() %}
LOCATION="{{ machine__location }}"
{% endif %}
# {{ ansible_managed }}
# Configuration installed by debops.machine Ansible role
#
# Create a symlink to fix issues with dynamic MOTD on older OS releases.
# More details: https://unix.stackexchange.com/a/246770
#Type Path Mode UID GID Age Argument
L /run/motd.dynamic - - - - /run/motd
......@@ -238,6 +238,7 @@ System configuration
- :ref:`debops.ferm`
- :ref:`debops.locales`
- :ref:`debops.logrotate`
- :ref:`debops.machine`
- :ref:`debops.nsswitch`
- :ref:`debops.ntp`
- :ref:`debops.resources`
......
Default variable details
========================
Some of the ``debops.machine`` default variables have more extensive
configuration than simple strings or lists, here you can find documentation and
examples for them.
.. contents::
:local:
:depth: 1
.. _machine__ref_motd_scripts:
machine__motd_scripts
---------------------
This variable is a list which can be used to manage scripts in the
:file:`/etc/update-motd.d/` directory, used to generate the dynamic Message Of
The Day.
Each list entry is a YAML dictionary with specific parameters:
``name``
Required. Name of a given entry, used as a handle to merge multiple entries
together. It's also used in an autogenerated filename of the script.
``weight``
Optional. Modify the base "weight" of a given script to reorder its output in
the resulting dynamic MOTD. The base weight used by the role is ``0``, you
can use positive or negative numbers to reorder the scripts. The filename
format uses 2 digits to define the weight, therefore you should use small
range of weights, otherwise weird filenames resulting in unwanted order might
be generated.
``filename``
Optional. Define a static filename of the script. This filename won't be
affected by the ``name`` and ``weight`` parameters. It's usually used for
scripts provided by Debian packages to aid in :command:`dpkg`
diversion/reversion.
``src``
Optional, conflicts with ``content``. Path to a script which should be
installed in the :file:`/etc/update-motd.d/` directory with a given name. By
default the path is relative to the :file:`files/` directory of the
``debops.machine`` Ansible role.
The script output will be added to the dynamic MOTD. Remember that the script
is executed with ``root`` privileges!
``content``
Optional, conflicts with ``src``. YAML text block which contains a script to
be installed in the :file:`/etc/update-motd.d/` directory with a given name.
The script can contain Jinja templating which will be evaluated at Ansible
execution time.
The script output will be added to the dynamic MOTD. Remember that the script
is executed with ``root`` privileges!
``divert``
Optional, boolean. If ``True``, the script managed by this entry will be
automatically diverted/reverted as necessary. This is useful to move the
files included in the APT packages aside, so that the package manager can
still update the packages without issues.
The diverted scripts will have a ``.disabled`` extension and their output
will not be included in the dynamic MOTD.
``state``
Optional. Define the state of a particular script. If multiple list entries
define a script state, the last one wins. Recognized states:
- not specified or ``present``: the script will be installed in the
:file:`/etc/update-motd.d/` directory. If an entry has a ``divert: True``
parameter, existing script will be diverted.
- ``absent``: the specified script will be removed. Diverted scripts will be
reverted to their previous location, but otherwise will not be removed.
- ``init``: the configuration of a given entry will be "initiated", but
otherwise the entry will not change anything on the host. This can be used
to prepare an entry and activate it later conditionally.
- ``ignore``: the given configuration entry will be ignored by the role. This
can be used to conditionally activate or skip entries.
- ``divert``: if a given entry has the ``divert: True`` parameter, the
specified script will be diverted and effectively disabled. The role will
not generate a replacement script. This state can be used to disable
scripts installed by APT packages without providing replacements.
- ``revert``: if a given entry has the ``divert: True`` parameter, the
specified script will be reverted to its original state.
Examples
~~~~~~~~
Include a random fortune in the dynamic MOTD using the :command:`fortune`
output, if it's installed:
.. code-block:: yaml
machine__motd_scripts:
- name: 'fortune'
weight: 95
content: |
#!/bin/sh
. /etc/default/locale
export LANG
export PATH="/usr/local/games:/usr/games:$PATH"
if [ -x /usr/games/fortune ] ; then
/usr/games/fortune -s
fi
state: 'present'
Include a random fortune in the dynamic MOTD using a script provided by the
role:
.. code-block:: yaml
machine__motd_scripts:
- name: 'fortune'
weight: 95
src: 'etc/update-motd.d/fortune'
Getting started
===============
.. contents::
:local:
Default configuration
---------------------
By default, the role will remove an existing :file:`/etc/motd` file to move the
original Debian/Ubuntu MOTD out of the way. This can be controlled using the
:envvar:`machine__etc_motd_state` variable. The original version of the file is
maintained by the ``base-files`` package and can be found in the
:file:`/usr/share/base-files/motd` file.
You can use the :file:`/etc/motd.tail` file to include manual text in the
dynamic MOTD. Alternatively, you can put a custom script in the
:file:`/etc/update-motd.d/` directory.
You can use the :ref:`debops.resources` Ansible role to install multiple custom
scripts in the :file:`/etc/update-motd.d/` directory at once by copying them
from the :file:`resources/` subdirectory on the Ansible Controller.
Ansible local facts
-------------------
The ``debops.machine`` role provides a set of Ansible local facts available in
the ``ansible_local.machine.*`` hierarchy. They will contain contents of the
:file:`/etc/machine-info` variables, so that they could be used by other
Ansible roles when configured.
Example inventory
-----------------