~charmers/charms/precise/meteor/trunk

« back to all changes in this revision

Viewing changes to hooks/hooks.py

  • Committer: Matt Bruzek
  • Date: 2014-09-09 21:06:11 UTC
  • mfrom: (4.2.2 meteor)
  • Revision ID: matthew.bruzek@canonical.com-20140909210611-5dnrzphgp2ycm8fd
[tvansteenburgh] Remove Config, fix npm install, implement upgrade-charm

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
 
1
#!/usr/bin/env python
2
2
 
3
3
from contextlib import contextmanager
4
 
import json
5
4
import os
6
5
import shutil
7
6
import subprocess
12
11
 
13
12
from charmhelpers import fetch
14
13
from charmhelpers.core import (
15
 
        hookenv,
16
 
        host,
17
 
        )
 
14
    hookenv,
 
15
    host,
 
16
)
18
17
 
19
18
hooks = hookenv.Hooks()
20
19
 
24
23
CODE_DIR = BASE_DIR + '/code'
25
24
BUNDLE_DIR = BASE_DIR + '/bundle'
26
25
BUNDLE_ARCHIVE = BASE_DIR + '/bundle.tgz'
27
 
METEOR_CONFIG = BASE_DIR + '/.juju-config'
28
26
NODEJS_REPO = 'ppa:chris-lea/node.js'
29
27
PACKAGES = ['nodejs', 'build-essential', 'curl']
30
28
DOWNLOAD_CMD = 'curl http://install.meteor.com -o /tmp/meteor-install'
31
29
INSTALL_CMD = '/bin/sh /tmp/meteor-install'
32
30
 
33
31
 
34
 
class Config(object):
35
 
    """A Juju config dictionary that can write itself to
36
 
    disk (json) and track which values have changed since
37
 
    the previous write.
38
 
 
39
 
    """
40
 
    def __init__(self, path=None):
41
 
        """Initialize a `hookenv.config(), with an optional
42
 
        "previous" copy loaded from `path`.
43
 
 
44
 
        """
45
 
        self.cur_dict = hookenv.config()
46
 
        self.prev_dict = None
47
 
        self.path = path
48
 
        if path:
49
 
            self.load_previous(path)
50
 
 
51
 
    def load_previous(self, path):
52
 
        """Load a previous copy of config so that current values
53
 
        can be compares to previous values.
54
 
 
55
 
        """
56
 
        try:
57
 
            with open(path) as f:
58
 
                self.prev_dict = json.load(f)
59
 
        except IOError:
60
 
            pass
61
 
 
62
 
    def changed(self, key):
63
 
        """Return true if the value for this key has changed since
64
 
        the last save.
65
 
 
66
 
        """
67
 
        if self.prev_dict is None:
68
 
            return True
69
 
        return self.prev_dict.get(key) != self.cur_dict.get(key)
70
 
 
71
 
    def previous(self, key):
72
 
        """Return previous value for this key, or None if there
73
 
        is no "previous" value.
74
 
 
75
 
        """
76
 
        if self.prev_dict:
77
 
            return self.prev_dict.get(key)
78
 
        return None
79
 
 
80
 
    def save(self, path=None):
81
 
        """Save this config to `path`, or to the path from which
82
 
        it was loaded. This is a no-op of path is None or this
83
 
        config was not loaded from a file.
84
 
 
85
 
        Preserves items in prev_dict that do not exist in cur_dict.
86
 
 
87
 
        """
88
 
        path = path or self.path
89
 
        if not path:
90
 
            return
91
 
        if self.prev_dict:
92
 
            for k, v in self.prev_dict.iteritems():
93
 
                if k not in self.cur_dict:
94
 
                    self.cur_dict[k] = v
95
 
        with open(path, 'w') as f:
96
 
            json.dump(self.cur_dict, f)
97
 
 
98
 
    def get(self, key, default=None):
99
 
        return self.cur_dict.get(key, default)
