cf 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,91 @@
1
+ require "cf/cli/command"
2
+
3
+ module CF
4
+ class Service < Command
5
+ desc "delete", "Delete a service"
6
+ flag(:name) { |choices|
7
+ ask "Delete which service?", :choices => choices
8
+ }
9
+ def delete(name = nil)
10
+ name ||= input(:name, client.services.collect(&:name))
11
+
12
+ with_progress("Deleting #{c(name, :blue)}") do
13
+ client.service(name).delete!
14
+ end
15
+ end
16
+
17
+ desc "bind", "Bind a service to an application"
18
+ flag(:name) { |choices|
19
+ ask "Which service?", :choices => choices
20
+ }
21
+ flag(:app) { |choices|
22
+ ask "Which application?", :choices => choices
23
+ }
24
+ def bind(name = nil, appname = nil)
25
+ name ||= input(:name, client.services.collect(&:name))
26
+ appname ||= input(:app, client.apps.collect(&:name))
27
+
28
+ with_progress("Binding #{c(name, :blue)} to #{c(appname, :blue)}") do
29
+ client.app(appname).bind(name)
30
+ end
31
+ end
32
+
33
+ desc "unbind", "Unbind a service from an application"
34
+ flag(:name) { |choices|
35
+ ask "Which service?", :choices => choices
36
+ }
37
+ flag(:app) { |choices|
38
+ ask "Which application?", :choices => choices
39
+ }
40
+ def unbind(name = nil, appname = nil)
41
+ appname ||= input(:app, client.apps.collect(&:name))
42
+
43
+ app = client.app(appname)
44
+ name ||= input(:name, app.services)
45
+
46
+ with_progress("Unbinding #{c(name, :blue)} from #{c(appname, :blue)}") do
47
+ app.unbind(name)
48
+ end
49
+ end
50
+
51
+ desc "create", "Create a service"
52
+ flag(:type) { |choices|
53
+ ask "What kind?", :choices => choices
54
+ }
55
+ flag(:name) { |vendor|
56
+ random = sprintf("%x", rand(1000000))
57
+ ask "Name?", :default => "#{vendor}-#{random}"
58
+ }
59
+ def create
60
+ choices = []
61
+ manifests = {}
62
+ client.system_services.each do |type, vendors|
63
+ vendors.each do |vendor, versions|
64
+ versions.each do |version, _|
65
+ choice = "#{vendor} #{version}"
66
+ manifests[choice] = {
67
+ :type => type,
68
+ :vendor => vendor,
69
+ :version => version
70
+ }
71
+
72
+ choices << choice
73
+ end
74
+ end
75
+ end
76
+
77
+ type = input(:type, choices)
78
+ meta = manifests[type]
79
+
80
+ service = client.service(input(:name, meta[:vendor]))
81
+ service.type = meta[:type]
82
+ service.vendor = meta[:vendor]
83
+ service.version = meta[:version]
84
+ service.tier = "free"
85
+
86
+ with_progress("Creating service") do
87
+ service.create!
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,71 @@
1
+ require "cf/cli/command"
2
+
3
+ module CF
4
+ class User < Command
5
+ desc "create [EMAIL]", "Create a user"
6
+ flag(:email) {
7
+ ask("Email")
8
+ }
9
+ flag(:password) {
10
+ ask("Password", :echo => "*", :forget => true)
11
+ }
12
+ flag(:verify) {
13
+ ask("Verify Password", :echo => "*", :forget => true)
14
+ }
15
+ def create(email = nil)
16
+ email ||= input(:email)
17
+ password = input(:password)
18
+ verify = input(:verify)
19
+
20
+ if password != verify
21
+ err "Passwords don't match."
22
+ return
23
+ end
24
+
25
+ with_progress("Creating user") do
26
+ client.register(email, password)
27
+ end
28
+ end
29
+
30
+ desc "delete [EMAIL]", "Delete a user"
31
+ flag(:really) { |email|
32
+ force? || ask("Really delete user #{c(email, :blue)}?", :default => false)
33
+ }
34
+ def delete(email)
35
+ return unless input(:really, email)
36
+
37
+ with_progress("Deleting #{c(email, :blue)}") do
38
+ client.user(email).delete!
39
+ end
40
+ ensure
41
+ forget(:really)
42
+ end
43
+
44
+ desc "passwd [EMAIL]", "Update a user's password"
45
+ flag(:email) {
46
+ ask("Email")
47
+ }
48
+ flag(:password) {
49
+ ask("Password", :echo => "*", :forget => true)
50
+ }
51
+ flag(:verify) {
52
+ ask("Verify Password", :echo => "*", :forget => true)
53
+ }
54
+ def passwd(email = nil)
55
+ email ||= input(:email)
56
+ password = input(:password)
57
+ verify = input(:verify)
58
+
59
+ if password != verify
60
+ err "Passwords don't match."
61
+ return
62
+ end
63
+
64
+ with_progress("Changing password") do
65
+ user = client.user(email)
66
+ user.password = password
67
+ user.update!
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,10 @@
1
+ module CF
2
+ OLD_TARGET_FILE = "~/.vmc_target"
3
+ OLD_TOKENS_FILE = "~/.vmc_token"
4
+
5
+ CONFIG_DIR = "~/.cf"
6
+ PLUGINS_DIR = "#{CONFIG_DIR}/plugins"
7
+ TARGET_FILE = "#{CONFIG_DIR}/target"
8
+ TOKENS_FILE = "#{CONFIG_DIR}/tokens"
9
+ CRASH_FILE = "#{CONFIG_DIR}/crash"
10
+ end
@@ -0,0 +1,60 @@
1
+ module CF
2
+ class Detector
3
+ def initialize(client, path)
4
+ @client = client
5
+ @path = path
6
+ end
7
+
8
+ def all_frameworks
9
+ info = @client.info
10
+ info["frameworks"] || {}
11
+ end
12
+
13
+ def frameworks
14
+ info = @client.info
15
+
16
+ matches = {}
17
+ all_frameworks.each do |name, meta|
18
+ matched = false
19
+ meta["detection"].first.each do |file, match|
20
+ files =
21
+ if File.file? @path
22
+ if File.fnmatch(file, @path)
23
+ [@path]
24
+ else
25
+ []
26
+ end
27
+ else
28
+ Dir.glob("#@path/#{file}")
29
+ end
30
+
31
+ unless files.empty?
32
+ if match == true
33
+ matched = true
34
+ elsif match == false
35
+ matched = false
36
+ break
37
+ else
38
+ files.each do |f|
39
+ contents = File.open(f, &:read)
40
+ if contents =~ Regexp.new(match)
41
+ matched = true
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ if matched
49
+ matches[name] = meta
50
+ end
51
+ end
52
+
53
+ if matches.size == 1
54
+ default = matches.keys.first
55
+ end
56
+
57
+ [matches, default]
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,24 @@
1
+ require "cf/constants"
2
+ require "cf/cli"
3
+
4
+ module CF
5
+ module Plugin
6
+ @@plugins = []
7
+
8
+ def self.load_all
9
+ bundled = File.expand_path("../../../plugins/*/main.rb", __FILE__)
10
+ Dir.glob(bundled).each do |main|
11
+ require main
12
+ end
13
+
14
+ Dir.glob(File.expand_path("#{CF::PLUGINS_DIR}/*/main.rb")).each do |main|
15
+ require main
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.Plugin(target = CLI, &blk)
21
+ # SUPER FANCY PLUGIN SYSTEM
22
+ target.class_eval &blk
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module CF
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,508 @@
1
+ require "yaml"
2
+ require "set"
3
+ require "cf/plugin"
4
+
5
+ CF.Plugin do
6
+ class_option :manifest,
7
+ :aliases => "-m", :desc => "Manifest file"
8
+
9
+ class_option :path,
10
+ :aliases => "-p", :desc => "Application path"
11
+ end
12
+
13
+ module CF::Plugins
14
+ module Manifests
15
+ MANIFEST_FILE = "manifest.yml"
16
+
17
+ def manifest
18
+ return @manifest if @manifest
19
+
20
+ if manifest_file && File.exists?(manifest_file)
21
+ @manifest = load_manifest(manifest_file)
22
+ end
23
+ end
24
+
25
+ def save_manifest(save_to = manifest_file)
26
+ err "No manifest to save!" unless @manifest
27
+
28
+ File.open(save_to, "w") do |io|
29
+ YAML.dump(@manifest, io)
30
+ end
31
+ end
32
+
33
+ # find the manifest file to work with
34
+ def manifest_file
35
+ return options[:manifest] if options[:manifest]
36
+ return @manifest_file if @manifest_file
37
+
38
+ where = Dir.pwd
39
+ while true
40
+ if File.exists?(File.join(where, MANIFEST_FILE))
41
+ @manifest_file = File.join(where, MANIFEST_FILE)
42
+ break
43
+ elsif File.basename(where) == "/"
44
+ @manifest_file = nil
45
+ break
46
+ else
47
+ where = File.expand_path("../", where)
48
+ end
49
+ end
50
+
51
+ @manifest_file
52
+ end
53
+
54
+ # load and resolve a given manifest file
55
+ def load_manifest(file)
56
+ manifest = build_manifest(file)
57
+ resolve_manifest(manifest)
58
+ manifest
59
+ end
60
+
61
+ # parse a manifest and merge with its inherited manifests
62
+ def build_manifest(file)
63
+ manifest = YAML.load_file file
64
+
65
+ Array(manifest["inherit"]).each do |p|
66
+ manifest = merge_parent(manifest, p)
67
+ end
68
+
69
+ manifest
70
+ end
71
+
72
+ # merge the manifest at `path' into the `child'
73
+ def merge_parent(child, path)
74
+ file = File.expand_path("../" + path, manifest_file)
75
+ merge_manifest(child, build_manifest(file))
76
+ end
77
+
78
+ # deep hash merge
79
+ def merge_manifest(child, parent)
80
+ merge = proc do |_, old, new|
81
+ if new.is_a?(Hash) and old.is_a?(Hash)
82
+ old.merge(new, &merge)
83
+ else
84
+ new
85
+ end
86
+ end
87
+
88
+ parent.merge(child, &merge)
89
+ end
90
+
91
+ # resolve symbols in a manifest
92
+ def resolve_manifest(manifest)
93
+ if apps = manifest["applications"]
94
+ apps.each_value do |v|
95
+ resolve_lexically(v, [manifest])
96
+ end
97
+ end
98
+
99
+ resolve_lexically(manifest, [manifest])
100
+
101
+ nil
102
+ end
103
+
104
+ # resolve symbols, with hashes introducing new lexical symbols
105
+ def resolve_lexically(val, ctx)
106
+ case val
107
+ when Hash
108
+ val.each_value do |v|
109
+ resolve_lexically(v, [val] + ctx)
110
+ end
111
+ when Array
112
+ val.each do |v|
113
+ resolve_lexically(v, ctx)
114
+ end
115
+ when String
116
+ val.gsub!(/\$\{([[:alnum:]\-]+)\}/) do
117
+ resolve_symbol($1, ctx)
118
+ end
119
+ end
120
+
121
+ nil
122
+ end
123
+
124
+ # resolve a symbol to its value, and then resolve that value
125
+ def resolve_symbol(sym, ctx)
126
+ case sym
127
+ when "target-url"
128
+ target_url(ctx)
129
+
130
+ when "target-base"
131
+ target_url(ctx).sub(/^[^\.]+\./, "")
132
+
133
+ when "random-word"
134
+ "%04x" % [rand(0x0100000)]
135
+
136
+ else
137
+ found = find_symbol(sym, ctx)
138
+
139
+ if found
140
+ resolve_lexically(found, ctx)
141
+ found
142
+ else
143
+ err("Unknown symbol in manifest: #{sym}")
144
+ end
145
+ end
146
+ end
147
+
148
+ # get the target url from either the manifest or the current client
149
+ def target_url(ctx = [])
150
+ find_symbol("target", ctx) || client_target
151
+ end
152
+
153
+ # search for a symbol introduced in the lexical context
154
+ def find_symbol(sym, ctx)
155
+ ctx.each do |h|
156
+ if val = resolve_in(h, sym)
157
+ return val
158
+ end
159
+ end
160
+
161
+ nil
162
+ end
163
+
164
+ # find a value, searching in explicit properties first
165
+ def resolve_in(hash, *where)
166
+ find_in_hash(hash, ["properties"] + where) ||
167
+ find_in_hash(hash, where)
168
+ end
169
+
170
+ # helper for following a path of values in a hash
171
+ def find_in_hash(hash, where)
172
+ what = hash
173
+ where.each do |x|
174
+ return nil unless what.is_a?(Hash)
175
+ what = what[x]
176
+ end
177
+
178
+ what
179
+ end
180
+
181
+ MANIFEST_META = ["applications", "properties"]
182
+
183
+ def toplevel_attributes
184
+ if m = manifest
185
+ m.reject do |k, _|
186
+ MANIFEST_META.include? k
187
+ end
188
+ end
189
+ end
190
+
191
+ def app_info(find_path)
192
+ return unless manifest and manifest["applications"]
193
+
194
+ manifest["applications"].each do |path, info|
195
+ app = File.expand_path("../" + path, manifest_file)
196
+ if find_path == app
197
+ return toplevel_attributes.merge info
198
+ end
199
+ end
200
+
201
+ nil
202
+ end
203
+
204
+ # call a block for each app in a manifest (in dependency order), setting
205
+ # inputs for each app
206
+ def each_app
207
+ given_path = passed_value(:path)
208
+
209
+ if manifest and all_apps = manifest["applications"]
210
+ # given a specific application
211
+ if given_path
212
+ full_path = File.expand_path(given_path)
213
+
214
+ if info = app_info(full_path)
215
+ with_app(full_path, info) do
216
+ yield info
217
+ end
218
+ else
219
+ raise "Path #{given_path} is not described by the manifest."
220
+ end
221
+ else
222
+ # all apps in the manifest
223
+ ordered_by_deps(all_apps).each do |path|
224
+ app = File.expand_path("../" + path, manifest_file)
225
+ info = app_info(app)
226
+
227
+ with_app(app, info) do
228
+ yield info
229
+ end
230
+ end
231
+ end
232
+
233
+ true
234
+
235
+ # manually created or legacy single-app manifest
236
+ elsif single = toplevel_attributes
237
+ with_app(full_path || ".", single) do
238
+ yield single
239
+ end
240
+
241
+ true
242
+
243
+ else
244
+ false
245
+ end
246
+ end
247
+
248
+ private
249
+
250
+ def inputs
251
+ @inputs ||= {}
252
+ end
253
+
254
+ # call the block as if the app info and path were given as flags
255
+ def with_app(path, info)
256
+ before_path = inputs[:path]
257
+ before_info = {}
258
+
259
+ inputs[:path] = path
260
+
261
+ info.each do |k, v|
262
+ before_info[k.to_sym] = inputs[k.to_sym]
263
+ inputs[k.to_sym] = v
264
+ end
265
+
266
+ yield
267
+ ensure
268
+ if before_path.nil?
269
+ inputs.delete :path
270
+ else
271
+ inputs[:path] = before_path
272
+ end
273
+
274
+ before_info.each do |k, v|
275
+ if v.nil?
276
+ inputs.delete k
277
+ else
278
+ inputs[k] = v
279
+ end
280
+ end
281
+ end
282
+
283
+ # sort applications in dependency order
284
+ # e.g. if A depends on B, B will be listed before A
285
+ def ordered_by_deps(apps, abspaths = nil, processed = Set[])
286
+ unless abspaths
287
+ abspaths = {}
288
+ apps.each do |p, i|
289
+ ep = File.expand_path("../" + p, manifest_file)
290
+ abspaths[ep] = i
291
+ end
292
+ end
293
+
294
+ ordered = []
295
+ apps.each do |path, info|
296
+ epath = File.expand_path("../" + path, manifest_file)
297
+
298
+ if deps = info["depends-on"]
299
+ dep_apps = {}
300
+ deps.each do |dep|
301
+ edep = File.expand_path("../" + dep, manifest_file)
302
+
303
+ err "Circular dependency detected." if processed.include? edep
304
+
305
+ dep_apps[dep] = abspaths[edep]
306
+ end
307
+
308
+ processed.add(epath)
309
+
310
+ ordered += ordered_by_deps(dep_apps, abspaths, processed)
311
+ ordered << path
312
+ elsif not processed.include? epath
313
+ ordered << path
314
+ processed.add(epath)
315
+ end
316
+ end
317
+
318
+ ordered
319
+ end
320
+
321
+ # detect changes in app info, and update the app if necessary.
322
+ #
323
+ # redeploys the app if necessary (after prompting the user), e.g. for
324
+ # runtime/framework change
325
+ def sync_changes(info)
326
+ app = client.app(info["name"])
327
+ return unless app.exists?
328
+
329
+ diff = {}
330
+ need_restage = []
331
+ info.each do |k, v|
332
+ case k
333
+ when /ur[li]s?/
334
+ old = app.urls
335
+ if old != Array(v)
336
+ diff[k] = [old, v]
337
+ app.urls = Array(v)
338
+ end
339
+ when "env"
340
+ old = app.env
341
+ if old != v
342
+ diff[k] = [old, v]
343
+ app.env = v
344
+ end
345
+ when "framework", "runtime"
346
+ old = app.send(k)
347
+ if old != v
348
+ diff[k] = [old, v]
349
+ app.send(:"#{k}=", v)
350
+ need_restage << k
351
+ end
352
+ when "instances"
353
+ old = app.total_instances
354
+ if old != v
355
+ diff[k] = [old, v]
356
+ app.total_instances = v
357
+ end
358
+ when "mem", "memory"
359
+ old = app.memory
360
+ new = megabytes(v)
361
+ if old != new
362
+ diff[k] = [old, new]
363
+ app.memory = new
364
+ end
365
+ end
366
+ end
367
+
368
+ return if diff.empty?
369
+
370
+ unless simple_output?
371
+ puts "Detected the following changes to #{c(app.name, :blue)}:"
372
+ diff.each do |k, d|
373
+ old, new = d
374
+ label = c(k, need_restage.include?(k) ? :red : :green)
375
+ puts " #{label}: #{old.inspect} #{c("->", :black)} #{new.inspect}"
376
+ end
377
+
378
+ puts ""
379
+ end
380
+
381
+ if need_restage.empty?
382
+ with_progress("Updating #{c(app.name, :blue)}") do
383
+ app.update!
384
+ end
385
+ else
386
+ unless simple_output?
387
+ puts "The following changes require the app to be recreated:"
388
+ need_restage.each do |n|
389
+ puts " #{c(n, :magenta)}"
390
+ end
391
+ puts ""
392
+ end
393
+
394
+ if force? || ask("Redeploy?", :default => false)
395
+ with_progress("Deleting #{c(app.name, :blue)}") do
396
+ app.delete!
397
+ end
398
+
399
+ with_progress("Recreating #{c(app.name, :blue)}") do
400
+ app.create!
401
+ end
402
+ end
403
+ end
404
+ end
405
+ end
406
+ end
407
+
408
+ CF.Plugin(CF::App) do
409
+ include CF::Plugins::Manifests
410
+
411
+ # basic commands that, when given no args, act on the
412
+ # app(s) described by the manifest, in dependency-order
413
+ [:start, :instances, :logs].each do |wrap|
414
+ around(wrap) do |cmd, args|
415
+ if args.empty?
416
+ each_app do |a|
417
+ cmd.call(a["name"])
418
+ puts "" unless simple_output?
419
+ end || err("No applications to act on.")
420
+ else
421
+ cmd.call(args)
422
+ end
423
+ end
424
+ end
425
+
426
+ # same as above but in reverse dependency-order
427
+ [:stop, :delete].each do |wrap|
428
+ around(wrap) do |cmd, args|
429
+ if args.empty?
430
+ reversed = []
431
+ each_app do |a|
432
+ reversed.unshift a["name"]
433
+ end || err("No applications to act on.")
434
+
435
+ reversed.each do |name|
436
+ cmd.call(name)
437
+ puts "" unless simple_output?
438
+ end
439
+ else
440
+ cmd.call(args)
441
+ end
442
+ end
443
+ end
444
+
445
+ # stop apps in reverse dependency order,
446
+ # and then start in dependency order
447
+ around(:restart) do |cmd, args|
448
+ if args.empty?
449
+ reversed = []
450
+ forwards = []
451
+ each_app do |a|
452
+ reversed.unshift a["name"]
453
+ forwards << a["name"]
454
+ end || err("No applications to act on.")
455
+
456
+ reversed.each do |name|
457
+ stop(name)
458
+ end
459
+
460
+ puts "" unless simple_output?
461
+
462
+ forwards.each do |name|
463
+ start(name)
464
+ end
465
+ else
466
+ cmd.call(args)
467
+ end
468
+ end
469
+
470
+ # push and sync meta changes in the manifest
471
+ # also sets env data on creation if present in manifest
472
+ around(:push) do |push, args|
473
+ if args.empty?
474
+ all_pushed =
475
+ each_app do |a|
476
+ app = client.app(a["name"])
477
+ updating = app.exists?
478
+
479
+ start = input(:start)
480
+
481
+ begin
482
+ inputs[:start] = false
483
+
484
+ sync_changes(a)
485
+ push.call(a["name"])
486
+
487
+ unless updating
488
+ app.env = a["env"]
489
+
490
+ if start
491
+ start(a["name"])
492
+ else
493
+ app.update!
494
+ end
495
+ end
496
+ ensure
497
+ inputs[:start] = start
498
+ end
499
+
500
+ puts "" unless simple_output?
501
+ end
502
+
503
+ push.call unless all_pushed
504
+ else
505
+ push.call(args)
506
+ end
507
+ end
508
+ end