~fo0bar/turku/turku-agent-encoding

« back to all changes in this revision

Viewing changes to turku-update-config

  • Committer: Ryan Finnie
  • Date: 2014-12-18 02:14:51 UTC
  • Revision ID: ryan.finnie@canonical.com-20141218021451-7spz560bpknitkd1
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
 
 
3
from __future__ import print_function
 
4
import uuid
 
5
import string
 
6
import random
 
7
import json
 
8
import os
 
9
import copy
 
10
import subprocess
 
11
import sys
 
12
import platform
 
13
 
 
14
CONFIG_D = '/etc/turku-agent/config.d'
 
15
SOURCES_D = '/etc/turku-agent/sources.d'
 
16
SOURCES_SECRETS_D = '/etc/turku-agent/sources_secrets.d'
 
17
SSH_PRIVATE_KEY = '/etc/turku-agent/id_rsa'
 
18
SSH_PUBLIC_KEY = '/etc/turku-agent/id_rsa.pub'
 
19
RSYNCD_CONF = '/etc/turku-agent/rsyncd.conf'
 
20
RSYNCD_SECRETS = '/etc/turku-agent/rsyncd.secrets'
 
21
 
 
22
 
 
23
def json_dump_p(obj, f):
 
24
    """Calls json.dump with standard (pretty) formatting"""
 
25
    return json.dump(obj, f, sort_keys=True, indent=4, separators=(',', ': '))
 
26
 
 
27
 
 
28
def json_dumps_p(obj):
 
29
    """Calls json.dumps with standard (pretty) formatting"""
 
30
    return json.dumps(obj, sort_keys=True, indent=4, separators=(',', ': '))
 
31
 
 
32
 
 
33
def dict_merge(s, m):
 
34
    """Recursively merge one dict into another."""
 
35
    if not isinstance(m, dict):
 
36
        return m
 
37
    out = copy.deepcopy(s)
 
38
    for k, v in m.items():
 
39
        if k in out and isinstance(out[k], dict):
 
40
            out[k] = dict_merge(out[k], v)
 
41
        else:
 
42
            out[k] = copy.deepcopy(v)
 
43
    return out
 
44
 
 
45
 
 
46
def check_directories():
 
47
    for d in (CONFIG_D, SOURCES_D):
 
48
        if not os.path.isdir(d):
 
49
            os.makedirs(d)
 
50
    for d in (SOURCES_SECRETS_D):
 
51
        if not os.path.isdir(d):
 
52
            print(d)
 
53
            os.makedirs(d)
 
54
            os.chmod(d, 0o700)
 
55
    for f in (SSH_PRIVATE_KEY, SSH_PUBLIC_KEY, RSYNCD_CONF, RSYNCD_SECRETS):
 
56
        d = os.path.dirname(f)
 
57
        if not os.path.isdir(d):
 
58
            os.makedirs(d)
 
59
 
 
60
 
 
61
def parse_config():
 
62
    built_config = {
 
63
        'sources': {}
 
64
    }
 
65
 
 
66
    # Merge in config.d/*.json to the root level
 
67
    config_files = [os.path.join(CONFIG_D, fn) for fn in os.listdir(CONFIG_D) if fn.endswith('.json') and os.path.isfile(os.path.join(CONFIG_D, fn)) and os.access(os.path.join(CONFIG_D, fn), os.R_OK)]
 
68
    config_files.sort()
 
69
    for file in config_files:
 
70
        with open(file) as f:
 
71
            j = json.load(f)
 
72
        built_config = dict_merge(built_config, j)
 
73
 
 
74
    # Validate the unit name
 
75
    if not 'unit_name' in built_config:
 
76
        built_config['unit_name'] = platform.node()
 
77
        # If this isn't in the on-disk config, don't write it; just
 
78
        # generate it every time
 
79
 
 
80
    # Validate the machine UUID/secret
 
81
    write_uuid_data = False
 
82
    if not 'machine_uuid' in built_config:
 
83
        built_config['machine_uuid'] = str(uuid.uuid4())
 
84
        write_uuid_data = True
 
85
    if not 'machine_secret' in built_config:
 
86
        built_config['machine_secret'] = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(30))
 
87
        write_uuid_data = True
 
88
    # Write out the machine UUID/secret if needed
 
89
    if write_uuid_data:
 
90
        with open(os.path.join(CONFIG_D, '10-machine_uuid.json'), 'w') as f:
 
91
            os.chmod(os.path.join(CONFIG_D, '10-machine_uuid.json'), 0o600)
 
92
            json_dump_p({'machine_uuid': built_config['machine_uuid'], 'machine_secret': built_config['machine_secret']}, f)
 
93
 
 
94
    # Generate the SSH keypair if it doesn't exist
 
