~canonical-ci-engineering/charms/trusty/core-image-publisher/trunk

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/contrib/ansible/__init__.py

  • Committer: Celso Providelo
  • Date: 2015-03-25 04:13:43 UTC
  • Revision ID: celso.providelo@canonical.com-20150325041343-jw05jaz6jscs3c8f
fork of core-image-watcher

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2014-2015 Canonical Limited.
 
2
#
 
3
# This file is part of charm-helpers.
 
4
#
 
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.
 
8
#
 
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.
 
13
#
 
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/>.
 
16
 
 
17
# Copyright 2013 Canonical Ltd.
 
18
#
 
19
# Authors:
 
20
#  Charm Helpers Developers <juju@lists.ubuntu.com>
 
21
"""Charm Helpers ansible - declare the state of your machines.
 
22
 
 
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::
 
26
 
 
27
    {{{
 
28
    import charmhelpers.contrib.ansible
 
29
 
 
30
 
 
31
    def install():
 
32
        charmhelpers.contrib.ansible.install_ansible_support()
 
33
        charmhelpers.contrib.ansible.apply_playbook('playbooks/install.yaml')
 
34
    }}}
 
35
 
 
36
and won't need to change (nor will its tests) when you change the machine
 
37
state.
 
38
 
 
39
All of your juju config and relation-data are available as template
 
40
variables within your playbooks and templates. An install playbook looks
 
41
something like::
 
42
 
 
43
    {{{
 
44
    ---
 
45
    - hosts: localhost
 
46
      user: root
 
47
 
 
48
      tasks:
 
49
        - name: Add private repositories.
 
50
          template:
 
51
            src: ../templates/private-repositories.list.jinja2
 
52
            dest: /etc/apt/sources.list.d/private.list
 
53
 
 
54
        - name: Update the cache.
 
55
          apt: update_cache=yes
 
56
 
 
57
        - name: Install dependencies.
 
58
          apt: pkg={{ item }}
 
59
          with_items:
 
60
            - python-mimeparse
 
61
            - python-webob
 
62
            - sunburnt
 
63
 
 
64
        - name: Setup groups.
 
65
          group: name={{ item.name }} gid={{ item.gid }}
 
66
          with_items:
 
67
            - { name: 'deploy_user', gid: 1800 }
 
68
            - { name: 'service_user', gid: 1500 }
 
69
 
 
70
      ...
 
71
    }}}
 
72
 
 
73
Read more online about `playbooks`_ and standard ansible `modules`_.
 
74
 
 
75
.. _playbooks: http://www.ansibleworks.com/docs/playbooks.html
 
76
.. _modules: http://www.ansibleworks.com/docs/modules.html
 
77
 
 
78
"""
 
79
import os
 
80
import subprocess
 
81
 
 
82
import charmhelpers.contrib.templating.contexts
 
83
import charmhelpers.core.host
 
84
import charmhelpers.core.hookenv
 
85
import charmhelpers.fetch
 
86
 
 
87
 
 
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'
 
93
 
 
94
 
 
95
def install_ansible_support(from_ppa=True, ppa_location='ppa:rquillo/ansible'):
 
96
    """Installs the ansible package.
 
97
 
 
98
    By default it is installed from the `PPA`_ linked from
 
99
    the ansible `website`_ or from a ppa specified by a charm config..
 
100
 
 
101
    .. _PPA: https://launchpad.net/~rquillo/+archive/ansible
 
102
    .. _website: http://docs.ansible.com/intro_installation.html#latest-releases-via-apt-ubuntu
 
103
 
 
104
    If from_ppa is empty, you must ensure that the package is available
 
105
    from a configured repository.
 
106
    """
 
107
    if from_ppa:
 
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')
 
113
 
 
114
 
 
115
def apply_playbook(playbook, tags=None):
 
116
    tags = tags or []
 
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"
 
124
    call = [
 
125
        'ansible-playbook',
 
126
        '-c',
 
127
        'local',
 
128
        playbook,
 
129
    ]
 
130
    if tags:
 
131
        call.extend(['--tags', '{}'.format(tags)])
 
132
    subprocess.check_call(call, env=env)
 
133
 
 
134
 
 
135
class AnsibleHooks(charmhelpers.core.hookenv.Hooks):
 
136
    """Run a playbook with the hook-name as the tag.
 
137
 
 
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).
 
141
 
 
142
    Example::
 
143
 
 
144
        hooks = AnsibleHooks(playbook_path='playbooks/my_machine_state.yaml')
 
145
 
 
146
        # All the tasks within my_machine_state.yaml tagged with 'install'
 
147
        # will be run automatically after do_custom_work()
 
148
        @hooks.hook()
 
149
        def install():
 
150
            do_custom_work()
 
151
 
 
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():
 
156
            pass
 
157
 
 
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
 
160
        # for you:
 
161
        # hooks = AnsibleHooks(
 
162
        #     'playbooks/my_machine_state.yaml',
 
163
        #     default_hooks=['config-changed', 'start', 'stop'])
 
164
 
 
165
        if __name__ == "__main__":
 
166
            # execute a hook based on the name the program is called by
 
167
            hooks.execute(sys.argv)
 
168
 
 
169
    """
 
170
 
 
171
    def __init__(self, playbook_path, default_hooks=None):
 
172
        """Register any hooks handled by ansible."""
 
173
        super(AnsibleHooks, self).__init__()
 
174
 
 
175
        self.playbook_path = playbook_path
 
176
 
 
177
        default_hooks = default_hooks or []
 
178
 
 
179
        def noop(*args, **kwargs):
 
180
            pass
 
181
 
 
182
        for hook in default_hooks:
 
183
            self.register(hook, noop)
 
184
 
 
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])