~0atman/charms/trusty/wsgi-app/trunk

« back to all changes in this revision

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

  • Committer: Tristram Oaten
  • Date: 2014-12-03 10:44:02 UTC
  • Revision ID: tristram@oaten.name-20141203104402-p3hsihf0t4h8v3rt
init

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2013 Canonical Ltd.
 
2
#
 
3
# Authors:
 
4
#  Charm Helpers Developers <juju@lists.ubuntu.com>
 
5
"""Charm Helpers ansible - declare the state of your machines.
 
6
 
 
7
This helper enables you to declare your machine state, rather than
 
8
program it procedurally (and have to test each change to your procedures).
 
9
Your install hook can be as simple as:
 
10
 
 
11
{{{
 
12
import charmhelpers.contrib.ansible
 
13
 
 
14
hooks = charmhelpers.contrib.ansible.AnsibleHooks(
 
15
    playbook_path="playbook.yaml")
 
16
 
 
17
@hooks.hook('install', 'upgrade-charm')
 
18
def install():
 
19
    charmhelpers.contrib.ansible.install_ansible_support()
 
20
}}}
 
21
 
 
22
and won't need to change (nor will its tests) when you change the machine
 
23
state.
 
24
 
 
25
All of your juju config and relation-data are available as template
 
26
variables within your playbooks and templates.
 
27
 
 
28
An install playbook looks
 
29
something like:
 
30
 
 
31
{{{
 
32
---
 
33
- hosts: localhost
 
34
 
 
35
  tasks:
 
36
 
 
37
    - name: Install dependencies.
 
38
      tags:
 
39
        - install
 
40
        - config-changed
 
41
      apt: pkg={{ item }}
 
42
      with_items:
 
43
        - python-mimeparse
 
44
        - python-webob
 
45
        - sunburnt
 
46
 
 
47
    - name: Setup groups.
 
48
        - install
 
49
        - config-changed
 
50
      group: name={{ item.name }} gid={{ item.gid }}
 
51
      with_items:
 
52
        - { name: 'deploy_user', gid: 1800 }
 
53
        - { name: 'service_user', gid: 1500 }
 
54
 
 
55
  ...
 
56
}}}
 
57
 
 
58
 
 
59
Alternatively, you can apply individual playbooks with:
 
60
{{{
 
61
charmhelpers.contrib.ansible.apply_playbook('playbooks/install.yaml')
 
62
}}}
 
63
 
 
64
Read more online about playbooks[1] and standard ansible modules[2].
 
65
 
 
66
[1] http://www.ansibleworks.com/docs/playbooks.html
 
67
[2] http://www.ansibleworks.com/docs/modules.html
 
68
"""
 
69
import os
 
70
import subprocess
 
71
import warnings
 
72
 
 
73
import charmhelpers.contrib.templating.contexts
 
74
import charmhelpers.core.host
 
75
import charmhelpers.core.hookenv
 
76
import charmhelpers.fetch
 
77
 
 
78
 
 
79
charm_dir = os.environ.get('CHARM_DIR', '')
 
80
ansible_hosts_path = '/etc/ansible/hosts'
 
81
# Ansible will automatically include any vars in the following
 
82
# file in its inventory when run locally.
 
83
ansible_vars_path = '/etc/ansible/host_vars/localhost'
 
84
available_tags = set([])
 
85
 
 
86
 
 
87
def install_ansible_support(from_ppa=True, ppa_location='ppa:rquillo/ansible'):
 
88
    """Installs the ansible package.
 
89
 
 
90
    By default it is installed from the PPA [1] linked from
 
91
    the ansible website [2] or from a ppa specified by a charm config.
 
92
 
 
93
    [1] https://launchpad.net/~rquillo/+archive/ansible
 
94
    [2] http://www.ansibleworks.com/docs/gettingstarted.html#ubuntu-and-debian
 
95
 
 
96
    If from_ppa is empty, you must ensure that the package is available
 
97
    from a configured repository.
 
98
    """
 
99
    if from_ppa:
 
100
        charmhelpers.fetch.add_source(ppa_location)
 
101
        charmhelpers.fetch.apt_update(fatal=True)
 
102
    charmhelpers.fetch.apt_install('ansible')
 
103
    with open(ansible_hosts_path, 'w+') as hosts_file:
 
104
        hosts_file.write('localhost ansible_connection=local')
 
105
 
 
106
 
 
107
def apply_playbook(playbook, tags=None):
 
108
    tags = tags or []
 
109
    tags = ",".join(tags)
 
110
    charmhelpers.contrib.templating.contexts.juju_state_to_yaml(
 
111
        ansible_vars_path, namespace_separator='__',
 
112
        allow_hyphens_in_keys=False)
 
