manifests-vmc-plugin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +30 -0
- data/Rakefile +1 -0
- data/lib/manifests-vmc-plugin.rb +410 -0
- data/lib/manifests-vmc-plugin/errors.rb +21 -0
- data/lib/manifests-vmc-plugin/manifest.rb +222 -0
- data/lib/manifests-vmc-plugin/plugin.rb +112 -0
- data/lib/manifests-vmc-plugin/version.rb +3 -0
- metadata +72 -0
data/LICENSE
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
Copyright (c)2012, Alex Suraci
|
2
|
+
|
3
|
+
All rights reserved.
|
4
|
+
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
7
|
+
|
8
|
+
* Redistributions of source code must retain the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer.
|
10
|
+
|
11
|
+
* Redistributions in binary form must reproduce the above
|
12
|
+
copyright notice, this list of conditions and the following
|
13
|
+
disclaimer in the documentation and/or other materials provided
|
14
|
+
with the distribution.
|
15
|
+
|
16
|
+
* Neither the name of Alex Suraci nor the names of other
|
17
|
+
contributors may be used to endorse or promote products derived
|
18
|
+
from this software without specific prior written permission.
|
19
|
+
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
21
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
22
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
23
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
24
|
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
25
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
26
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
27
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
28
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
29
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
30
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,410 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
module VMCManifests
|
5
|
+
MANIFEST_FILE = "manifest.yml"
|
6
|
+
|
7
|
+
def manifest
|
8
|
+
return @manifest if @manifest
|
9
|
+
|
10
|
+
if manifest_file && File.exists?(manifest_file)
|
11
|
+
@manifest = load_manifest(manifest_file)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def save_manifest(save_to = manifest_file)
|
16
|
+
err "No manifest to save!" unless @manifest
|
17
|
+
|
18
|
+
File.open(save_to, "w") do |io|
|
19
|
+
YAML.dump(@manifest, io)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# find the manifest file to work with
|
24
|
+
def manifest_file
|
25
|
+
return options[:manifest] if options[:manifest]
|
26
|
+
return @manifest_file if @manifest_file
|
27
|
+
|
28
|
+
where = Dir.pwd
|
29
|
+
while true
|
30
|
+
if File.exists?(File.join(where, MANIFEST_FILE))
|
31
|
+
@manifest_file = File.join(where, MANIFEST_FILE)
|
32
|
+
break
|
33
|
+
elsif File.basename(where) == "/"
|
34
|
+
@manifest_file = nil
|
35
|
+
break
|
36
|
+
else
|
37
|
+
where = File.expand_path("../", where)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
@manifest_file
|
42
|
+
end
|
43
|
+
|
44
|
+
# load and resolve a given manifest file
|
45
|
+
def load_manifest(file)
|
46
|
+
manifest = build_manifest(file)
|
47
|
+
resolve_manifest(manifest)
|
48
|
+
manifest
|
49
|
+
end
|
50
|
+
|
51
|
+
# parse a manifest and merge with its inherited manifests
|
52
|
+
def build_manifest(file)
|
53
|
+
manifest = YAML.load_file file
|
54
|
+
|
55
|
+
Array(manifest["inherit"]).each do |p|
|
56
|
+
manifest = merge_parent(manifest, p)
|
57
|
+
end
|
58
|
+
|
59
|
+
manifest
|
60
|
+
end
|
61
|
+
|
62
|
+
# merge the manifest at `path' into the `child'
|
63
|
+
def merge_parent(child, path)
|
64
|
+
file = File.expand_path("../" + path, manifest_file)
|
65
|
+
merge_manifest(child, build_manifest(file))
|
66
|
+
end
|
67
|
+
|
68
|
+
# deep hash merge
|
69
|
+
def merge_manifest(child, parent)
|
70
|
+
merge = proc do |_, old, new|
|
71
|
+
if new.is_a?(Hash) and old.is_a?(Hash)
|
72
|
+
old.merge(new, &merge)
|
73
|
+
else
|
74
|
+
new
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
parent.merge(child, &merge)
|
79
|
+
end
|
80
|
+
|
81
|
+
# resolve symbols in a manifest
|
82
|
+
def resolve_manifest(manifest)
|
83
|
+
if apps = manifest["applications"]
|
84
|
+
apps.each_value do |v|
|
85
|
+
resolve_lexically(v, [manifest])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
resolve_lexically(manifest, [manifest])
|
90
|
+
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
|
94
|
+
# resolve symbols, with hashes introducing new lexical symbols
|
95
|
+
def resolve_lexically(val, ctx)
|
96
|
+
case val
|
97
|
+
when Hash
|
98
|
+
val.each_value do |v|
|
99
|
+
resolve_lexically(v, [val] + ctx)
|
100
|
+
end
|
101
|
+
when Array
|
102
|
+
val.each do |v|
|
103
|
+
resolve_lexically(v, ctx)
|
104
|
+
end
|
105
|
+
when String
|
106
|
+
val.gsub!(/\$\{([[:alnum:]\-]+)\}/) do
|
107
|
+
resolve_symbol($1, ctx)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
# resolve a symbol to its value, and then resolve that value
|
115
|
+
def resolve_symbol(sym, ctx)
|
116
|
+
case sym
|
117
|
+
when "target-url"
|
118
|
+
target_url(ctx)
|
119
|
+
|
120
|
+
when "target-base"
|
121
|
+
target_url(ctx).sub(/^[^\.]+\./, "")
|
122
|
+
|
123
|
+
when "random-word"
|
124
|
+
"%04x" % [rand(0x0100000)]
|
125
|
+
|
126
|
+
else
|
127
|
+
found = find_symbol(sym, ctx)
|
128
|
+
|
129
|
+
if found
|
130
|
+
resolve_lexically(found, ctx)
|
131
|
+
found
|
132
|
+
else
|
133
|
+
err("Unknown symbol in manifest: #{sym}")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# get the target url from either the manifest or the current client
|
139
|
+
def target_url(ctx = [])
|
140
|
+
find_symbol("target", ctx) || client_target
|
141
|
+
end
|
142
|
+
|
143
|
+
# search for a symbol introduced in the lexical context
|
144
|
+
def find_symbol(sym, ctx)
|
145
|
+
ctx.each do |h|
|
146
|
+
if val = resolve_in(h, sym)
|
147
|
+
return val
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
|
154
|
+
# find a value, searching in explicit properties first
|
155
|
+
def resolve_in(hash, *where)
|
156
|
+
find_in_hash(hash, ["properties"] + where) ||
|
157
|
+
find_in_hash(hash, where)
|
158
|
+
end
|
159
|
+
|
160
|
+
# helper for following a path of values in a hash
|
161
|
+
def find_in_hash(hash, where)
|
162
|
+
what = hash
|
163
|
+
where.each do |x|
|
164
|
+
return nil unless what.is_a?(Hash)
|
165
|
+
what = what[x]
|
166
|
+
end
|
167
|
+
|
168
|
+
what
|
169
|
+
end
|
170
|
+
|
171
|
+
MANIFEST_META = ["applications", "properties"]
|
172
|
+
|
173
|
+
def toplevel_attributes
|
174
|
+
if m = manifest
|
175
|
+
info =
|
176
|
+
m.reject do |k, _|
|
177
|
+
MANIFEST_META.include? k
|
178
|
+
end
|
179
|
+
|
180
|
+
if info["framework"].is_a?(Hash)
|
181
|
+
info["framework"] = info["framework"]["name"]
|
182
|
+
end
|
183
|
+
|
184
|
+
info
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def app_info(find_path)
|
189
|
+
return unless manifest and manifest["applications"]
|
190
|
+
|
191
|
+
manifest["applications"].each do |path, info|
|
192
|
+
if info["framework"].is_a?(Hash)
|
193
|
+
info["framework"] = info["framework"]["name"]
|
194
|
+
end
|
195
|
+
|
196
|
+
app = File.expand_path("../" + path, manifest_file)
|
197
|
+
if find_path == app
|
198
|
+
return toplevel_attributes.merge info
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
nil
|
203
|
+
end
|
204
|
+
|
205
|
+
# call a block for each app in a manifest (in dependency order), setting
|
206
|
+
# inputs for each app
|
207
|
+
def each_app
|
208
|
+
given_path = passed_value(:path)
|
209
|
+
|
210
|
+
if manifest and all_apps = manifest["applications"]
|
211
|
+
# given a specific application
|
212
|
+
if given_path
|
213
|
+
full_path = File.expand_path(given_path)
|
214
|
+
|
215
|
+
if info = app_info(full_path)
|
216
|
+
with_app(full_path, info) do
|
217
|
+
yield info
|
218
|
+
end
|
219
|
+
else
|
220
|
+
raise "Path #{given_path} is not described by the manifest."
|
221
|
+
end
|
222
|
+
else
|
223
|
+
# all apps in the manifest
|
224
|
+
ordered_by_deps(all_apps).each do |path|
|
225
|
+
app = File.expand_path("../" + path, manifest_file)
|
226
|
+
info = app_info(app)
|
227
|
+
|
228
|
+
with_app(app, info) do
|
229
|
+
yield info
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
true
|
235
|
+
|
236
|
+
# manually created or legacy single-app manifest
|
237
|
+
elsif single = toplevel_attributes
|
238
|
+
with_app(full_path || ".", single) do
|
239
|
+
yield single
|
240
|
+
end
|
241
|
+
|
242
|
+
true
|
243
|
+
|
244
|
+
else
|
245
|
+
false
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
private
|
250
|
+
|
251
|
+
def inputs
|
252
|
+
@inputs ||= {}
|
253
|
+
end
|
254
|
+
|
255
|
+
# call the block as if the app info and path were given as flags
|
256
|
+
def with_app(path, info)
|
257
|
+
before_path = inputs[:path]
|
258
|
+
before_info = {}
|
259
|
+
|
260
|
+
inputs[:path] = path
|
261
|
+
|
262
|
+
info.each do |k, v|
|
263
|
+
if k == "mem"
|
264
|
+
k = "memory"
|
265
|
+
end
|
266
|
+
|
267
|
+
before_info[k.to_sym] = inputs[k.to_sym]
|
268
|
+
inputs[k.to_sym] = v
|
269
|
+
end
|
270
|
+
|
271
|
+
yield
|
272
|
+
ensure
|
273
|
+
if before_path.nil?
|
274
|
+
inputs.delete :path
|
275
|
+
else
|
276
|
+
inputs[:path] = before_path
|
277
|
+
end
|
278
|
+
|
279
|
+
before_info.each do |k, v|
|
280
|
+
if v.nil?
|
281
|
+
inputs.delete k
|
282
|
+
else
|
283
|
+
inputs[k] = v
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# sort applications in dependency order
|
289
|
+
# e.g. if A depends on B, B will be listed before A
|
290
|
+
def ordered_by_deps(apps, abspaths = nil, processed = Set[])
|
291
|
+
unless abspaths
|
292
|
+
abspaths = {}
|
293
|
+
apps.each do |p, i|
|
294
|
+
ep = File.expand_path("../" + p, manifest_file)
|
295
|
+
abspaths[ep] = i
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
ordered = []
|
300
|
+
apps.each do |path, info|
|
301
|
+
epath = File.expand_path("../" + path, manifest_file)
|
302
|
+
|
303
|
+
if deps = info["depends-on"]
|
304
|
+
dep_apps = {}
|
305
|
+
deps.each do |dep|
|
306
|
+
edep = File.expand_path("../" + dep, manifest_file)
|
307
|
+
|
308
|
+
err "Circular dependency detected." if processed.include? edep
|
309
|
+
|
310
|
+
dep_apps[dep] = abspaths[edep]
|
311
|
+
end
|
312
|
+
|
313
|
+
processed.add(epath)
|
314
|
+
|
315
|
+
ordered += ordered_by_deps(dep_apps, abspaths, processed)
|
316
|
+
ordered << path
|
317
|
+
elsif not processed.include? epath
|
318
|
+
ordered << path
|
319
|
+
processed.add(epath)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
ordered
|
324
|
+
end
|
325
|
+
|
326
|
+
# detect changes in app info, and update the app if necessary.
|
327
|
+
#
|
328
|
+
# redeploys the app if necessary (after prompting the user), e.g. for
|
329
|
+
# runtime/framework change
|
330
|
+
def sync_changes(info)
|
331
|
+
app = client.app(info["name"])
|
332
|
+
return unless app.exists?
|
333
|
+
|
334
|
+
diff = {}
|
335
|
+
need_restage = []
|
336
|
+
info.each do |k, v|
|
337
|
+
case k
|
338
|
+
when /ur[li]s?/
|
339
|
+
old = app.urls
|
340
|
+
if old != Array(v)
|
341
|
+
diff[k] = [old, v]
|
342
|
+
app.urls = Array(v)
|
343
|
+
end
|
344
|
+
when "env"
|
345
|
+
old = app.env
|
346
|
+
if old != v
|
347
|
+
diff[k] = [old, v]
|
348
|
+
app.env = v
|
349
|
+
end
|
350
|
+
when "framework", "runtime"
|
351
|
+
old = app.send(k)
|
352
|
+
if old != v
|
353
|
+
diff[k] = [old, v]
|
354
|
+
app.send(:"#{k}=", v)
|
355
|
+
need_restage << k
|
356
|
+
end
|
357
|
+
when "instances"
|
358
|
+
old = app.total_instances
|
359
|
+
if old != v
|
360
|
+
diff[k] = [old, v]
|
361
|
+
app.total_instances = v
|
362
|
+
end
|
363
|
+
when "mem", "memory"
|
364
|
+
old = app.memory
|
365
|
+
new = megabytes(v)
|
366
|
+
if old != new
|
367
|
+
diff["memory"] = [old, new]
|
368
|
+
app.memory = new
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
return if diff.empty?
|
374
|
+
|
375
|
+
unless simple_output?
|
376
|
+
puts "Detected the following changes to #{c(app.name, :blue)}:"
|
377
|
+
diff.each do |k, d|
|
378
|
+
old, new = d
|
379
|
+
label = c(k, need_restage.include?(k) ? :red : :green)
|
380
|
+
puts " #{label}: #{old.inspect} #{c("->", :black)} #{new.inspect}"
|
381
|
+
end
|
382
|
+
|
383
|
+
puts ""
|
384
|
+
end
|
385
|
+
|
386
|
+
if need_restage.empty?
|
387
|
+
with_progress("Updating #{c(app.name, :blue)}") do
|
388
|
+
app.update!
|
389
|
+
end
|
390
|
+
else
|
391
|
+
unless simple_output?
|
392
|
+
puts "The following changes require the app to be recreated:"
|
393
|
+
need_restage.each do |n|
|
394
|
+
puts " #{c(n, :magenta)}"
|
395
|
+
end
|
396
|
+
puts ""
|
397
|
+
end
|
398
|
+
|
399
|
+
if force? || ask("Redeploy?", :default => false)
|
400
|
+
with_progress("Deleting #{c(app.name, :blue)}") do
|
401
|
+
app.delete!
|
402
|
+
end
|
403
|
+
|
404
|
+
with_progress("Recreating #{c(app.name, :blue)}") do
|
405
|
+
app.create!
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module VMCManifests
|
2
|
+
class CircularDependency < RuntimeError
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
"Circular dependency in application '#@app'"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class UnknownSymbol < RuntimeError
|
13
|
+
def initialize(sym)
|
14
|
+
@sym = sym
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"Undefined symbol in manifest: '#@sym'"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
module VMCManifests
|
5
|
+
class Manifest
|
6
|
+
def initialize(file)
|
7
|
+
@file = file
|
8
|
+
end
|
9
|
+
|
10
|
+
def body
|
11
|
+
@body ||= load
|
12
|
+
end
|
13
|
+
|
14
|
+
# load and resolve a given manifest file
|
15
|
+
def load
|
16
|
+
manifest = build_manifest(@file)
|
17
|
+
resolve_manifest(manifest)
|
18
|
+
manifest
|
19
|
+
end
|
20
|
+
|
21
|
+
def save(dest = @file)
|
22
|
+
File.open(save_to, "w") do |io|
|
23
|
+
YAML.dump(@body, io)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
MANIFEST_META = ["applications", "properties"]
|
28
|
+
|
29
|
+
def toplevel_attributes
|
30
|
+
info =
|
31
|
+
body.reject do |k, _|
|
32
|
+
MANIFEST_META.include? k
|
33
|
+
end
|
34
|
+
|
35
|
+
if info["framework"].is_a?(Hash)
|
36
|
+
info["framework"] = info["framework"]["name"]
|
37
|
+
end
|
38
|
+
|
39
|
+
info
|
40
|
+
end
|
41
|
+
|
42
|
+
def app_info(find_path)
|
43
|
+
return unless body["applications"]
|
44
|
+
|
45
|
+
body["applications"].each do |path, info|
|
46
|
+
if info["framework"].is_a?(Hash)
|
47
|
+
info["framework"] = info["framework"]["name"]
|
48
|
+
end
|
49
|
+
|
50
|
+
app = File.expand_path("../" + path, manifest_file)
|
51
|
+
if find_path == app
|
52
|
+
return toplevel_attributes.merge info
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
# sort applications in dependency order
|
60
|
+
# e.g. if A depends on B, B will be listed before A
|
61
|
+
def applications(
|
62
|
+
apps = body["applications"],
|
63
|
+
abspaths = nil,
|
64
|
+
processed = Set[])
|
65
|
+
unless abspaths
|
66
|
+
abspaths = {}
|
67
|
+
apps.each do |p, i|
|
68
|
+
ep = File.expand_path("../" + p, manifest_file)
|
69
|
+
abspaths[ep] = i
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
ordered = []
|
74
|
+
apps.each do |path, info|
|
75
|
+
epath = File.expand_path("../" + path, manifest_file)
|
76
|
+
|
77
|
+
if deps = info["depends-on"]
|
78
|
+
dep_apps = {}
|
79
|
+
deps.each do |dep|
|
80
|
+
edep = File.expand_path("../" + dep, manifest_file)
|
81
|
+
|
82
|
+
raise CircularDependency.new(edep) if processed.include?(edep)
|
83
|
+
|
84
|
+
dep_apps[dep] = abspaths[edep]
|
85
|
+
end
|
86
|
+
|
87
|
+
processed.add(epath)
|
88
|
+
|
89
|
+
ordered += applications(dep_apps, abspaths, processed)
|
90
|
+
ordered << path
|
91
|
+
elsif not processed.include? epath
|
92
|
+
ordered << path
|
93
|
+
processed.add(epath)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
ordered
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
# parse a manifest and merge with its inherited manifests
|
103
|
+
def build_manifest(file)
|
104
|
+
manifest = YAML.load_file file
|
105
|
+
|
106
|
+
Array(manifest["inherit"]).each do |p|
|
107
|
+
manifest = merge_parent(manifest, p)
|
108
|
+
end
|
109
|
+
|
110
|
+
manifest
|
111
|
+
end
|
112
|
+
|
113
|
+
# merge the manifest at `path' into the `child'
|
114
|
+
def merge_parent(child, path)
|
115
|
+
file = File.expand_path("../" + path, @file)
|
116
|
+
deep_merge(child, build_manifest(file))
|
117
|
+
end
|
118
|
+
|
119
|
+
# resolve symbols in a manifest
|
120
|
+
def resolve_manifest(manifest)
|
121
|
+
if apps = manifest["applications"]
|
122
|
+
apps.each_value do |v|
|
123
|
+
resolve_lexically(v, [manifest])
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
resolve_lexically(manifest, [manifest])
|
128
|
+
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
# resolve symbols, with hashes introducing new lexical symbols
|
133
|
+
def resolve_lexically(val, ctx)
|
134
|
+
case val
|
135
|
+
when Hash
|
136
|
+
val.each_value do |v|
|
137
|
+
resolve_lexically(v, [val] + ctx)
|
138
|
+
end
|
139
|
+
when Array
|
140
|
+
val.each do |v|
|
141
|
+
resolve_lexically(v, ctx)
|
142
|
+
end
|
143
|
+
when String
|
144
|
+
val.gsub!(/\$\{([[:alnum:]\-]+)\}/) do
|
145
|
+
resolve_symbol($1, ctx)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
nil
|
150
|
+
end
|
151
|
+
|
152
|
+
# resolve a symbol to its value, and then resolve that value
|
153
|
+
def resolve_symbol(sym, ctx)
|
154
|
+
case sym
|
155
|
+
when "target-url"
|
156
|
+
target_url(ctx)
|
157
|
+
|
158
|
+
when "target-base"
|
159
|
+
target_url(ctx).sub(/^[^\.]+\./, "")
|
160
|
+
|
161
|
+
when "random-word"
|
162
|
+
"%04x" % [rand(0x0100000)]
|
163
|
+
|
164
|
+
else
|
165
|
+
found = find_symbol(sym, ctx)
|
166
|
+
|
167
|
+
if found
|
168
|
+
resolve_lexically(found, ctx)
|
169
|
+
found
|
170
|
+
else
|
171
|
+
raise UnknownSymbol.new(sym)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# get the target url from either the manifest or the current client
|
177
|
+
def target_url(ctx = [])
|
178
|
+
find_symbol("target", ctx) || client_target
|
179
|
+
end
|
180
|
+
|
181
|
+
# search for a symbol introduced in the lexical context
|
182
|
+
def find_symbol(sym, ctx)
|
183
|
+
ctx.each do |h|
|
184
|
+
if val = resolve_in(h, sym)
|
185
|
+
return val
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
nil
|
190
|
+
end
|
191
|
+
|
192
|
+
# find a value, searching in explicit properties first
|
193
|
+
def resolve_in(hash, *where)
|
194
|
+
find_in_hash(hash, ["properties"] + where) ||
|
195
|
+
find_in_hash(hash, where)
|
196
|
+
end
|
197
|
+
|
198
|
+
# helper for following a path of values in a hash
|
199
|
+
def find_in_hash(hash, where)
|
200
|
+
what = hash
|
201
|
+
where.each do |x|
|
202
|
+
return nil unless what.is_a?(Hash)
|
203
|
+
what = what[x]
|
204
|
+
end
|
205
|
+
|
206
|
+
what
|
207
|
+
end
|
208
|
+
|
209
|
+
# deep hash merge
|
210
|
+
def deep_merge(child, parent)
|
211
|
+
merge = proc do |_, old, new|
|
212
|
+
if new.is_a?(Hash) and old.is_a?(Hash)
|
213
|
+
old.merge(new, &merge)
|
214
|
+
else
|
215
|
+
new
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
parent.merge(child, &merge)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require "cf/plugin"
|
2
|
+
require File.expand_path("../../manifests-vmc-plugin", __FILE__)
|
3
|
+
|
4
|
+
CF.Plugin do
|
5
|
+
class_option :manifest,
|
6
|
+
:aliases => "-m", :desc => "Manifest file"
|
7
|
+
|
8
|
+
class_option :path,
|
9
|
+
:aliases => "-p", :desc => "Application path"
|
10
|
+
end
|
11
|
+
|
12
|
+
CF.Plugin(CF::App) do
|
13
|
+
include VMCManifests
|
14
|
+
|
15
|
+
# basic commands that, when given no args, act on the
|
16
|
+
# app(s) described by the manifest, in dependency-order
|
17
|
+
[:start, :instances, :logs].each do |wrap|
|
18
|
+
around(wrap) do |cmd, args|
|
19
|
+
if args.empty?
|
20
|
+
each_app do |a|
|
21
|
+
cmd.call(a["name"])
|
22
|
+
puts "" unless simple_output?
|
23
|
+
end || err("No applications to act on.")
|
24
|
+
else
|
25
|
+
cmd.call(args)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# same as above but in reverse dependency-order
|
31
|
+
[:stop, :delete].each do |wrap|
|
32
|
+
around(wrap) do |cmd, args|
|
33
|
+
if args.empty?
|
34
|
+
reversed = []
|
35
|
+
each_app do |a|
|
36
|
+
reversed.unshift a["name"]
|
37
|
+
end || err("No applications to act on.")
|
38
|
+
|
39
|
+
reversed.each do |name|
|
40
|
+
cmd.call(name)
|
41
|
+
puts "" unless simple_output?
|
42
|
+
end
|
43
|
+
else
|
44
|
+
cmd.call(args)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# stop apps in reverse dependency order,
|
50
|
+
# and then start in dependency order
|
51
|
+
around(:restart) do |cmd, args|
|
52
|
+
if args.empty?
|
53
|
+
reversed = []
|
54
|
+
forwards = []
|
55
|
+
each_app do |a|
|
56
|
+
reversed.unshift a["name"]
|
57
|
+
forwards << a["name"]
|
58
|
+
end || err("No applications to act on.")
|
59
|
+
|
60
|
+
reversed.each do |name|
|
61
|
+
stop(name)
|
62
|
+
end
|
63
|
+
|
64
|
+
puts "" unless simple_output?
|
65
|
+
|
66
|
+
forwards.each do |name|
|
67
|
+
start(name)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
cmd.call(args)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# push and sync meta changes in the manifest
|
75
|
+
# also sets env data on creation if present in manifest
|
76
|
+
around(:push) do |push, args|
|
77
|
+
if args.empty?
|
78
|
+
all_pushed =
|
79
|
+
each_app do |a|
|
80
|
+
app = client.app(a["name"])
|
81
|
+
updating = app.exists?
|
82
|
+
|
83
|
+
start = input(:start)
|
84
|
+
|
85
|
+
begin
|
86
|
+
inputs[:start] = false
|
87
|
+
|
88
|
+
sync_changes(a)
|
89
|
+
push.call(a["name"])
|
90
|
+
|
91
|
+
unless updating
|
92
|
+
app.env = a["env"]
|
93
|
+
|
94
|
+
if start
|
95
|
+
start(a["name"])
|
96
|
+
else
|
97
|
+
app.update!
|
98
|
+
end
|
99
|
+
end
|
100
|
+
ensure
|
101
|
+
inputs[:start] = start
|
102
|
+
end
|
103
|
+
|
104
|
+
puts "" unless simple_output?
|
105
|
+
end
|
106
|
+
|
107
|
+
push.call unless all_pushed
|
108
|
+
else
|
109
|
+
push.call(args)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: manifests-vmc-plugin
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Alex Suraci
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-05-01 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description:
|
22
|
+
email:
|
23
|
+
- asuraci@vmware.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- LICENSE
|
32
|
+
- Rakefile
|
33
|
+
- lib/manifests-vmc-plugin/errors.rb
|
34
|
+
- lib/manifests-vmc-plugin/manifest.rb
|
35
|
+
- lib/manifests-vmc-plugin/plugin.rb
|
36
|
+
- lib/manifests-vmc-plugin/version.rb
|
37
|
+
- lib/manifests-vmc-plugin.rb
|
38
|
+
homepage: http://cloudfoundry.com/
|
39
|
+
licenses: []
|
40
|
+
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
hash: 3
|
52
|
+
segments:
|
53
|
+
- 0
|
54
|
+
version: "0"
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
requirements: []
|
65
|
+
|
66
|
+
rubyforge_project: manifests-vmc-plugin
|
67
|
+
rubygems_version: 1.8.23
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Cloud Foundry automation via manifest documents.
|
71
|
+
test_files: []
|
72
|
+
|