95
    if not os.path.isfile(SSH_PUBLIC_KEY):
 
96
        subprocess.check_call(['ssh-keygen', '-t', 'rsa', '-N', '', '-C', 'turku', '-f', SSH_PRIVATE_KEY])
 
97
 
 
98
    # Pull the SSH public key
 
99
    with open(SSH_PUBLIC_KEY) as f:
 
100
        built_config['ssh_public_key'] = f.read().rstrip()
 
101
 
 
102
    # Merge in sources.d/*.json to the sources dict
 
103
    sources_files = [os.path.join(SOURCES_D, fn) for fn in os.listdir(SOURCES_D) if fn.endswith('.json') and os.path.isfile(os.path.join(SOURCES_D, fn)) and os.access(os.path.join(SOURCES_D, fn), os.R_OK)]
 
104
    sources_files.sort()
 
105
    for file in sources_files:
 
106
        with open(file) as f:
 
107
            j = json.load(f)
 
108
        for s in j.keys():
 
109
            # Ignore incomplete source entries
 
110
            if not 'path' in j[s]:
 
111
                print('WARNING: Path not found for "%s", not using.' % s, file=sys.stderr)
 
112
                del j[s]
 
113
        built_config = dict_merge(built_config, {'sources': j})
 
114
 
 
115
    for s in built_config['sources']:
 
116
        # Check for missing usernames/passwords
 
117
        if not ('username' in built_config['sources'][s] or 'password' in built_config['sources'][s]):
 
118
            # If they're in sources_secrets.d, use them
 
119
            if os.path.isfile(os.path.join(SOURCES_SECRETS_D, s + '.json')):
 
120
                with open(os.path.join(SOURCES_SECRETS_D, s + '.json')) as f:
 
121
                    j = json.load(f)
 
122
                built_config = dict_merge(built_config, {'sources': {s: j}})
 
123
        # Check again and generate sources_secrets.d if still not found
 
124
        if not ('username' in built_config['sources'][s] or 'password' in built_config['sources'][s]):
 
125
            if not 'username' in built_config['sources'][s]:
 
126
                built_config['sources'][s]['username'] = str(uuid.uuid4())
 
127
            if not 'password' in built_config['sources'][s]:
 
128
                built_config['sources'][s]['password'] = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(30))
 
129
            with open(os.path.join(SOURCES_SECRETS_D, s + '.json'), 'w') as f:
 
130
                json_dump_p({'username': built_config['sources'][s]['username'], 'password': built_config['sources'][s]['password']}, f)
 
131
 
 
132
    #print(json_dumps_p(built_config))
 
133
 
 
134
    return(built_config)
 
135
 
 
136
 
 
137
def write_conf_files(built_config):
 
138
    # Build rsyncd.conf
 
139
    built_rsyncd_conf = 'address = 127.0.0.1\nport = 27873\nlog file = /dev/stdout\nuid = root\ngid = root\nlist = false\n\n'
 
140
    rsyncd_secrets = []
 
141
    for s in built_config['sources']:
 
142
        sd = built_config['sources'][s]
 
143
        rsyncd_secrets.append((sd['username'], sd['password']))
 
144
        built_rsyncd_conf += '[%s]\n    path = %s\n    auth users = %s\n    secrets_file = %s\n    read only = true\n\n' % (s, sd['path'], sd['username'], RSYNCD_SECRETS)
 
145
    with open(RSYNCD_CONF, 'w') as f:
 
146
        f.write(built_rsyncd_conf)
 
147
 
 
148
    #print(built_rsyncd_conf)
 
149
 
 
150
    # Build rsyncd.secrets
 
151
    built_rsyncd_secrets = ''
 
152
    for (username, password) in rsyncd_secrets:
 
153
        built_rsyncd_secrets += username + ':' + password + '\n'
 
154
    with open(RSYNCD_SECRETS, 'w') as f:
 
155
        os.chmod(RSYNCD_SECRETS, 0o600)
 
156
        f.write(built_rsyncd_secrets)
 
157
 
 
158
    #print(built_rsyncd_secrets)
 
159
 
 
160
 
 
161
def restart_services():
 
162
    # Restart rsyncd
 
163
    if not subprocess.call(['service', 'turku-agent-rsyncd', 'restart']) == 0:
 
164
        subprocess.check_call(['service', 'turku-agent-rsyncd', 'start'])
 
165
 
 
166
 
 
167
def main(argv):
 
168
    check_directories()
 
169
    built_config = parse_config()
 
170
    write_conf_files(built_config)
 
171
    restart_services()
 
172
 
 
173
 
 
174
if __name__ == '__main__':
 
175
    sys.exit(main(sys.argv[1:]))