113
    call = [
 
114
        'ansible-playbook',
 
115
        '-c',
 
116
        'local',
 
117
        playbook,
 
118
    ]
 
119
    if tags:
 
120
        call.extend(['--tags', '{}'.format(tags)])
 
121
    subprocess.check_call(call)
 
122
 
 
123
 
 
124
def get_tags_for_playbook(playbook_path):
 
125
    """Return all tags within a playbook.
 
126
 
 
127
    The charmhelpers lib should not depend on ansible, hence the
 
128
    inline imports here.
 
129
 
 
130
    Discussion whether --list-tags should be a feature of ansible at
 
131
    http://goo.gl/6gXd50
 
132
    """
 
133
    import ansible.utils
 
134
    import ansible.callbacks
 
135
    import ansible.playbook
 
136
    stats = ansible.callbacks.AggregateStats()
 
137
    callbacks = ansible.callbacks.PlaybookRunnerCallbacks(stats)
 
138
    runner_callbacks = ansible.callbacks.PlaybookRunnerCallbacks(stats)
 
139
    playbook = ansible.playbook.PlayBook(playbook=playbook_path,
 
140
                                         callbacks=callbacks,
 
141
                                         runner_callbacks=runner_callbacks,
 
142
                                         stats=stats)
 
143
    myplay = ansible.playbook.Play(playbook, ds=playbook.playbook[0],
 
144
                                   basedir=os.path.dirname(playbook_path))
 
145
 
 
146
    _, playbook_tags = myplay.compare_tags([])
 
147
    playbook_tags.remove('all')
 
148
    return playbook_tags
 
149
 
 
150
 
 
151
class AnsibleHooks(charmhelpers.core.hookenv.Hooks):
 
152
    """Run a playbook with the hook-name as the tag.
 
153
 
 
154
    This helper builds on the standard hookenv.Hooks helper,
 
155
    but additionally runs the playbook with the hook-name specified
 
156
    using --tags (ie. running all the tasks tagged with the hook-name).
 
157
 
 
158
    Example:
 
159
        hooks = AnsibleHooks(playbook_path='playbooks/my_machine_state.yaml')
 
160
 
 
161
        # All the tasks within my_machine_state.yaml tagged with 'install'
 
162
        # will be run automatically after do_custom_work()
 
163
        @hooks.hook()
 
164
        def install():
 
165
            do_custom_work()
 
166
 
 
167
        # For most of your hooks, you won't need to do anything other
 
168
        # than run the tagged tasks for the hook:
 
169
        @hooks.hook('config-changed', 'start', 'stop')
 
170
        def just_use_playbook():
 
171
            pass
 
172
 
 
173
        # As a convenience, you can avoid the above noop function by specifying
 
174
        # the hooks which are handled by ansible-only and they'll be registered
 
175
        # for you:
 
176
        # hooks = AnsibleHooks(
 
177
        #     'playbooks/my_machine_state.yaml',
 
178
        #     default_hooks=['config-changed', 'start', 'stop'])
 
179
 
 
180
        if __name__ == "__main__":
 
181
            # execute a hook based on the name the program is called by
 
182
            hooks.execute(sys.argv)
 
183
    """
 
184
 
 
185
    def __init__(self, playbook_path, default_hooks=None):
 
186
        """Register any hooks handled by ansible.
 
187
 
 
188
        default_hooks is now deprecated, as we use ansible to
 
189
        determine the supported hooks from the playbook.
 
190
        """
 
191
        super(AnsibleHooks, self).__init__()
 
192
 
 
193
        self.playbook_path = playbook_path
 
194
 
 
195
        # The hooks decorator is created at module load time, which on the
 
196
        # first run, will be before ansible is itself installed.
 
197
        try:
 
198
            available_tags.update(get_tags_for_playbook(playbook_path))
 
199
        except ImportError:
 
200
            available_tags.add('install')
 
201
 
 
202
        if default_hooks is not None:
 
203
            warnings.warn(
 
204
                "The use of default_hooks is deprecated. Ansible is now "
 
205
                "used to query your playbook for available tags.",
 
206
                DeprecationWarning)
 
207
        noop = lambda *args, **kwargs: None
 
208
        for hook in available_tags:
 
209
            self.register(hook, noop)
 
210
 
 
211
    def execute(self, args):
 
212
        """Execute the hook followed by the playbook using the hook as tag."""
 
213
        super(AnsibleHooks, self).execute(args)
 
214
        hook_name = os.path.basename(args[0])
 
215
 
 
216
        if hook_name in available_tags:
 
217
            charmhelpers.contrib.ansible.apply_playbook(
 
218
                self.playbook_path, tags=[hook_name])