1
# Copyright 2014-2015 Canonical Limited.
3
# This file is part of charm-helpers.
5
# charm-helpers is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU Lesser General Public License version 3 as
7
# published by the Free Software Foundation.
9
# charm-helpers is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU Lesser General Public License for more details.
14
# You should have received a copy of the GNU Lesser General Public License
15
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
17
# Copyright 2013 Canonical Ltd.
20
# Charm Helpers Developers <juju@lists.ubuntu.com>
21
"""Charm Helpers ansible - declare the state of your machines.
23
This helper enables you to declare your machine state, rather than
24
program it procedurally (and have to test each change to your procedures).
25
Your install hook can be as simple as::
28
import charmhelpers.contrib.ansible
32
charmhelpers.contrib.ansible.install_ansible_support()
33
charmhelpers.contrib.ansible.apply_playbook('playbooks/install.yaml')
36
and won't need to change (nor will its tests) when you change the machine
39
All of your juju config and relation-data are available as template
40
variables within your playbooks and templates. An install playbook looks
49
- name: Add private repositories.
51
src: ../templates/private-repositories.list.jinja2
52
dest: /etc/apt/sources.list.d/private.list
54
- name: Update the cache.
57
- name: Install dependencies.
65
group: name={{ item.name }} gid={{ item.gid }}
67
- { name: 'deploy_user', gid: 1800 }
68
- { name: 'service_user', gid: 1500 }
73
Read more online about `playbooks`_ and standard ansible `modules`_.
75
.. _playbooks: http://www.ansibleworks.com/docs/playbooks.html
76
.. _modules: http://www.ansibleworks.com/docs/modules.html
82
import charmhelpers.contrib.templating.contexts
83
import charmhelpers.core.host
84
import charmhelpers.core.hookenv
85
import charmhelpers.fetch
88
charm_dir = os.environ.get('CHARM_DIR', '')
89
ansible_hosts_path = '/etc/ansible/hosts'
90
# Ansible will automatically include any vars in the following
91
# file in its inventory when run locally.
92
ansible_vars_path = '/etc/ansible/host_vars/localhost'
95
def install_ansible_support(from_ppa=True, ppa_location='ppa:rquillo/ansible'):
96
"""Installs the ansible package.
98
By default it is installed from the `PPA`_ linked from
99
the ansible `website`_ or from a ppa specified by a charm config..
101
.. _PPA: https://launchpad.net/~rquillo/+archive/ansible
102
.. _website: http://docs.ansible.com/intro_installation.html#latest-releases-via-apt-ubuntu
104
If from_ppa is empty, you must ensure that the package is available
105
from a configured repository.
108
charmhelpers.fetch.add_source(ppa_location)
109
charmhelpers.fetch.apt_update(fatal=True)
110
charmhelpers.fetch.apt_install('ansible')
111
with open(ansible_hosts_path, 'w+') as hosts_file:
112
hosts_file.write('localhost ansible_connection=local')
115
def apply_playbook(playbook, tags=None):
117
tags = ",".join(tags)
118
charmhelpers.contrib.templating.contexts.juju_state_to_yaml(
119
ansible_vars_path, namespace_separator='__',
120
allow_hyphens_in_keys=False)
121
# we want ansible's log output to be unbuffered
122
env = os.environ.copy()
123
env['PYTHONUNBUFFERED'] = "1"
131
call.extend(['--tags', '{}'.format(tags)])
132
subprocess.check_call(call, env=env)
135
class AnsibleHooks(charmhelpers.core.hookenv.Hooks):
136
"""Run a playbook with the hook-name as the tag.
138
This helper builds on the standard hookenv.Hooks helper,
139
but additionally runs the playbook with the hook-name specified
140
using --tags (ie. running all the tasks tagged with the hook-name).
144
hooks = AnsibleHooks(playbook_path='playbooks/my_machine_state.yaml')
146
# All the tasks within my_machine_state.yaml tagged with 'install'
147
# will be run automatically after do_custom_work()
152
# For most of your hooks, you won't need to do anything other
153
# than run the tagged tasks for the hook:
154
@hooks.hook('config-changed', 'start', 'stop')
155
def just_use_playbook():
158
# As a convenience, you can avoid the above noop function by specifying
159
# the hooks which are handled by ansible-only and they'll be registered
161
# hooks = AnsibleHooks(
162
# 'playbooks/my_machine_state.yaml',
163
# default_hooks=['config-changed', 'start', 'stop'])
165
if __name__ == "__main__":
166
# execute a hook based on the name the program is called by
167
hooks.execute(sys.argv)
171
def __init__(self, playbook_path, default_hooks=None):
172
"""Register any hooks handled by ansible."""
173
super(AnsibleHooks, self).__init__()
175
self.playbook_path = playbook_path
177
default_hooks = default_hooks or []
179
def noop(*args, **kwargs):
182
for hook in default_hooks:
183
self.register(hook, noop)
185
def execute(self, args):
186
"""Execute the hook followed by the playbook using the hook as tag."""
187
super(AnsibleHooks, self).execute(args)
188
hook_name = os.path.basename(args[0])
189
charmhelpers.contrib.ansible.apply_playbook(
190
self.playbook_path, tags=[hook_name])