Merge lp:~dstroppa/charms/precise/node-app/refactor-hooks into lp:charms/node-app
- Precise Pangolin (12.04)
- refactor-hooks
- Merge into trunk
Status: | Work in progress |
---|---|
Proposed branch: | lp:~dstroppa/charms/precise/node-app/refactor-hooks |
Merge into: | lp:charms/node-app |
Diff against target: |
1377 lines (+679/-367) 20 files modified
.bzrignore (+3/-0) config.yaml (+2/-2) config/config.js (+10/-0) hooks/config-changed (+0/-137) hooks/install (+0/-64) hooks/install-app (+14/-0) hooks/mongodb-relation-changed (+0/-50) hooks/mongodb-relation-departed (+0/-4) hooks/start (+9/-3) hooks/stop (+0/-5) hooks/website-relation-changed (+0/-4) icon.svg (+37/-97) lib/app.js (+435/-0) lib/charm.js (+75/-0) metadata.yaml (+1/-1) tests/00-setup (+5/-0) tests/01-mongodb-deployment (+31/-0) tests/02-mongodb-haproxy-deployment (+33/-0) tpl/mongodb_env.tpl (+8/-0) tpl/upstart.tpl (+16/-0) |
To merge this branch: | bzr merge lp:~dstroppa/charms/precise/node-app/refactor-hooks |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
charmers | Pending | ||
Review via email: mp+200330@code.launchpad.net |
Commit message
Description of the change
Hooks have been re-written in JS
- 58. By Daniele Stroppa
-
Changes after review: moved app specific method into lib/app.js, updated upstart script, new icon
Daniele Stroppa (dstroppa) wrote : | # |
Comments inline.
>
> - I don't understand what expose_app is for, how does this differ from
> juju expose node-app?
>
It's actually not different, and seeing your other comment below (" juju
unexpose (and expose) aren't executed from the charm but instead by the
user") I've removed this.
> - Excellent use of app_user, however it's not being used in the upstart
> script. Consider using the following as a reference:
> https:/
Modified the upstart script according to this reference.
- I really like the way lib/charm.js is shaping up, I'm confident this will
> be a key part in helping other node.js authors write hooks/charms in
> node.js Along the same vein I think methods like installPackage,
> installApp, updateApp, configureApp, etc are fantastic, but don't seem
> inheriently a part of Charm itself. As an opinion it might be better served
> breaking the two apart. Charm to emulate all the core juju functions in
> node then a seperat App module with these methods.
>
This is actually a good point. I've moved out of lib/charm.js all methods
that are app related (now in lib/app.js)
>
> - The charm needs more logging to juju-log, most of the comments you have
> in the final callbacks are great, sending them to juju-log will notify the
> user what's going on and help debug issues
>
Agreed, added logging to juju-log.
>
> - juju unexpose (and expose) aren't executed from the charm but instead by
> the user, as a result the mongodb-
> be updated. Instead the charm needs to tell juju /what/ports to open by
> calling `open-port` and `close-port` These won't explicitly open or close
> the ports, but tells juju what ports to open in the firewall when the user
> run juju expose/unexpose.
>
Done.
>
> # Knitpick
>
> - I really like the idea of a polling_schedule, as an alternative it might
> be nice to have a push_url, where the user could specify a token as a
> configuration option, then have a url like http://
> so they could have github push notifications on updates and other
> repositories instead of a constant polling. Just an idea.
>
> - Having config/config.js where it is, I thought it was something the user
> filled out. I fear other users may also make this assumption. I'm not sure
> of a better place just making you aware of the perception.
>
> - You'll want to update the maintainer field in the metadata.yaml file
>
Done :)
>
> - Again, not sure if this is a common practice in node, but relation_set
> seems to take a list of keys and values, would it be better served as an
> object of key: val pairs?
>
Agreed, that's a better way.
>
> Sorry this review was so...breif...in words. I honestly love the direction
> of the charm so far and understand it's still under development. These were
> things that popped out at me doing a comb through the charm. Let me know if
> you have any question, happy to discuss these points further.
>
> I'm going to move the merge request to Work In Progress, feel free to
> email me when you'd like another look or just move the merge to "Needs
> Review"
> --
>
> https:/
- 59. By Daniele Stroppa
-
Adding Amulet tests
Unmerged revisions
- 59. By Daniele Stroppa
-
Adding Amulet tests
- 58. By Daniele Stroppa
-
Changes after review: moved app specific method into lib/app.js, updated upstart script, new icon
- 57. By Daniele Stroppa
-
Fixing relation-set and website-
relation- changed hook - 56. By Daniele Stroppa
-
Added flag to expose the app or use proxy
- 55. By Daniele Stroppa
-
code cleanup, added close-port on stop
- 54. By Daniele Stroppa
-
mongodb-
relation- changed, config-changed hooks tested - 53. By Daniele Stroppa
-
Testing/Debuggin hooks
- 52. By Daniele Stroppa
-
Hooks debugging - install hook fixed
- 51. By Daniele Stroppa
-
Hooks debugging
- 50. By Daniele Stroppa
-
Hooks Debugging/Testing
Preview Diff
1 | === added file '.bzrignore' |
2 | --- .bzrignore 1970-01-01 00:00:00 +0000 |
3 | +++ .bzrignore 2014-01-24 18:05:39 +0000 |
4 | @@ -0,0 +1,3 @@ |
5 | +.idea |
6 | +node-app.iml |
7 | +hooks.old |
8 | |
9 | === added directory 'config' |
10 | === modified file 'config.yaml' |
11 | --- config.yaml 2013-03-08 13:06:46 +0000 |
12 | +++ config.yaml 2014-01-24 18:05:39 +0000 |
13 | @@ -10,10 +10,10 @@ |
14 | app_scm: |
15 | type: string |
16 | default: git |
17 | - description: Repository type (git/bzr/...) |
18 | + description: Repository type (git/bzr/subversion/mercurial) |
19 | app_url: |
20 | type: string |
21 | - default: "http://github.com/mmm/testnode.git" |
22 | + default: "https://github.com/dstroppa/testnode.git" |
23 | description: Application repository URL |
24 | app_branch: |
25 | type: string |
26 | |
27 | === added file 'config/config.js' |
28 | --- config/config.js 1970-01-01 00:00:00 +0000 |
29 | +++ config/config.js 2014-01-24 18:05:39 +0000 |
30 | @@ -0,0 +1,10 @@ |
31 | +{ |
32 | + 'name': '', |
33 | + 'listen_port': 0, |
34 | + 'database': { |
35 | + 'mongo_host': '', |
36 | + 'mongo_port': '', |
37 | + 'mongo_replset': '' |
38 | + }, |
39 | + |
40 | +} |
41 | \ No newline at end of file |
42 | |
43 | === added file 'hooks/config-changed' |
44 | --- hooks/config-changed 1970-01-01 00:00:00 +0000 |
45 | +++ hooks/config-changed 2014-01-24 18:05:39 +0000 |
46 | @@ -0,0 +1,14 @@ |
47 | +#!/usr/bin/env node |
48 | + |
49 | +var app = require('../lib/app')(); |
50 | +var charm = require('../lib/charm')(); |
51 | +var util = require('util'); |
52 | + |
53 | +charm.config_get_all(function(config) { |
54 | + app.updateApp(config, function() { |
55 | + app.configureApp(config, function() { |
56 | + // app updated and configured successfully |
57 | + charm.log(util.format('%s updated and configured successfully.', config.app_name)); |
58 | + }); |
59 | + }); |
60 | +}); |
61 | \ No newline at end of file |
62 | |
63 | === removed file 'hooks/config-changed' |
64 | --- hooks/config-changed 2013-04-04 12:42:58 +0000 |
65 | +++ hooks/config-changed 1970-01-01 00:00:00 +0000 |
66 | @@ -1,137 +0,0 @@ |
67 | -#!/bin/bash |
68 | - |
69 | -set -eu # -x for verbose logging to juju debug-log |
70 | - |
71 | -umask 002 |
72 | - |
73 | -install_root=`config-get install_root` |
74 | -app_name=`config-get app_name` |
75 | -app_dir="$install_root/$app_name" |
76 | -app_user=`config-get app_user` |
77 | -app_branch=`config-get app_branch` |
78 | -app_port=`config-get app_port` |
79 | -app_node_env=`config-get app_node_env` |
80 | -cron_schedule=`config-get polling_schedule` |
81 | - |
82 | -update_app() { |
83 | - cd $app_dir |
84 | - |
85 | - is_branch=false |
86 | - |
87 | - # Always fetch latest if branch requested |
88 | - if git checkout origin/${app_branch}; then # is a local branch |
89 | - git fetch origin && git checkout origin/${app_branch} |
90 | - is_branch=true |
91 | - else # tag/commit ref? |
92 | - if ! git checkout ${app_branch}; then # commit does not exist locally |
93 | - git fetch origin |
94 | - if ! git checkout origin/${app_branch}; then # not a new remote branch, is a commit ref/tag |
95 | - git checkout $app_branch |
96 | - else |
97 | - is_branch=true |
98 | - fi |
99 | - fi |
100 | - fi |
101 | -} |
102 | - |
103 | -configure_app() { |
104 | - juju-log "Configuring ${app_name}..." |
105 | - |
106 | - # If Procfile found, use it with foreman module to create upstart script |
107 | - if [ -f ${app_dir}/Procfile ]; then |
108 | - juju-log "Using Procfile to configure ${app_name} using foreman..." |
109 | - |
110 | - if [ -f /etc/juju_nodejs_app_${app_name}_mongodb.env ]; then |
111 | - mongodb_config=`cat /etc/juju_nodejs_app_${app_name}_mongodb.env` |
112 | - else |
113 | - mongodb_config='""' |
114 | - fi |
115 | - |
116 | - cat > /etc/juju_nodejs_app_${app_name}.env <<EOS |
117 | -{ |
118 | - "name": "${app_name}" |
119 | - , "node_env": "${app_node_env}" |
120 | - , "mongo": ${mongodb_config} |
121 | -} |
122 | -EOS |
123 | - |
124 | - cd $app_dir # foreman export uses this as root |
125 | - nf export -j ${app_dir}/Procfile -e /etc/juju_nodejs_app_${app_name}.env -a ${app_name} -u ${app_user} -p ${app_port} -o /etc/init -t upstart |
126 | - else |
127 | - config_file_path=${app_dir}/config/config.js |
128 | - if [ -f $config_file_path ]; then |
129 | - juju-log "Writing $app_name config file $config_file_path" |
130 | - sed -i "s/name.*/name\" : \"${app_name}\"/" $config_file_path |
131 | - sed -i "s/listen_port.*/listen_port\" : \"${app_port}\"/" $config_file_path |
132 | - fi |
133 | - fi |
134 | -} |
135 | - |
136 | -install_cronjob() { |
137 | - update_cron_job_filename=/opt/bin/update_node-app_from_SCM.sh |
138 | - |
139 | - if [ $is_branch ]; then |
140 | - if [ ! -d `dirname $update_cron_job_filename` ]; then |
141 | - mkdir -p `dirname $update_cron_job_filename` |
142 | - fi |
143 | - |
144 | - cat > $update_cron_job_filename <<EOS |
145 | - |
146 | -#!/usr/bin/env bash |
147 | - |
148 | -cd \${app_dir} |
149 | - |
150 | -old_commit=\`git rev-parse origin/\${app_branch}\` |
151 | - |
152 | -echo "Checking for updates to \${app_name} branch \${app_branch} commit \${old_commit}" |
153 | -git fetch origin |
154 | - |
155 | -new_commit=\`git rev-parse origin/\${app_branch}\` |
156 | - |
157 | -if [ \$old_commit != \$new_commit ]; then |
158 | - echo "Found new latest commit \${new_commit}" |
159 | - |
160 | - git checkout origin/\${app_branch} |
161 | - npm update && npm install |
162 | - sudo service \${app_name} restart |
163 | -else |
164 | - echo 'No updates found'; |
165 | -fi |
166 | -EOS |
167 | - |
168 | - chmod u=rwx,g=r,o=r $update_cron_job_filename |
169 | - chown ${app_user} $update_cron_job_filename |
170 | - |
171 | - cat > $update_cron_d_file <<EOF |
172 | -app_branch=$app_branch |
173 | -app_name=$app_name |
174 | -app_dir=$app_dir |
175 | - |
176 | -$cron_schedule $app_user $update_cron_job_filename |
177 | -EOF |
178 | - chmod u=rwx,g=r,o=r $update_cron_d_file |
179 | - else |
180 | - juju-log "Ignoring polling for new code as commit/tag specified" |
181 | - |
182 | - if [ -f $update_cron_d_file ]; then |
183 | - rm $update_cron_d_file |
184 | - fi |
185 | - fi |
186 | -} |
187 | - |
188 | -update_app |
189 | -configure_app |
190 | - |
191 | -update_cron_d_file=/etc/cron.d/${app_name} # TODO: what happens if app_name changes? |
192 | - |
193 | -if [ "$cron_schedule" != "" ]; then |
194 | - install_cronjob |
195 | -elif [ -f $update_cron_d_file ]; then |
196 | - rm $update_cron_d_file |
197 | -fi |
198 | - |
199 | -npm update && npm install |
200 | - |
201 | -chown -Rf ${app_user}.${app_user} ${app_dir} |
202 | - |
203 | -service ${app_name} restart |
204 | |
205 | === added file 'hooks/install' |
206 | --- hooks/install 1970-01-01 00:00:00 +0000 |
207 | +++ hooks/install 2014-01-24 18:05:39 +0000 |
208 | @@ -0,0 +1,20 @@ |
209 | +#!/bin/bash |
210 | + |
211 | +set -eu # -x for verbose logging to juju debug-log |
212 | + |
213 | +umask 002 |
214 | + |
215 | +install_node() { |
216 | + juju-log "Installing node..." |
217 | + add-apt-repository ppa:chris-lea/node.js |
218 | + apt-get update |
219 | + apt-get -y install -qq nodejs build-essential curl |
220 | +} |
221 | +[[ -x /usr/bin/node ]] || install_node |
222 | + |
223 | +modules=( mustache uid-number vasync ) |
224 | +for module in ${modules[@]}; do |
225 | + npm install $module |
226 | +done |
227 | + |
228 | +./hooks/install-app |
229 | \ No newline at end of file |
230 | |
231 | === removed file 'hooks/install' |
232 | --- hooks/install 2013-03-26 21:18:19 +0000 |
233 | +++ hooks/install 1970-01-01 00:00:00 +0000 |
234 | @@ -1,64 +0,0 @@ |
235 | -#!/bin/bash |
236 | - |
237 | -set -eu # -x for verbose logging to juju debug-log |
238 | - |
239 | -umask 002 |
240 | - |
241 | -install_root=`config-get install_root` |
242 | -app_name=`config-get app_name` |
243 | -app_dir="$install_root/$app_name" |
244 | -app_user=`config-get app_user` |
245 | -app_scm=`config-get app_scm` |
246 | -app_url=`config-get app_url` |
247 | -app_branch=`config-get app_branch` |
248 | -app_port=`config-get app_port` |
249 | - |
250 | -apt-get -y install -qq git-core |
251 | - |
252 | -install_node() { |
253 | - juju-log "Installing node..." |
254 | - add-apt-repository ppa:chris-lea/node.js |
255 | - apt-get update |
256 | - apt-get -y install -qq nodejs build-essential curl |
257 | -} |
258 | -[[ -x /usr/bin/node ]] || install_node |
259 | - |
260 | -install_app() { |
261 | - juju-log "Installing ${app_name}..." |
262 | - git clone ${app_url} ${app_dir} -b ${app_branch} |
263 | - chown -Rf ${app_user}.${app_user} ${app_dir} |
264 | - |
265 | - if [ -f ${app_dir}/package.json ]; then |
266 | - cd ${app_dir} && npm install |
267 | - fi |
268 | - |
269 | - # If Procfile found, use it with foreman module |
270 | - if [ -f ${app_dir}/Procfile ]; then |
271 | - npm install -g foreman |
272 | - else |
273 | - cat > /etc/init/${app_name}.conf <<EOS |
274 | -description "${app_name} node.js server" |
275 | - |
276 | -start on (net-device-up |
277 | - and local-filesystems |
278 | - and runlevel [2345]) |
279 | -stop on runlevel [!2345] |
280 | - |
281 | -expect fork |
282 | -respawn |
283 | - |
284 | -script |
285 | - export HOME=/ |
286 | - export NODE_PATH=/usr/lib/node |
287 | - #exec sudo -u ${app_user} /usr/bin/node ${app_dir}/server.js >> /var/log/${app_name}.log 2>&1 & |
288 | - exec /usr/bin/node ${app_dir}/server.js >> /var/log/${app_name}.log 2>&1 & |
289 | -end script |
290 | -EOS |
291 | - fi |
292 | -} |
293 | -[[ -d ${app_dir} ]] || install_app |
294 | - |
295 | -juju-log "Delaying app startup until mongodb joins" |
296 | -#juju-log "starting app" |
297 | -#service ${app_name} restart || service ${app_name} start |
298 | - |
299 | |
300 | === added file 'hooks/install-app' |
301 | --- hooks/install-app 1970-01-01 00:00:00 +0000 |
302 | +++ hooks/install-app 2014-01-24 18:05:39 +0000 |
303 | @@ -0,0 +1,14 @@ |
304 | +#!/usr/bin/env node |
305 | + |
306 | +var app = require('../lib/app')(); |
307 | +var charm = require('../lib/charm')(); |
308 | +var util = require('util'); |
309 | + |
310 | +charm.config_get_all(function(config) { |
311 | + app.installPackage(config.app_scm, function() { |
312 | + app.installApp(config, function() { |
313 | + // All done |
314 | + charm.log(util.format('%s installed successfully.', config.app_name)); |
315 | + }); |
316 | + }); |
317 | +}); |
318 | \ No newline at end of file |
319 | |
320 | === added file 'hooks/mongodb-relation-changed' |
321 | --- hooks/mongodb-relation-changed 1970-01-01 00:00:00 +0000 |
322 | +++ hooks/mongodb-relation-changed 2014-01-24 18:05:39 +0000 |
323 | @@ -0,0 +1,12 @@ |
324 | +#!/usr/bin/env node |
325 | + |
326 | +var app = require('../lib/app')(); |
327 | +var charm = require('../lib/charm')(); |
328 | +var util = require('util'); |
329 | + |
330 | +charm.config_get_all(function(config) { |
331 | + app.reconfigureApp(config, function() { |
332 | + //all done |
333 | + charm.log(util.format('%s configured and started successfully', config.app_name)); |
334 | + }); |
335 | +}); |
336 | |
337 | === removed file 'hooks/mongodb-relation-changed' |
338 | --- hooks/mongodb-relation-changed 2013-02-21 11:40:29 +0000 |
339 | +++ hooks/mongodb-relation-changed 1970-01-01 00:00:00 +0000 |
340 | @@ -1,50 +0,0 @@ |
341 | -#!/bin/bash |
342 | - |
343 | -set -eu |
344 | - |
345 | -# Get the database settings; if not set, wait for this hook to be |
346 | -# invoked again |
347 | -host=`relation-get private-address` |
348 | -if [ -z "$host" ] ; then |
349 | - exit 0 # wait for future handshake from database service unit |
350 | -fi |
351 | - |
352 | -relation_port=`relation-get port` |
353 | -port=${relation_port:-27017} |
354 | - |
355 | -replset=`relation-get replset` |
356 | - |
357 | -install_root=`config-get install_root` |
358 | -app_name=`config-get app_name` |
359 | -app_dir="$install_root/$app_name" |
360 | - |
361 | -configure_app() { |
362 | - juju-log "configuring ${app_name} to work with the mongodb service" |
363 | - |
364 | - config_file_path=$app_dir/config/config.js |
365 | - |
366 | - if [ -f ${app_dir}/Procfile ]; then |
367 | - cat > /etc/juju_nodejs_app_${app_name}_mongodb.env <<EOF |
368 | -{ |
369 | - "host": "${host}" |
370 | -, "port": ${port} |
371 | -, "replset": "${replset}" |
372 | -} |
373 | -EOF |
374 | - MY_DIR=`dirname $0` |
375 | - $MY_DIR/config-changed |
376 | - elif [ -f $config_file_path ]; then |
377 | - juju-log "Writing $app_name config file $config_file_path" |
378 | - sed -i "s/mongo_host.*/mongo_host\" : \"${host}\"/" $config_file_path |
379 | - sed -i "s/mongo_port.*/mongo_port\" : ${port}/" $config_file_path |
380 | - sed -i "s/mongo_replset.*/mongo_replset\" : \"${replset}\"/" $config_file_path |
381 | - fi |
382 | - |
383 | - app_port=`config-get app_port` |
384 | - open-port $app_port/TCP |
385 | -} |
386 | -configure_app |
387 | - |
388 | -juju-log "(re)starting app" |
389 | -service ${app_name} restart || service ${app_name} start |
390 | - |
391 | |
392 | === added file 'hooks/mongodb-relation-departed' |
393 | --- hooks/mongodb-relation-departed 1970-01-01 00:00:00 +0000 |
394 | +++ hooks/mongodb-relation-departed 2014-01-24 18:05:39 +0000 |
395 | @@ -0,0 +1,14 @@ |
396 | +#!/usr/bin/env node |
397 | + |
398 | +var app = require('../lib/app')(); |
399 | +var charm = require('../lib/charm')(); |
400 | +var util = require('util'); |
401 | + |
402 | +charm.config_get_all(function(config) { |
403 | + app.exec('service', [config.app_name, 'status'], function(ret) { |
404 | + app.exec('service', [config.app_name, 'stop'], function(ret) { |
405 | + charm.close_port([config.app_port + '/TCP']); |
406 | + charm.log(util.format('%s stopped successfully.', config.app_name)); |
407 | + }); |
408 | + }); |
409 | +}); |
410 | |
411 | === removed file 'hooks/mongodb-relation-departed' |
412 | --- hooks/mongodb-relation-departed 2011-12-03 00:08:39 +0000 |
413 | +++ hooks/mongodb-relation-departed 1970-01-01 00:00:00 +0000 |
414 | @@ -1,4 +0,0 @@ |
415 | -#!/bin/sh |
416 | - |
417 | -app_name=`config-get app_name` |
418 | -service ${app_name} status && service ${app_name} stop |
419 | |
420 | === modified file 'hooks/start' |
421 | --- hooks/start 2011-09-07 12:30:23 +0000 |
422 | +++ hooks/start 2014-01-24 18:05:39 +0000 |
423 | @@ -1,3 +1,9 @@ |
424 | -#!/bin/bash |
425 | - |
426 | -# delay startup until mongo joins |
427 | +#!/usr/bin/env node |
428 | + |
429 | +var charm = require('../lib/charm')(); |
430 | + |
431 | +charm.config_get_all(function(config) { |
432 | + // delay start until mongo joins |
433 | + // app.exec('service', [config.app_name, 'start'], function(ret) { |
434 | + //}); |
435 | +}); |
436 | \ No newline at end of file |
437 | |
438 | === added file 'hooks/stop' |
439 | --- hooks/stop 1970-01-01 00:00:00 +0000 |
440 | +++ hooks/stop 2014-01-24 18:05:39 +0000 |
441 | @@ -0,0 +1,14 @@ |
442 | +#!/usr/bin/env node |
443 | + |
444 | +var app = require('../lib/app')(); |
445 | +var charm = require('../lib/charm')(); |
446 | +var util = require('util'); |
447 | + |
448 | +charm.config_get_all(function(config) { |
449 | + app.exec('service', [config.app_name, 'status'], function(ret) { |
450 | + app.exec('service', [config.app_name, 'stop'], function(ret) { |
451 | + charm.close_port([config.app_port + '/TCP']); |
452 | + charm.log(util.format('%s stopped successfully.', config.app_name)); |
453 | + }); |
454 | + }); |
455 | +}); |
456 | \ No newline at end of file |
457 | |
458 | === removed file 'hooks/stop' |
459 | --- hooks/stop 2011-12-03 00:08:39 +0000 |
460 | +++ hooks/stop 1970-01-01 00:00:00 +0000 |
461 | @@ -1,5 +0,0 @@ |
462 | -#!/bin/bash |
463 | - |
464 | -app_name=`config-get app_name` |
465 | -service ${app_name} status && service ${app_name} stop |
466 | - |
467 | |
468 | === removed symlink 'hooks/upgrade-charm' |
469 | === target was u'install' |
470 | === added file 'hooks/website-relation-changed' |
471 | --- hooks/website-relation-changed 1970-01-01 00:00:00 +0000 |
472 | +++ hooks/website-relation-changed 2014-01-24 18:05:39 +0000 |
473 | @@ -0,0 +1,14 @@ |
474 | +#!/usr/bin/env node |
475 | + |
476 | +var charm = require('../lib/charm')(); |
477 | +var util = require('util'); |
478 | + |
479 | +charm.config_get_all(function(config) { |
480 | + charm.unit_get('private-address', function(hostname) { |
481 | + charm.relation_set({'port' : config.app_port, 'hostname' : hostname}, function() { |
482 | + // relation successfully changed |
483 | + charm.log(util.format('%s website relation successfully set up.', config.app_name)); |
484 | + }); |
485 | + }); |
486 | +}); |
487 | + |
488 | |
489 | === removed file 'hooks/website-relation-changed' |
490 | --- hooks/website-relation-changed 2012-02-09 21:37:03 +0000 |
491 | +++ hooks/website-relation-changed 1970-01-01 00:00:00 +0000 |
492 | @@ -1,4 +0,0 @@ |
493 | -#!/bin/sh |
494 | - |
495 | -relation-set port=`config-get app_port` hostname=`unit-get private-address` |
496 | - |
497 | |
498 | === modified file 'icon.svg' |
499 | --- icon.svg 2013-09-18 14:44:20 +0000 |
500 | +++ icon.svg 2014-01-24 18:05:39 +0000 |
501 | @@ -7,17 +7,27 @@ |
502 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" |
503 | xmlns:svg="http://www.w3.org/2000/svg" |
504 | xmlns="http://www.w3.org/2000/svg" |
505 | - xmlns:xlink="http://www.w3.org/1999/xlink" |
506 | xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" |
507 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" |
508 | width="96" |
509 | height="96" |
510 | id="svg6517" |
511 | version="1.1" |
512 | - inkscape:version="0.48+devel r12505" |
513 | - sodipodi:docname="nodejs.svg"> |
514 | + inkscape:version="0.48.2 r9819" |
515 | + sodipodi:docname="icon.svg"> |
516 | <defs |
517 | id="defs6519"> |
518 | + <linearGradient |
519 | + id="Background"> |
520 | + <stop |
521 | + id="stop4178" |
522 | + offset="0" |
523 | + style="stop-color:#b8b8b8;stop-opacity:1" /> |
524 | + <stop |
525 | + id="stop4180" |
526 | + offset="1" |
527 | + style="stop-color:#c9c9c9;stop-opacity:1" /> |
528 | + </linearGradient> |
529 | <filter |
530 | style="color-interpolation-filters:sRGB;" |
531 | inkscape:label="Inner Shadow" |
532 | @@ -82,29 +92,6 @@ |
533 | result="composite2" |
534 | id="feComposite960" /> |
535 | </filter> |
536 | - <linearGradient |
537 | - inkscape:collect="always" |
538 | - id="linearGradient902"> |
539 | - <stop |
540 | - style="stop-color:#74b524;stop-opacity:1" |
541 | - offset="0" |
542 | - id="stop904" /> |
543 | - <stop |
544 | - style="stop-color:#83cd29;stop-opacity:1" |
545 | - offset="1" |
546 | - id="stop906" /> |
547 | - </linearGradient> |
548 | - <linearGradient |
549 | - id="Background"> |
550 | - <stop |
551 | - id="stop4178" |
552 | - offset="0" |
553 | - style="stop-color:#22779e;stop-opacity:1" /> |
554 | - <stop |
555 | - id="stop4180" |
556 | - offset="1" |
557 | - style="stop-color:#2991c0;stop-opacity:1" /> |
558 | - </linearGradient> |
559 | <clipPath |
560 | clipPathUnits="userSpaceOnUse" |
561 | id="clipPath873"> |
562 | @@ -130,38 +117,6 @@ |
563 | stdDeviation="0.71999962" |
564 | id="feGaussianBlur893" /> |
565 | </filter> |
566 | - <style |
567 | - id="style867" |
568 | - type="text/css"><![CDATA[ |
569 | - .fil0 {fill:#1F1A17} |
570 | - ]]></style> |
571 | - <linearGradient |
572 | - inkscape:collect="always" |
573 | - xlink:href="#linearGradient902" |
574 | - id="linearGradient908" |
575 | - x1="-220" |
576 | - y1="731.29077" |
577 | - x2="-220" |
578 | - y2="635.29077" |
579 | - gradientUnits="userSpaceOnUse" /> |
580 | - <clipPath |
581 | - id="clipPath16"> |
582 | - <path |
583 | - id="path18" |
584 | - d="m -9,-9 614,0 0,231 -614,0 0,-231 z" /> |
585 | - </clipPath> |
586 | - <clipPath |
587 | - id="clipPath116"> |
588 | - <path |
589 | - id="path118" |
590 | - d="m 91.7368,146.3253 -9.7039,-1.577 -8.8548,-3.8814 -7.5206,-4.7308 -7.1566,-8.7335 -4.0431,-4.282 -3.9093,-1.4409 -1.034,2.5271 1.8079,2.6096 0.4062,3.6802 1.211,-0.0488 1.3232,-1.2069 -0.3569,3.7488 -1.4667,0.9839 0.0445,1.4286 -3.4744,-1.9655 -3.1462,-3.712 -0.6559,-3.3176 1.3453,-2.6567 1.2549,-4.5133 2.5521,-1.2084 2.6847,0.1318 2.5455,1.4791 -1.698,-8.6122 1.698,-9.5825 -1.8692,-4.4246 -6.1223,-6.5965 1.0885,-3.941 2.9002,-4.5669 5.4688,-3.8486 2.9007,-0.3969 3.225,-0.1094 -2.012,-8.2601 7.3993,-3.0326 9.2188,-1.2129 3.1535,2.0619 0.2427,5.5797 3.5178,5.8224 0.2426,4.6094 8.4909,-0.6066 7.8843,0.7279 -7.8843,-4.7307 1.3343,-5.701 4.9731,-7.763 4.8521,-2.0622 3.8814,1.5769 1.577,3.1538 8.1269,6.1861 1.5769,-1.3343 12.7363,-0.485 2.5473,2.0619 0.2426,3.6391 -0.849,1.5767 -0.6066,9.8251 -4.2454,8.4909 0.7276,3.7605 2.5475,-1.3343 7.1566,-6.6716 3.5175,-0.2424 3.8815,1.5769 3.8818,2.9109 1.9406,6.3077 11.4021,-0.7277 6.914,2.6686 5.5797,5.2157 4.0028,7.5206 0.9706,8.8546 -0.8493,10.3105 -2.1832,9.2185 -2.1836,2.9112 -3.0322,0.9706 -5.3373,-5.8224 -4.8518,-1.6982 -4.2455,7.0353 -4.2454,3.8815 -2.3049,1.4556 -9.2185,7.6419 -7.3993,4.0028 -7.3993,0.6066 -8.6119,-1.4556 -7.5206,-2.7899 -5.2158,-4.2454 -4.1241,-4.9734 -4.2454,-1.2129" /> |
591 | - </clipPath> |
592 | - <clipPath |
593 | - id="clipPath128"> |
594 | - <path |
595 | - id="path130" |
596 | - d="m 91.7368,146.3253 -9.7039,-1.577 -8.8548,-3.8814 -7.5206,-4.7308 -7.1566,-8.7335 -4.0431,-4.282 -3.9093,-1.4409 -1.034,2.5271 1.8079,2.6096 0.4062,3.6802 1.211,-0.0488 1.3232,-1.2069 -0.3569,3.7488 -1.4667,0.9839 0.0445,1.4286 -3.4744,-1.9655 -3.1462,-3.712 -0.6559,-3.3176 1.3453,-2.6567 1.2549,-4.5133 2.5521,-1.2084 2.6847,0.1318 2.5455,1.4791 -1.698,-8.6122 1.698,-9.5825 -1.8692,-4.4246 -6.1223,-6.5965 1.0885,-3.941 2.9002,-4.5669 5.4688,-3.8486 2.9007,-0.3969 3.225,-0.1094 -2.012,-8.2601 7.3993,-3.0326 9.2188,-1.2129 3.1535,2.0619 0.2427,5.5797 3.5178,5.8224 0.2426,4.6094 8.4909,-0.6066 7.8843,0.7279 -7.8843,-4.7307 1.3343,-5.701 4.9731,-7.763 4.8521,-2.0622 3.8814,1.5769 1.577,3.1538 8.1269,6.1861 1.5769,-1.3343 12.7363,-0.485 2.5473,2.0619 0.2426,3.6391 -0.849,1.5767 -0.6066,9.8251 -4.2454,8.4909 0.7276,3.7605 2.5475,-1.3343 7.1566,-6.6716 3.5175,-0.2424 3.8815,1.5769 3.8818,2.9109 1.9406,6.3077 11.4021,-0.7277 6.914,2.6686 5.5797,5.2157 4.0028,7.5206 0.9706,8.8546 -0.8493,10.3105 -2.1832,9.2185 -2.1836,2.9112 -3.0322,0.9706 -5.3373,-5.8224 -4.8518,-1.6982 -4.2455,7.0353 -4.2454,3.8815 -2.3049,1.4556 -9.2185,7.6419 -7.3993,4.0028 -7.3993,0.6066 -8.6119,-1.4556 -7.5206,-2.7899 -5.2158,-4.2454 -4.1241,-4.9734 -4.2454,-1.2129" /> |
597 | - </clipPath> |
598 | </defs> |
599 | <sodipodi:namedview |
600 | id="base" |
601 | @@ -170,38 +125,25 @@ |
602 | borderopacity="1.0" |
603 | inkscape:pageopacity="0.0" |
604 | inkscape:pageshadow="2" |
605 | - inkscape:zoom="3.2596289" |
606 | - inkscape:cx="62.012793" |
607 | - inkscape:cy="45.550063" |
608 | + inkscape:zoom="4.0745362" |
609 | + inkscape:cx="43.548195" |
610 | + inkscape:cy="43.426863" |
611 | inkscape:document-units="px" |
612 | - inkscape:current-layer="layer1" |
613 | + inkscape:current-layer="layer3" |
614 | showgrid="true" |
615 | fit-margin-top="0" |
616 | fit-margin-left="0" |
617 | fit-margin-right="0" |
618 | fit-margin-bottom="0" |
619 | - inkscape:window-width="1920" |
620 | - inkscape:window-height="1029" |
621 | + inkscape:window-width="1280" |
622 | + inkscape:window-height="687" |
623 | inkscape:window-x="0" |
624 | - inkscape:window-y="24" |
625 | + inkscape:window-y="0" |
626 | inkscape:window-maximized="1" |
627 | showborder="true" |
628 | showguides="true" |
629 | inkscape:guide-bbox="true" |
630 | - inkscape:showpageshadow="false" |
631 | - inkscape:snap-global="true" |
632 | - inkscape:snap-bbox="true" |
633 | - inkscape:bbox-paths="true" |
634 | - inkscape:bbox-nodes="true" |
635 | - inkscape:snap-bbox-edge-midpoints="true" |
636 | - inkscape:snap-bbox-midpoints="true" |
637 | - inkscape:object-paths="true" |
638 | - inkscape:snap-intersection-paths="true" |
639 | - inkscape:object-nodes="true" |
640 | - inkscape:snap-smooth-nodes="true" |
641 | - inkscape:snap-midpoints="true" |
642 | - inkscape:snap-object-midpoints="true" |
643 | - inkscape:snap-center="true"> |
644 | + inkscape:showpageshadow="false"> |
645 | <inkscape:grid |
646 | type="xygrid" |
647 | id="grid821" /> |
648 | @@ -230,7 +172,7 @@ |
649 | <dc:format>image/svg+xml</dc:format> |
650 | <dc:type |
651 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> |
652 | - <dc:title></dc:title> |
653 | + <dc:title /> |
654 | </cc:Work> |
655 | </rdf:RDF> |
656 | </metadata> |
657 | @@ -239,29 +181,27 @@ |
658 | inkscape:groupmode="layer" |
659 | id="layer1" |
660 | transform="translate(268,-635.29076)" |
661 | - style="display:inline"> |
662 | + style="display:inline" |
663 | + sodipodi:insensitive="true"> |
664 | <path |
665 | - style="fill:url(#linearGradient908);fill-opacity:1;stroke:none;display:inline;filter:url(#filter1121)" |
666 | - d="m -268,700.15563 0,-33.72973 c 0,-27.24324 3.88785,-31.13513 31.10302,-31.13513 l 33.79408,0 c 27.21507,0 31.1029,3.89189 31.1029,31.13513 l 0,33.72973 c 0,27.24325 -3.88783,31.13514 -31.1029,31.13514 l -33.79408,0 C -264.11215,731.29077 -268,727.39888 -268,700.15563 Z" |
667 | + style="fill:#82cd29;fill-opacity:1;stroke:none;display:inline;filter:url(#filter1121)" |
668 | + d="m -268,700.15563 0,-33.72973 c 0,-27.24324 3.88785,-31.13513 31.10302,-31.13513 l 33.79408,0 c 27.21507,0 31.1029,3.89189 31.1029,31.13513 l 0,33.72973 c 0,27.24325 -3.88783,31.13514 -31.1029,31.13514 l -33.79408,0 c -27.21517,0 -31.10302,-3.89189 -31.10302,-31.13514 z" |
669 | id="path6455" |
670 | inkscape:connector-curvature="0" |
671 | sodipodi:nodetypes="sssssssss" /> |
672 | - <path |
673 | - style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none" |
674 | - d="m -220,651.29076 c -0.86298,0 -1.70107,0.17841 -2.44333,0.59855 l -23.49563,13.56741 c -1.52044,0.87539 -2.44457,2.53071 -2.44457,4.29059 l 0,27.0869 c 0,1.75892 0.92413,3.41126 2.44457,4.28936 l 6.13539,3.54211 c 2.98102,1.46918 4.08046,1.44658 5.43728,1.44658 4.41367,0 6.93426,-2.67777 6.93426,-7.33247 l 0,-26.73786 c 0,-0.37799 -0.32696,-0.64892 -0.69809,-0.64892 l -2.94356,0 c -0.37712,0 -0.6981,0.27093 -0.6981,0.64892 l 0,26.73786 c 0,2.06316 -2.14855,4.13777 -5.63639,2.39418 l -6.38611,-3.74122 c -0.22547,-0.12291 -0.39821,-0.33979 -0.39821,-0.59854 l 0,-27.0869 c 0,-0.25716 0.17073,-0.51883 0.39821,-0.64893 l 23.44524,-13.51826 c 0.21948,-0.12609 0.48183,-0.12609 0.69932,0 l 23.44401,13.51826 c 0.22348,0.13408 0.39945,0.38395 0.39945,0.64893 l 0,27.0869 c 0,0.25875 -0.12965,0.52304 -0.34906,0.6477 l -23.4944,13.51825 c -0.20153,0.11972 -0.47984,0.11972 -0.69932,0 l -6.03584,-3.59126 c -0.17957,-0.10535 -0.42494,-0.0974 -0.59854,0 -1.6661,0.94451 -1.97855,1.05459 -3.54088,1.59653 -0.38509,0.13408 -0.97015,0.34304 0.19911,0.99798 l 7.88185,4.63841 c 0.75024,0.43418 1.58535,0.64894 2.44333,0.64894 0.86996,0 1.69435,-0.21476 2.44458,-0.64894 l 23.49564,-13.51825 c 1.52044,-0.88434 2.44333,-2.53044 2.44333,-4.28936 l 0,-27.0869 c 0,-1.75988 -0.92289,-3.41153 -2.44333,-4.29059 l -23.49564,-13.56741 c -0.73627,-0.42014 -1.58159,-0.59855 -2.44458,-0.59855 z m 6.28534,19.35499 c -6.69034,0 -10.67425,2.84698 -10.67425,7.58196 0,5.13662 3.95462,6.54723 10.37559,7.18254 7.68201,0.752 8.28007,1.88002 8.28007,3.39216 0,2.62281 -2.09017,3.7412 -7.03259,3.7412 -6.20947,0 -7.57488,-1.54948 -8.03181,-4.6384 -0.0538,-0.33123 -0.30973,-0.59854 -0.64893,-0.59854 l -3.04188,0 c -0.37512,0 -0.69932,0.32232 -0.69932,0.69809 0,3.95393 2.15198,8.63034 12.42194,8.63034 7.43458,0 11.72261,-2.91881 11.72261,-8.0318 0,-5.06877 -3.46436,-6.42942 -10.67547,-7.38286 -7.28694,-0.96414 -7.98141,-1.43611 -7.98141,-3.14267 0,-1.4087 0.58808,-3.2926 5.98545,-3.2926 4.8207,0 6.60341,1.04011 7.3337,4.29058 0.0639,0.30553 0.33245,0.54816 0.64771,0.54816 l 3.0431,0 c 0.18756,0 0.3693,-0.11493 0.49899,-0.2495 0.1277,-0.14365 0.21708,-0.30462 0.19911,-0.49776 -0.47089,-5.59793 -4.20423,-8.2309 -11.72261,-8.2309 z" |
675 | - id="path4116" |
676 | - inkscape:connector-curvature="0" /> |
677 | - <path |
678 | - style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none" |
679 | - d="m -193.22175,651.29076 0,0.83723 2.33334,0 0,6.16277 0.95662,0 0,-6.16277 2.39377,0 0,-0.83723 -5.68373,0 z m 6.52096,0 0,7 0.89766,0 0,-4.12866 c 0,-0.16846 0.008,-0.42818 0,-0.77679 -0.0115,-0.35513 0,-0.66564 0,-0.83871 l 0,-0.17982 1.97368,5.92251 0.95662,0 2.03411,-5.92251 c 0,0.37675 -0.0496,0.74113 -0.0589,1.07748 -0.004,0.32736 0,0.57311 0,0.71784 l 0,4.12719 0.89766,0 0,-6.99853 -1.31628,0 -2.03411,5.92251 -1.97515,-5.92251 -1.37523,0 z" |
680 | - id="path4114" |
681 | - inkscape:connector-curvature="0" /> |
682 | </g> |
683 | <g |
684 | inkscape:groupmode="layer" |
685 | id="layer3" |
686 | inkscape:label="PLACE YOUR PICTOGRAM HERE" |
687 | - style="display:inline" /> |
688 | + style="display:inline"> |
689 | + <path |
690 | + inkscape:connector-curvature="0" |
691 | + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none" |
692 | + id="path28" |
693 | + d="m 47.833258,16.541698 c -0.870556,0 -1.716941,0.1807 -2.465712,0.606108 L 21.666509,30.886221 c -1.533772,0.88637 -2.465712,2.561812 -2.465712,4.343768 l 0,27.426403 c 0,1.780987 0.93194,3.454484 2.465712,4.343608 l 6.189443,3.586212 c 3.007161,1.487625 4.116233,1.464674 5.484953,1.464674 4.452374,0 6.994572,-2.711642 6.994572,-7.424728 l 0,-27.07276 c 0,-0.382737 -0.330104,-0.656698 -0.704489,-0.656698 l -2.968916,0 c -0.380429,0 -0.704496,0.273961 -0.704496,0.656698 l 0,27.07276 c 0,2.089049 -2.167811,4.189899 -5.686232,2.424426 l -6.441042,-3.788085 c -0.227451,-0.12445 -0.40257,-0.344106 -0.40257,-0.606107 l 0,-27.426403 c 0,-0.260384 0.173107,-0.524807 0.40257,-0.656534 L 47.481013,20.885466 c 0.221408,-0.127688 0.485087,-0.127688 0.704489,0 l 23.650711,13.687989 c 0.225439,0.135767 0.402564,0.38823 0.402564,0.656534 l 0,27.426403 c 0,0.262001 -0.130912,0.530305 -0.352245,0.656539 l -23.70103,13.68799 c -0.203297,0.121221 -0.483081,0.121221 -0.704489,0 L 41.39221,73.364278 c -0.181156,-0.106675 -0.42873,-0.0986 -0.60385,0 -1.680706,0.956357 -1.996723,1.067554 -3.572764,1.616285 -0.388473,0.135768 -0.978236,0.347019 0.201284,1.010179 l 7.950666,4.697247 c 0.756821,0.439624 1.600195,0.656534 2.465712,0.656534 0.877589,0 1.708891,-0.21691 2.465711,-0.656534 L 74,67 c 1.533777,-0.895425 2.465712,-2.562621 2.465712,-4.343608 l 0,-27.426403 c 0,-1.781956 -0.931935,-3.453676 -2.465712,-4.343768 L 50.298969,17.147806 C 49.556234,16.722398 48.703813,16.541698 47.833258,16.541698 z M 54.17366,36.13947 c -6.74901,0 -10.768622,2.882968 -10.768622,7.677349 0,5.201047 3.989422,6.630001 10.466697,7.273281 7.749386,0.761436 8.353229,1.903501 8.353229,3.434606 0,2.655717 -2.109443,3.788084 -7.09521,3.788084 -6.263916,0 -7.640692,-1.56957 -8.101631,-4.697241 -0.05428,-0.335384 -0.311987,-0.606108 -0.654164,-0.606108 l -3.069561,0 c -0.37841,0 -0.704489,0.326648 -0.704489,0.707125 0,4.003534 2.169824,8.738119 12.529845,8.738119 7.499794,0 11.825355,-2.954892 11.825355,-8.132011 0,-5.132353 -3.494268,-6.509914 -10.768621,-7.47532 -7.350847,-0.976236 -8.051307,-1.454007 -8.051307,-3.181975 0,-1.426374 0.593782,-3.333749 6.038479,-3.333749 4.862987,0 6.660444,1.052684 7.397136,4.343927 0.06441,0.309356 0.336146,0.555511 0.654168,0.555511 l 3.069561,0 c 0.189206,0 0.372374,-0.116372 0.503211,-0.252622 0.128817,-0.145465 0.219395,-0.309357 0.201279,-0.504927 C 65.523989,38.805369 61.757989,36.13947 54.17366,36.13947 z" |
694 | + sodipodi:nodetypes="sccssccsssssssccssccccssccccccccsccssccssscsscssssscsscssccs" /> |
695 | + </g> |
696 | <g |
697 | inkscape:groupmode="layer" |
698 | id="layer2" |
699 | @@ -282,7 +222,7 @@ |
700 | style="opacity:0.6;filter:url(#filter891)"> |
701 | <path |
702 | transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)" |
703 | - d="m 264,552.36218 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 6.62742,0 12,5.37259 12,12 z" |
704 | + d="m 264,552.36218 a 12,12 0 1 1 -24,0 12,12 0 1 1 24,0 z" |
705 | sodipodi:ry="12" |
706 | sodipodi:rx="12" |
707 | sodipodi:cy="552.36218" |
708 | @@ -301,11 +241,11 @@ |
709 | sodipodi:cy="552.36218" |
710 | sodipodi:rx="12" |
711 | sodipodi:ry="12" |
712 | - d="m 264,552.36218 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 6.62742,0 12,5.37259 12,12 z" |
713 | + d="m 264,552.36218 a 12,12 0 1 1 -24,0 12,12 0 1 1 24,0 z" |
714 | transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" /> |
715 | <path |
716 | transform="matrix(1.25,0,0,1.25,33,-100.45273)" |
717 | - d="m 264,552.36218 c 0,6.62742 -5.37258,12 -12,12 -6.62742,0 -12,-5.37258 -12,-12 0,-6.62741 5.37258,-12 12,-12 6.62742,0 12,5.37259 12,12 z" |
718 | + d="m 264,552.36218 a 12,12 0 1 1 -24,0 12,12 0 1 1 24,0 z" |
719 | sodipodi:ry="12" |
720 | sodipodi:rx="12" |
721 | sodipodi:cy="552.36218" |
722 | |
723 | === added directory 'lib' |
724 | === added file 'lib/app.js' |
725 | --- lib/app.js 1970-01-01 00:00:00 +0000 |
726 | +++ lib/app.js 2014-01-24 18:05:39 +0000 |
727 | @@ -0,0 +1,435 @@ |
728 | +var execFile = require('child_process').execFile; |
729 | +var util = require('util'); |
730 | +var vm = require('vm'); |
731 | +var fs = require('fs'); |
732 | +var path = require('path'); |
733 | +var uidNumber = require('uid-number'); |
734 | +var mustache = require('mustache'); |
735 | +var vasync = require('vasync'); |
736 | +var charm = require('./charm')(); |
737 | + |
738 | +module.exports = App; |
739 | + |
740 | +function App(options) { |
741 | + if (!(this instanceof App)) return new App(options); |
742 | + |
743 | + if (options == null) { |
744 | + options = {}; |
745 | + } |
746 | + |
747 | + // assuming charm.js is in lib/charm.js the config file defaults to config/config.js |
748 | + this.CONFIG_FILE = options.config || path.join(__dirname, '..', 'config', 'config.js'); |
749 | +} |
750 | + |
751 | +function loadTemplate(template) { |
752 | + return fs.readFileSync(path.join(__dirname, '..', 'tpl', template)) + ''; |
753 | +} |
754 | + |
755 | +App.prototype.exec = function(cmd, args, cb) { |
756 | + var self = this; |
757 | + execFile(cmd, args, function(err, stdout, stderr) { |
758 | + if (err) { |
759 | + charm.log(util.format('failed to run: %s %j %j', cmd, args, stderr)); |
760 | + process.exit(1); |
761 | + return; |
762 | + } |
763 | + if (typeof cb != 'undefined') { |
764 | + cb(stdout ? stdout.trim() : ''); |
765 | + } |
766 | + }); |
767 | +}; |
768 | + |
769 | +App.prototype.configUpdate = function(section, value, cb) { |
770 | + var self = this; |
771 | + fs.readFile(this.CONFIG_FILE, 'utf8', function(err, value) { |
772 | + if (err) { |
773 | + charm.log(util.format('Failed to read config file %s, trying to create it...', this.CONFIG_FILE)); |
774 | + // anything else to do? will writefile create file? |
775 | + } |
776 | + var config = vm.runInNewContext('(function(){ return ' + value + ' })()'); |
777 | + var s = config[section] || {}; |
778 | + util._extend(s, value); |
779 | + config[section] = s; |
780 | + // write back to the config file |
781 | + fs.writeFile(this.CONFIG_FILE, config, function(err) { |
782 | + if (err) { |
783 | + charm.log(util.format('Failed to write config file %s: %j', this.CONFIG_FILE, err)); |
784 | + process.exit(1); |
785 | + return; |
786 | + } |
787 | + }) |
788 | + }); |
789 | +}; |
790 | + |
791 | +App.prototype.installPackage = function(pkg, cb) { |
792 | + var self = this; |
793 | + // check if already installed |
794 | + self.exec('apt-cache',['policy', pkg], function(resp){ |
795 | + // if not installed, install package |
796 | + if (resp.indexOf('Installed: (none)') != -1) { |
797 | + charm.log(util.format('Installing %s...', pkg)); |
798 | + self.exec('apt-get', ['-y', 'install', '-qq', pkg], function(err) { |
799 | + if (err) { |
800 | + charm.log(util.format('Failed to install %s: %j', pkg, err)); |
801 | + process.exit(1); |
802 | + return; |
803 | + } |
804 | + }); |
805 | + } |
806 | + else { |
807 | + charm.log(util.format('%s is already installed, nothing to do.', pkg)); |
808 | + } |
809 | + }); |
810 | + cb(); |
811 | +} |
812 | + |
813 | +App.prototype.installApp = function(config, cb) { |
814 | + var self = this; |
815 | + var appDir = config.install_root + '/' + config.app_name; |
816 | + // check if already installed |
817 | + fs.exists(appDir, function(exists) { |
818 | + if(!exists) { |
819 | + // install from repository |
820 | + var command = ''; |
821 | + var args= []; |
822 | + switch(config.app_scm) { |
823 | + case 'subversion': |
824 | + command = 'svn'; |
825 | + args.push('checkout', config.app_url, appDir); |
826 | + break; |
827 | + case 'mercurial': |
828 | + command = 'hg'; |
829 | + args.push('clone', config.app_url, '-r' , config.app_branch, appDir); |
830 | + break; |
831 | + case 'bzr': |
832 | + command = 'bzr'; |
833 | + args.push('checkout', config.app_url, '-r' , config.app_branch, appDir); |
834 | + break; |
835 | + default: |
836 | + command = 'git'; |
837 | + args.push('clone', config.app_url, '-b' , config.app_branch, appDir); |
838 | + break; |
839 | + } |
840 | + self.exec(command, args, function() { |
841 | + uidNumber(config.app_user, config.app_user, function (err, uid, gid) { |
842 | + if (err) { |
843 | + charm.log(util.format('Failed to get uid:gid for %s:%s: %j', config.app_user, config.app_user, err)); |
844 | + process.exit(1); |
845 | + return; |
846 | + } |
847 | + else { |
848 | + fs.chown(appDir, uid, gid, function(err) { |
849 | + if (err) { |
850 | + charm.log(util.format('Failed to change ownership of %s to %s: %j', appDir, config.app_user, err)); |
851 | + process.exit(1); |
852 | + return; |
853 | + } |
854 | + else { |
855 | + vasync.pipeline({ |
856 | + funcs: [ |
857 | + function installNpmModules(args, cb) { |
858 | + var self = args[1]; |
859 | + var appDir = args[0].install_root + '/' + args[0].app_name; |
860 | + // install module dependencies, if any |
861 | + var dependencies = appDir + '/package.json'; |
862 | + fs.exists(dependencies, function (exists) { |
863 | + if (exists) { |
864 | + process.chdir(appDir); |
865 | + self.exec('npm', ['install']); |
866 | + } |
867 | + }); |
868 | + cb(); |
869 | + }, |
870 | + function createAppService(args, cb) { |
871 | + var self = args[1]; |
872 | + var appDir = args[0].install_root + '/' + args[0].app_name; |
873 | + var procFile = appDir + '/Procfile'; |
874 | + fs.exists(procFile, function (exists) { |
875 | + if (exists) { |
876 | + self.exec('npm', ['install', 'foreman']); |
877 | + } |
878 | + else { |
879 | + var output = mustache.render(loadTemplate('upstart.tpl'), args[0]); |
880 | + var upstartFile = '/etc/init/' + args[0].app_name + '.conf' |
881 | + fs.writeFile(upstartFile, output, function (err) { |
882 | + if (err) { |
883 | + charm.log(util.format('Failed to write upstart file %s: %j', upstartFile, err)); |
884 | + process.exit(1); |
885 | + return; |
886 | + } |
887 | + }); |
888 | + } |
889 | + }); |
890 | + cb(); |
891 | + } |
892 | + ], |
893 | + arg: [ |
894 | + config, |
895 | + self |
896 | + ] |
897 | + }, function(err) { |
898 | + if (err) { |
899 | + charm.log(util.format('An error occurred: %j', err)); |
900 | + process.exit(1); |
901 | + return; |
902 | + } |
903 | + }); |
904 | + } |
905 | + }); |
906 | + } |
907 | + }); |
908 | + }); |
909 | + } |
910 | + }); |
911 | + cb(); |
912 | +} |
913 | + |
914 | +App.prototype.updateApp = function(config, cb) { |
915 | + var self = this; |
916 | + var appDir = config.install_root + '/' + config.app_name; |
917 | + |
918 | + process.chdir(appDir); |
919 | + |
920 | + // update from repository |
921 | + var command = ''; |
922 | + var args= []; |
923 | + switch(config.app_scm) { |
924 | + case 'subversion': |
925 | + command = 'svn'; |
926 | + args.push('update') |
927 | + break; |
928 | + case 'mercurial': |
929 | + command = 'hg'; |
930 | + args.push('merge') |
931 | + break; |
932 | + case 'bzr': |
933 | + command = 'bzr'; |
934 | + args.push('merge') |
935 | + break; |
936 | + default: |
937 | + command = 'git'; |
938 | + args.push('checkout', config.app_branch); |
939 | + break; |
940 | + }; |
941 | + self.exec(command, args, function(err) { |
942 | + if (err) { |
943 | + charm.log(util.format('Failed to update %s from %s: %j', config.app_name, config.app_url, err)); |
944 | + process.exit(1); |
945 | + return; |
946 | + } |
947 | + }); |
948 | + cb(); |
949 | +} |
950 | + |
951 | +App.prototype.configureApp = function(config, cb) { |
952 | + var self = this; |
953 | + var configFile = path.join(__dirname, '..', 'config', 'config.js'); |
954 | + var appDir = config.install_root + '/' + config.app_name; |
955 | + var mongodbConfigFile = '/etc/juju_nodejs_app_' + config.app_name + '_mongodb.env'; |
956 | + var mongodbConfig = ""; |
957 | + |
958 | + charm.log(util.format('Configuring %s...', config.app_name)); |
959 | + |
960 | + var procFile = appDir + '/Procfile'; |
961 | + fs.exists(procFile, function(exists) { |
962 | + if(exists) { |
963 | + charm.log(util.format('Using Procfile to configure %s using foreman...', config.app_name)); |
964 | + |
965 | + fs.exists(mongodbConfigFile, function(exists) { |
966 | + if(exists) { |
967 | + fs.readFile(mongodbConfigFile, function (err, data) { |
968 | + if (err) { |
969 | + charm.log(util.format('Failed to read config file %s: %j', mongodbConfigFile, err)); |
970 | + process.exit(1); |
971 | + return; |
972 | + } |
973 | + mongodbConfig = data; |
974 | + }); |
975 | + } |
976 | + else { |
977 | + var output = mustache.render(loadTemplate('mongodb_env.tpl'), config); |
978 | + fs.writeFile(mongodbConfigFile, output, function (err) { |
979 | + if (err) { |
980 | + charm.log(util.format('Failed to write config file %s: %j', mongodbConfigFile, err)); |
981 | + process.exit(1); |
982 | + return; |
983 | + } |
984 | + }); |
985 | + } |
986 | + }); |
987 | + |
988 | + process.chdir(appDir); |
989 | + var args = ['export', '-j', appDir + '/Procfile', '-e', mongodbConfigFile, '-a', config.app_name, '-u', config.app_user, '-p', config.app_port, '-o', '/etc/init', '-t', 'upstart']; |
990 | + self.exec('nf', args, function(err) { |
991 | + if (err) { |
992 | + charm.log(util.format('Failed to execute foreman: %j', err)); |
993 | + process.exit(1); |
994 | + return; |
995 | + } |
996 | + }); |
997 | + } |
998 | + else { |
999 | + charm.log(util.format('Writing %s config file %s', config.app_name, configFile)); |
1000 | + fs.exists(configFile, function(exists) { |
1001 | + if(exists) { |
1002 | + var data = fs.readFileSync(configFile, {encoding:'utf8'}); |
1003 | + if (data == null) { |
1004 | + charm.log(util.format('Couldn\'t read config file %s: %j', configFile, err)); |
1005 | + process.exit(1); |
1006 | + return; |
1007 | + } |
1008 | + |
1009 | + var result = data.replace(/name.*/g, 'name : "' + config.app_name + '"'); |
1010 | + result = result.replace(/listen_port.*/g, 'listen_port : "' + config.app_port + '"'); |
1011 | + |
1012 | + fs.writeFile(configFile, result, 'utf8', function (err) { |
1013 | + if (err) { |
1014 | + charm.log(util.format('Couldn\'t write config file %s: %j', configFile, err)); |
1015 | + process.exit(1); |
1016 | + return; |
1017 | + } |
1018 | + }); |
1019 | + } |
1020 | + else { |
1021 | + charm.log(util.format('Config file %s doesn\'t exist!', configFile)); |
1022 | + process.exit(1); |
1023 | + return; |
1024 | + } |
1025 | + }); |
1026 | + } |
1027 | + }); |
1028 | + |
1029 | + // cron schedule??? |
1030 | + |
1031 | + vasync.pipeline({ |
1032 | + funcs: [ |
1033 | + function installNpmModules(args, cb) { |
1034 | + var self = args[1]; |
1035 | + var appDir = args[0].install_root + '/' + args[0].app_name; |
1036 | + |
1037 | + self.exec('npm', ['update'], function() { |
1038 | + self.exec('npm', ['install'], function() { |
1039 | + }); |
1040 | + }); |
1041 | + cb(); |
1042 | + }, |
1043 | + function chownAndRestart(args, cb) { |
1044 | + var self = args[1]; |
1045 | + var config = args[0]; |
1046 | + var appDir = args[0].install_root + '/' + args[0].app_name; |
1047 | + |
1048 | + uidNumber(config.app_user, config.app_user, function (err, uid, gid) { |
1049 | + if (err) { |
1050 | + charm.log(util.format('Failed to get uid:gid for %s:%s: %j', config.app_user, config.app_user, err)); |
1051 | + process.exit(1); |
1052 | + return; |
1053 | + } |
1054 | + else { |
1055 | + fs.chown(appDir, uid, gid, function(err) { |
1056 | + if (err) { |
1057 | + charm.log(util.format('Failed to change ownership of %s to %s: %j', appDir, config.app_user, err)); |
1058 | + process.exit(1); |
1059 | + return; |
1060 | + } |
1061 | + else { |
1062 | + self.exec('service', [config.app_name, 'restart'], function(ret) { |
1063 | + if (ret < 0) { |
1064 | + charm.log(util.format('Failed to restart %s', config.app_name)); |
1065 | + } |
1066 | + else { |
1067 | + charm.log(util.format('%s restarted.', config.app_name)); |
1068 | + } |
1069 | + }); |
1070 | + } |
1071 | + }); |
1072 | + } |
1073 | + }); |
1074 | + cb(); |
1075 | + } |
1076 | + ], |
1077 | + arg: [ |
1078 | + config, |
1079 | + self |
1080 | + ] |
1081 | + }, function(err) { |
1082 | + if (err) { |
1083 | + charm.log(util.format('An error occurred: %j', err)); |
1084 | + process.exit(1); |
1085 | + return; |
1086 | + } |
1087 | + }); |
1088 | + |
1089 | + cb(); |
1090 | +} |
1091 | + |
1092 | +App.prototype.reconfigureApp = function(config, cb) { |
1093 | + var self = this; |
1094 | + var configFile = path.join(__dirname, '..', 'config', 'config.js'); |
1095 | + var appDir = config.install_root + '/' + config.app_name; |
1096 | + var mongodbConfigFile = '/etc/juju_nodejs_app_' + config.app_name + '_mongodb.env'; |
1097 | + |
1098 | + self.relation_get(['--format', 'json'], function(relation) { |
1099 | + var mongoNodeRelation = JSON.parse(relation); |
1100 | + if (mongoNodeRelation.hostname != ''){ |
1101 | + charm.log(util.format('Configuring %s to work with the mongodb service...', config.app_name)); |
1102 | + |
1103 | + var procFile = appDir + '/Procfile'; |
1104 | + fs.exists(procFile, function(exists) { |
1105 | + if(exists) { |
1106 | + var output = mustache.render(loadTemplate('mongodb_env.tpl'), config); |
1107 | + fs.writeFile(mongodbConfigFile, output, function (err) { |
1108 | + if (err) { |
1109 | + charm.log(util.format('Failed to write config file %s: %j', mongodbConfigFile, err)); |
1110 | + process.exit(1); |
1111 | + return; |
1112 | + } |
1113 | + }); |
1114 | + self.emit('config-changed', config); |
1115 | + } |
1116 | + else { |
1117 | + charm.log(util.format('Writing %s config file %s', config.app_name, configFile)); |
1118 | + fs.exists(configFile, function(exists) { |
1119 | + if(exists) { |
1120 | + var data = fs.readFileSync(configFile, {encoding:'utf8'}); |
1121 | + if (data == null) { |
1122 | + charm.log(util.format('Couldn\'t read config file %s: %j', configFile, err)); |
1123 | + process.exit(1); |
1124 | + return; |
1125 | + } |
1126 | + |
1127 | + var result = data.replace(/mongo_host.*/g, 'mongo_host : "' + mongoNodeRelation.hostname + '"'); |
1128 | + result = result.replace(/mongo_port.*/g, 'mongo_port : "' + mongoNodeRelation.port + '"'); |
1129 | + result = result.replace(/mongo_replset.*/g, 'mongo_replset : "' + mongoNodeRelation.replset + '"'); |
1130 | + |
1131 | + fs.writeFile(configFile, result, 'utf8', function (err) { |
1132 | + if (err) { |
1133 | + charm.log(util.format('Couldn\'t write config file %s: %j', configFile, err)); |
1134 | + process.exit(1); |
1135 | + return; |
1136 | + } |
1137 | + }); |
1138 | + } |
1139 | + else { |
1140 | + charm.log(util.format('Config file %s doesn\'t exist!', configFile)); |
1141 | + process.exit(1); |
1142 | + return; |
1143 | + } |
1144 | + }); |
1145 | + } |
1146 | + }); |
1147 | + |
1148 | + charm.open_port([config.app_port + '/TCP']); |
1149 | + |
1150 | + charm.log(util.format('Restarting %s...', config.app_name)) |
1151 | + self.exec('service', [config.app_name, 'restart'], function(ret) { |
1152 | + if (ret < 0) { |
1153 | + charm.log(util.format('Failed to restart %s', config.app_name)); |
1154 | + } |
1155 | + else { |
1156 | + charm.log(util.format('%s restarted.', config.app_name)); |
1157 | + } |
1158 | + }); |
1159 | + } |
1160 | + }); |
1161 | + cb(); |
1162 | +} |
1163 | |
1164 | === added file 'lib/charm.js' |
1165 | --- lib/charm.js 1970-01-01 00:00:00 +0000 |
1166 | +++ lib/charm.js 2014-01-24 18:05:39 +0000 |
1167 | @@ -0,0 +1,75 @@ |
1168 | +var execFile = require('child_process').execFile; |
1169 | +var EE = require('events').EventEmitter; |
1170 | +var util = require('util'); |
1171 | + |
1172 | +module.exports = Charm; |
1173 | + |
1174 | +function Charm() { |
1175 | + if (!(this instanceof Charm)) return new Charm(); |
1176 | + |
1177 | + // delay a tick so things can attach, maybe have a separate init that users pass in instead of this being magic on first require |
1178 | + var self = this; |
1179 | + process.nextTick(function() { |
1180 | + //argv[0] is node, argv[1] is script name, the rest will be args for the hook |
1181 | + var hook = process.argv[1]; |
1182 | + //if we have defined a prototype method for this call it instead. otherwise re-emit |
1183 | + if (self[hook]) |
1184 | + self[hook].apply(self, process.argv.slice(2)); |
1185 | + else |
1186 | + self.emit.apply(self, process.argv.slice(1)); |
1187 | + }); |
1188 | +} |
1189 | +util.inherits(Charm, EE); |
1190 | + |
1191 | +Charm.prototype.relation_get = function(key, cb) { |
1192 | + this.exec('relation-get', key, cb); |
1193 | +}; |
1194 | + |
1195 | +Charm.prototype.relation_set = function(map, cb) { |
1196 | + var params = []; |
1197 | + for (var key in map) { |
1198 | + params.push(key + '=' + map[key]) |
1199 | + } |
1200 | + this.exec('relation-set', params, cb); |
1201 | +}; |
1202 | + |
1203 | +Charm.prototype.log = function(msg, cb) { |
1204 | + this.exec('juju-log', [msg], cb); |
1205 | +}; |
1206 | + |
1207 | +Charm.prototype.config_get = function(key, cb) { |
1208 | + this.exec('config-get', [key], cb); |
1209 | +}; |
1210 | + |
1211 | +Charm.prototype.config_get_all = function(cb) { |
1212 | + this.exec('config-get', ['--all', '--format', 'json'], function(config) { |
1213 | + cb(JSON.parse(config)); |
1214 | + }); |
1215 | +}; |
1216 | + |
1217 | +Charm.prototype.unit_get = function(key, cb) { |
1218 | + this.exec('unit-get', [key], cb); |
1219 | +}; |
1220 | + |
1221 | +Charm.prototype.open_port = function(key, cb) { |
1222 | + this.exec('open-port', [key], cb); |
1223 | +}; |
1224 | + |
1225 | +Charm.prototype.close_port = function(key, cb) { |
1226 | + this.exec('close-port', [key], cb); |
1227 | +}; |
1228 | + |
1229 | +/* evil this exits like set -e does */ |
1230 | +Charm.prototype.exec = function(cmd, args, cb) { |
1231 | + var self = this; |
1232 | + execFile(cmd, args, function(err, stdout, stderr) { |
1233 | + if (err) { |
1234 | + self.log(util.format('failed to run: %s %j %j', cmd, args, stderr)); |
1235 | + process.exit(1); |
1236 | + return; |
1237 | + } |
1238 | + if (typeof cb != 'undefined') { |
1239 | + cb(stdout ? stdout.trim() : ''); |
1240 | + } |
1241 | + }); |
1242 | +}; |
1243 | \ No newline at end of file |
1244 | |
1245 | === modified file 'metadata.yaml' |
1246 | --- metadata.yaml 2013-07-11 19:24:26 +0000 |
1247 | +++ metadata.yaml 2014-01-24 18:05:39 +0000 |
1248 | @@ -3,7 +3,7 @@ |
1249 | description: "Deploy a user-defined node.js application" |
1250 | categories: |
1251 | - app-servers |
1252 | -maintainer: Mark Mims <mark.mims@canonical.com> |
1253 | +maintainer: Daniele Stroppa <daniele.stroppa@joyent.com> |
1254 | requires: |
1255 | mongodb: |
1256 | interface: mongodb |
1257 | |
1258 | === added directory 'tests' |
1259 | === added file 'tests/00-setup' |
1260 | --- tests/00-setup 1970-01-01 00:00:00 +0000 |
1261 | +++ tests/00-setup 2014-01-24 18:05:39 +0000 |
1262 | @@ -0,0 +1,5 @@ |
1263 | +#!/bin/bash |
1264 | + |
1265 | +add-apt-repository ppa:juju/stable |
1266 | +apt-get update |
1267 | +apt-get install amulet |
1268 | |
1269 | === added file 'tests/01-mongodb-deployment' |
1270 | --- tests/01-mongodb-deployment 1970-01-01 00:00:00 +0000 |
1271 | +++ tests/01-mongodb-deployment 2014-01-24 18:05:39 +0000 |
1272 | @@ -0,0 +1,31 @@ |
1273 | +#!/usr/bin/python3 |
1274 | + |
1275 | +import amulet |
1276 | + |
1277 | +d = amulet.Deployment() |
1278 | + |
1279 | +d.add('node-app', charm='lp:~dstroppa/charms/precise/node-app') |
1280 | +d.add('mongodb', charm='cs:precise/mongodb-20') |
1281 | + |
1282 | +d.relate('node-app:mongodb', 'mongodb:database') |
1283 | + |
1284 | +d.expose('node-app') |
1285 | + |
1286 | +try: |
1287 | + d.setup(timeout=900) |
1288 | + d.sentry.wait() |
1289 | +except amulet.helpers.TimeoutError: |
1290 | + amulet.raise_status(amulet.SKIP, msg="Environment wasn't stood up in time") |
1291 | +except: |
1292 | + raise |
1293 | + |
1294 | +# Now you can use d.sentry.unit[UNIT] to address each of the units and perform |
1295 | +# more in-depth steps. There are three test statuses: amulet.PASS, amulet.FAIL, |
1296 | +# and amulet.SKIP - these can be triggered with amulet.raise_status(). Each |
1297 | +# d.sentry.unit[] has the following methods: |
1298 | +# - .info - An array of the information of that unit from Juju |
1299 | +# - .file(PATH) - Get the details of a file on that unit |
1300 | +# - .file_contents(PATH) - Get plain text output of PATH file from that unit |
1301 | +# - .directory(PATH) - Get details of directory |
1302 | +# - .directory_contents(PATH) - List files and folders in PATH on that unit |
1303 | +# - .relation(relation, service:rel) - Get relation data from return service |
1304 | |
1305 | === added file 'tests/02-mongodb-haproxy-deployment' |
1306 | --- tests/02-mongodb-haproxy-deployment 1970-01-01 00:00:00 +0000 |
1307 | +++ tests/02-mongodb-haproxy-deployment 2014-01-24 18:05:39 +0000 |
1308 | @@ -0,0 +1,33 @@ |
1309 | +#!/usr/bin/python3 |
1310 | + |
1311 | +import amulet |
1312 | + |
1313 | +d = amulet.Deployment() |
1314 | + |
1315 | +d.add('node-app', charm='lp:~dstroppa/charms/precise/node-app') |
1316 | +d.add('haproxy', charm='cs:precise/haproxy-22') |
1317 | +d.add('mongodb', charm='cs:precise/mongodb-20') |
1318 | + |
1319 | +d.relate('node-app:website', 'haproxy:reverseproxy') |
1320 | +d.relate('node-app:mongodb', 'mongodb:database') |
1321 | + |
1322 | +d.expose('haproxy') |
1323 | + |
1324 | +try: |
1325 | + d.setup(timeout=900) |
1326 | + d.sentry.wait() |
1327 | +except amulet.helpers.TimeoutError: |
1328 | + amulet.raise_status(amulet.SKIP, msg="Environment wasn't stood up in time") |
1329 | +except: |
1330 | + raise |
1331 | + |
1332 | +# Now you can use d.sentry.unit[UNIT] to address each of the units and perform |
1333 | +# more in-depth steps. There are three test statuses: amulet.PASS, amulet.FAIL, |
1334 | +# and amulet.SKIP - these can be triggered with amulet.raise_status(). Each |
1335 | +# d.sentry.unit[] has the following methods: |
1336 | +# - .info - An array of the information of that unit from Juju |
1337 | +# - .file(PATH) - Get the details of a file on that unit |
1338 | +# - .file_contents(PATH) - Get plain text output of PATH file from that unit |
1339 | +# - .directory(PATH) - Get details of directory |
1340 | +# - .directory_contents(PATH) - List files and folders in PATH on that unit |
1341 | +# - .relation(relation, service:rel) - Get relation data from return service |
1342 | |
1343 | === added directory 'tpl' |
1344 | === added file 'tpl/mongodb_env.tpl' |
1345 | --- tpl/mongodb_env.tpl 1970-01-01 00:00:00 +0000 |
1346 | +++ tpl/mongodb_env.tpl 2014-01-24 18:05:39 +0000 |
1347 | @@ -0,0 +1,8 @@ |
1348 | +{ |
1349 | + "name": "{{appName}}" |
1350 | + , "node_env": "{{appNodeEnv}}" |
1351 | + , "mongo": {{mongdbConfig}} |
1352 | + , "host": "{{hostname}}" |
1353 | + , "port": {{port}} |
1354 | + , "replset": "{{replset}}" |
1355 | +} |
1356 | \ No newline at end of file |
1357 | |
1358 | === added file 'tpl/upstart.tpl' |
1359 | --- tpl/upstart.tpl 1970-01-01 00:00:00 +0000 |
1360 | +++ tpl/upstart.tpl 2014-01-24 18:05:39 +0000 |
1361 | @@ -0,0 +1,16 @@ |
1362 | +description "{{app_name}} node.js server" |
1363 | +author "Daniele Stroppa <daniele.stroppa@joyent.com>" |
1364 | + |
1365 | +start on (local-filesystems and net-device-up IFACE!=lo) |
1366 | +stop on runlevel [06] |
1367 | + |
1368 | +respawn |
1369 | +respawn limit 5 3 |
1370 | + |
1371 | +setuid {{app_user}} |
1372 | +setgid {{app_user}} |
1373 | + |
1374 | +env HOME="/home/{{app_user}}" |
1375 | +env NODE_PATH="/usr/lib/node" |
1376 | + |
1377 | +exec /usr/bin/node {{{install_root}}}/{{app_name}}/server.js >> /var/log/{{app_name}}.log 2>&1 |
# Review
- I don't understand what expose_app is for, how does this differ from juju expose node-app?
- Excellent use of app_user, however it's not being used in the upstart script. Consider using the following as a reference: https:/ /gist.github. com/marcoceppi/ 8458344
- I really like the way lib/charm.js is shaping up, I'm confident this will be a key part in helping other node.js authors write hooks/charms in node.js Along the same vein I think methods like installPackage, installApp, updateApp, configureApp, etc are fantastic, but don't seem inheriently a part of Charm itself. As an opinion it might be better served breaking the two apart. Charm to emulate all the core juju functions in node then a seperat App module with these methods.
- The charm needs more logging to juju-log, most of the comments you have in the final callbacks are great, sending them to juju-log will notify the user what's going on and help debug issues
- juju unexpose (and expose) aren't executed from the charm but instead by the user, as a result the mongodb- relations- departed and stop hooks need to be updated. Instead the charm needs to tell juju /what/ports to open by calling `open-port` and `close-port` These won't explicitly open or close the ports, but tells juju what ports to open in the firewall when the user run juju expose/unexpose.
# Knitpick
- I really like the idea of a polling_schedule, as an alternative it might be nice to have a push_url, where the user could specify a token as a configuration option, then have a url like http:// ip:2222/ update? key=<key-they-set> so they could have github push notifications on updates and other repositories instead of a constant polling. Just an idea.
- Having config/config.js where it is, I thought it was something the user filled out. I fear other users may also make this assumption. I'm not sure of a better place just making you aware of the perception.
- You'll want to update the maintainer field in the metadata.yaml file
- Again, not sure if this is a common practice in node, but relation_set seems to take a list of keys and values, would it be better served as an object of key: val pairs?
Sorry this review was so...breif...in words. I honestly love the direction of the charm so far and understand it's still under development. These were things that popped out at me doing a comb through the charm. Let me know if you have any question, happy to discuss these points further.
I'm going to move the merge request to Work In Progress, feel free to email me when you'd like another look or just move the merge to "Needs Review"