100
 
 
101
 
    def __getitem__(self, key):
102
 
        return self.cur_dict[key]
103
 
 
104
 
    def __setitem__(self, key, val):
105
 
        self.cur_dict[key] = val
106
 
 
107
 
 
108
32
def write_upstart(config):
109
33
    upstart = textwrap.dedent("""\
110
34
    description "Meteor app '{app_name}'"
206
130
            }[config['repo-type']]
207
131
        fetch.apt_install(pkg)
208
132
        clone(config['repo-type'], config['repo-url'], CODE_DIR,
209
 
                config['repo-revision'])
 
133
              config['repo-revision'])
210
134
    else:
211
135
        hookenv.log('Creating demo app')
212
 
        subprocess.check_call(['mrt', 'create', '--example',
213
 
            config['demo-app'], CODE_DIR])
 
136
        subprocess.check_call(
 
137
            ['mrt', 'create', '--example', config['demo-app'], CODE_DIR])
214
138
 
215
139
 
216
140
def init_bundle(config):
230
154
        shutil.copytree(os.path.join(CODE_DIR, 'bundle'), BUNDLE_DIR)
231
155
 
232
156
 
 
157
def init_dependencies(config):
 
158
    """Install dependencies for installed meteor app.
 
159
 
 
160
    """
 
161
    depends_dir = os.path.join(BUNDLE_DIR, 'programs', 'server')
 
162
    if os.path.exists(depends_dir):
 
163
        with chdir(depends_dir):
 
164
            subprocess.call(['npm', 'install'])
 
165
 
 
166
 
233
167
@hooks.hook('install')
234
168
def install():
235
 
    config = Config(METEOR_CONFIG)
 
169
    config = hookenv.config()
236
170
 
237
171
    host.adduser(USER, password='')
238
172
    host.mkdir(BASE_DIR, owner=USER, group=USER)
252
186
 
253
187
    init_code(config)
254
188
    init_bundle(config)
 
189
    init_dependencies(config)
255
190
 
256
191
    hookenv.open_port(config['port'])
257
 
    subprocess.check_call(['chown', '-R', '{user}:{user}'.format(user=USER),
258
 
        BASE_DIR])
 
192
    subprocess.check_call(
 
193
        ['chown', '-R', '{user}:{user}'.format(user=USER), BASE_DIR])
259
194
 
260
195
    config['mongo_url'] = ''
261
196
    write_upstart(config)
262
 
    config.save()
263
197
    # wait for mongodb to join before starting
264
198
 
265
199
 
266
200
@hooks.hook('config-changed')
267
201
def config_changed():
268
 
    config = Config(METEOR_CONFIG)
 
202
    config = hookenv.config()
269
203
    os.environ['HOME'] = os.path.expanduser('~' + USER)
270
204
 
271
205
    if config.changed('repo-url') or config.changed('demo-app'):
274
208
        init_code(config)
275
209
    elif config.changed('repo-revision') and config['repo-url']:
276
210
        with chdir(CODE_DIR):
277
 
            subprocess.check_call([config['repo-type'], 'pull',
278
 
                config['repo-url']])
 
211
            subprocess.check_call(
 
212
                [config['repo-type'], 'pull', config['repo-url']])
279
213
        checkout(config['repo-type'], CODE_DIR, config['repo-revision'])
280
214
 
281
215
    if (config.changed('repo-url') or
283
217
            config.changed('bundled') or
284
218
            config.changed('demo-app')):
285
219
        init_bundle(config)
 
220
        init_dependencies(config)
286
221
 
287
222
    if config.changed('port'):
288
223
        hookenv.open_port(config['port'])
290
225
            hookenv.close_port(config.previous('port'))
291
226
        if hookenv.is_relation_made('website'):
292
227
            hookenv.relation_set(
293
 
                    relation_id=config.previous('website-relation-id'),
 
228
                relation_id=config.previous('website-relation-id'),
294
229
                relation_settings={
295
230
                    'port': config['port'],
296
231
                    'hostname': hookenv.unit_private_ip()})
297
232
 
298
 
    subprocess.check_call(['chown', '-R', '{user}:{user}'.format(user=USER),
299
 
        BASE_DIR])
 
233
    subprocess.check_call(
 
234
        ['chown', '-R', '{user}:{user}'.format(user=USER), BASE_DIR])
300
235
 
301
236
    if config.previous('mongo_url'):
302
237
        mongo_host = config.previous('mongo_url').rsplit('/', 1)[0]
304
239
        config['mongo_url'] = mongo_url
305
240
 
306
241
    write_upstart(config)
307
 
    config.save(METEOR_CONFIG)
308
242
    start()
309
243
 
310
244
 
311
245
@hooks.hook('mongodb-relation-changed')
312
246
def mongodb_relation_changed():
313
 
    config = Config(METEOR_CONFIG)
 
247
    config = hookenv.config()
314
248
    db_host = hookenv.relation_get('private-address')
315
249
    if not db_host:
316
250
        return
317
251
    db_port = hookenv.relation_get('port') or '27017'
318
252
    config['mongo_url'] = 'mongodb://{host}:{port}/{app_name}'.format(
319
 
            host=db_host, port=db_port, app_name=config['app-name'])
 
253
        host=db_host, port=db_port, app_name=config['app-name'])
320
254
    write_upstart(config)
321
 
    config.save()
322
255
    start()
323
256
 
324
257
 
330
263
@hooks.hook('upgrade-charm')
331
264
def upgrade_charm():
332
265
    hookenv.log('Upgrading Meteor')
333
 
    config = Config(METEOR_CONFIG)
334
 
 
 
266
 
 
267
    OLD_METEOR_CONFIG = BASE_DIR + '/.juju-config'
 
268
    NEW_METEOR_CONFIG = os.path.join(hookenv.charm_dir(),
 
269
                                     hookenv.Config.CONFIG_FILE_NAME)
 
270
 
 
271
    if (os.path.exists(OLD_METEOR_CONFIG) and not
 
272
            os.path.exists(NEW_METEOR_CONFIG)):
 
273
        hookenv.log('Moving config from {} to {}'.format(
 
274
            OLD_METEOR_CONFIG, NEW_METEOR_CONFIG))
 
275
        shutil.move(OLD_METEOR_CONFIG, NEW_METEOR_CONFIG)
 
276
 
 
277
    config = hookenv.config()
 
278
    os.environ['HOME'] = os.path.expanduser('~' + USER)
 
279
 
 
280
    hookenv.log('Upgrading nodejs')
 
281
    fetch.apt_update()
 
282
    fetch.apt_install(PACKAGES)
 
283
 
 
284
    hookenv.log('Upgrading meteor/meteorite')
335
285
    subprocess.check_call(DOWNLOAD_CMD.split())
336
286
    subprocess.check_call(INSTALL_CMD.split())
337
 
    init_bundle(config)
 
287
    subprocess.check_call('npm install -g meteorite'.split())
 
288
 
 
289
    init_dependencies(config)
 
290
 
338
291
    if host.service_running(SERVICE):
339
292
        start()
340
293
 
354
307
 
355
308
@hooks.hook('website-relation-joined')
356
309
def website_relation_joined():
357
 
    config = Config(METEOR_CONFIG)
358
 
    hookenv.relation_set(port=config['port'],
359
 
            hostname=hookenv.unit_private_ip())
 
310
    config = hookenv.config()
 
311
    hookenv.relation_set(
 
312
        port=config['port'], hostname=hookenv.unit_private_ip())
360
313
    # save relation id so we can inform the remote side if we change ports
361
314
    config['website-relation-id'] = os.environ['JUJU_RELATION_ID']
362
 
    config.save(METEOR_CONFIG)
363
315
 
364
316
 
365
317
if __name__ == "__main__":