~openstack-ubuntu-testing/charms/precise/keystone/trunk

« back to all changes in this revision

Viewing changes to hooks/lib/unison.py

  • Committer: james.page at ubuntu
  • Date: 2014-04-29 13:05:20 UTC
  • mfrom: (12.23.23 keystone)
  • Revision ID: james.page@ubuntu.com-20140429130520-n358qfaw1vu0q1r6
Update

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
2
 
#
3
 
# Easy file synchronization among peer units using ssh + unison.
4
 
#
5
 
# From *both* peer relation -joined and -changed, add a call to
6
 
# ssh_authorized_peers() describing the peer relation and the desired
7
 
# user + group.  After all peer relations have settled, all hosts should
8
 
# be able to connect to on another via key auth'd ssh as the specified user.
9
 
#
10
 
# Other hooks are then free to synchronize files and directories using
11
 
# sync_to_peers().
12
 
#
13
 
# For a peer relation named 'cluster', for example:
14
 
#
15
 
# cluster-relation-joined:
16
 
# ...
17
 
# ssh_authorized_peers(peer_interface='cluster',
18
 
#                      user='juju_ssh', group='juju_ssh',
19
 
#                      ensure_user=True)
20
 
# ...
21
 
#
22
 
# cluster-relation-changed:
23
 
# ...
24
 
# ssh_authorized_peers(peer_interface='cluster',
25
 
#                      user='juju_ssh', group='juju_ssh',
26
 
#                      ensure_user=True)
27
 
# ...
28
 
#
29
 
# Hooks are now free to sync files as easily as:
30
 
#
31
 
# files = ['/etc/fstab', '/etc/apt.conf.d/']
32
 
# sync_to_peers(peer_interface='cluster',
33
 
#                user='juju_ssh, paths=[files])
34
 
#
35
 
# It is assumed the charm itself has setup permissions on each unit
36
 
# such that 'juju_ssh' has read + write permissions.  Also assumed
37
 
# that the calling charm takes care of leader delegation.
38
 
#
39
 
# TODO: Currently depends on the utils.py shipped with the keystone charm.
40
 
#       Either copy required functionality to this library or depend on
41
 
#       something more generic.
42
 
 
43
 
import os
44
 
import sys
45
 
import lib.utils as utils
46
 
import subprocess
47
 
import grp
48
 
import pwd
49
 
 
50
 
 
51
 
def get_homedir(user):
52
 
    try:
53
 
        user = pwd.getpwnam(user)
54
 
        return user.pw_dir
55
 
    except KeyError:
56
 
        utils.juju_log('INFO',
57
 
                       'Could not get homedir for user %s: user exists?')
58
 
        sys.exit(1)
59
 
 
60
 
 
61
 
def get_keypair(user):
62
 
    home_dir = get_homedir(user)
63
 
    ssh_dir = os.path.join(home_dir, '.ssh')
64
 
    if not os.path.isdir(ssh_dir):
65
 
        os.mkdir(ssh_dir)
66
 
 
67
 
    priv_key = os.path.join(ssh_dir, 'id_rsa')
68
 
    if not os.path.isfile(priv_key):
69
 
        utils.juju_log('INFO', 'Generating new ssh key for user %s.' % user)
70
 
        cmd = ['ssh-keygen', '-q', '-N', '', '-t', 'rsa', '-b', '2048',
71
 
               '-f', priv_key]
72
 
        subprocess.check_call(cmd)
73
 
 
74
 
    pub_key = '%s.pub' % priv_key
75
 
    if not os.path.isfile(pub_key):
76
 
        utils.juju_log('INFO', 'Generatring missing ssh public key @ %s.' %
77
 
                       pub_key)
78
 
        cmd = ['ssh-keygen', '-y', '-f', priv_key]
79
 
        p = subprocess.check_output(cmd).strip()
80
 
        with open(pub_key, 'wb') as out:
81
 
            out.write(p)
82
 
    subprocess.check_call(['chown', '-R', user, ssh_dir])
83
 
    return open(priv_key, 'r').read().strip(), open(pub_key, 'r').read().strip()
84
 
 
85
 
 
86
 
def write_authorized_keys(user, keys):
87
 
    home_dir = get_homedir(user)
88
 
    ssh_dir = os.path.join(home_dir, '.ssh')
89
 
    auth_keys = os.path.join(ssh_dir, 'authorized_keys')
90
 
    utils.juju_log('INFO', 'Syncing authorized_keys @ %s.' % auth_keys)
91
 
    with open(auth_keys, 'wb') as out:
92
 
        for k in keys:
93
 
            out.write('%s\n' % k)
94
 
 
95
 
 
96
 
def write_known_hosts(user, hosts):
97
 
    home_dir = get_homedir(user)
98
 
    ssh_dir = os.path.join(home_dir, '.ssh')
99
 
    known_hosts = os.path.join(ssh_dir, 'known_hosts')
100
 
    khosts = []
101
 
    for host in hosts:
102
 
        cmd = ['ssh-keyscan', '-H', '-t', 'rsa', host]
103
 
        remote_key = subprocess.check_output(cmd).strip()
104
 
        khosts.append(remote_key)
105
 
    utils.juju_log('INFO', 'Syncing known_hosts @ %s.' % known_hosts)
106
 
    with open(known_hosts, 'wb') as out:
107
 
        for host in khosts:
108
 
            out.write('%s\n' % host)
109
 
 
110
 
 
111
 
def ensure_user(user, group=None):
112
 
    # need to ensure a bash shell'd user exists.
113
 
    try:
114
 
        pwd.getpwnam(user)
115
 
    except KeyError:
116
 
        utils.juju_log('INFO', 'Creating new user %s.%s.' % (user, group))
117
 
        cmd = ['adduser', '--system', '--shell', '/bin/bash', user]
118
 
        if group:
119
 
            try:
120
 
                grp.getgrnam(group)
121
 
            except KeyError:
122
 
                subprocess.check_call(['addgroup', group])
123
 
            cmd += ['--ingroup', group]
124
 
        subprocess.check_call(cmd)
125
 
 
126
 
 
127
 
def ssh_authorized_peers(peer_interface, user, group=None, ensure_local_user=False):
128
 
    """
129
 
    Main setup function, should be called from both peer -changed and -joined
130
 
    hooks with the same parameters.
131
 
    """
132
 
    if ensure_local_user:
133
 
        ensure_user(user, group)
134
 
    priv_key, pub_key = get_keypair(user)
135
 
    hook = os.path.basename(sys.argv[0])
136
 
    if hook == '%s-relation-joined' % peer_interface:
137
 
        utils.relation_set(ssh_pub_key=pub_key)
138
 
        print 'joined'
139
 
    elif hook == '%s-relation-changed' % peer_interface:
140
 
        hosts = []
141
 
        keys = []
142
 
        for r_id in utils.relation_ids(peer_interface):
143
 
            for unit in utils.relation_list(r_id):
144
 
                settings = utils.relation_get_dict(relation_id=r_id,
145
 
                                                   remote_unit=unit)
146
 
                if 'ssh_pub_key' in settings:
147
 
                    keys.append(settings['ssh_pub_key'])
148
 
                    hosts.append(settings['private-address'])
149
 
                else:
150
 
                    utils.juju_log('INFO',
151
 
                                   'ssh_authorized_peers(): ssh_pub_key '
152
 
                                   'missing for unit %s, skipping.' % unit)
153
 
        write_authorized_keys(user, keys)
154
 
        write_known_hosts(user, hosts)
155
 
        authed_hosts = ':'.join(hosts)
156
 
        utils.relation_set(ssh_authorized_hosts=authed_hosts)
157
 
 
158
 
 
159
 
def _run_as_user(user):
160
 
    try:
161
 
        user = pwd.getpwnam(user)
162
 
    except KeyError:
163
 
        utils.juju_log('INFO', 'Invalid user: %s' % user)
164
 
        sys.exit(1)
165
 
    uid, gid = user.pw_uid, user.pw_gid
166
 
    os.environ['HOME'] = user.pw_dir
167
 
 
168
 
    def _inner():
169
 
        os.setgid(gid)
170
 
        os.setuid(uid)
171
 
    return _inner
172
 
 
173
 
 
174
 
def run_as_user(user, cmd):
175
 
    return subprocess.check_output(cmd, preexec_fn=_run_as_user(user), cwd='/')
176
 
 
177
 
 
178
 
def sync_to_peers(peer_interface, user, paths=[], verbose=False):
179
 
    base_cmd = ['unison', '-auto', '-batch=true', '-confirmbigdel=false',
180
 
                '-fastcheck=true', '-group=false', '-owner=false',
181
 
                '-prefer=newer', '-times=true']
182
 
    if not verbose:
183
 
        base_cmd.append('-silent')
184
 
 
185
 
    hosts = []
186
 
    for r_id in (utils.relation_ids(peer_interface) or []):
187
 
        for unit in utils.relation_list(r_id):
188
 
            settings = utils.relation_get_dict(relation_id=r_id,
189
 
                                               remote_unit=unit)
190
 
            try:
191
 
                authed_hosts = settings['ssh_authorized_hosts'].split(':')
192
 
            except KeyError:
193
 
                print 'unison sync_to_peers: peer has not authorized *any* '\
194
 
                      'hosts yet.'
195
 
                return
196
 
 
197
 
            unit_hostname = utils.unit_get('private-address')
198
 
            add_host = None
199
 
            for authed_host in authed_hosts:
200
 
                if unit_hostname == authed_host:
201
 
                    add_host = settings['private-address']
202
 
            if add_host:
203
 
                hosts.append(settings['private-address'])
204
 
            else:
205
 
                print 'unison sync_to_peers: peer (%s) has not authorized '\
206
 
                      '*this* host yet, skipping.' % settings['private-address']
207
 
 
208
 
    for path in paths:
209
 
        # removing trailing slash from directory paths, unison
210
 
        # doesn't like these.
211
 
        if path.endswith('/'):
212
 
            path = path[:(len(path) - 1)]
213
 
        for host in hosts:
214
 
            cmd = base_cmd + [path, 'ssh://%s@%s/%s' % (user, host, path)]
215
 
            utils.juju_log('INFO', 'Syncing local path %s to %s@%s:%s' %
216
 
                           (path, user, host, path))
217
 
            run_as_user(user, cmd)