manifests-cf-plugin 0.7.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ require "rake"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task :default => :spec
@@ -0,0 +1,317 @@
1
+ require "yaml"
2
+ require "set"
3
+
4
+ require "manifests-cf-plugin/loader"
5
+
6
+
7
+ module CFManifests
8
+ MANIFEST_FILE = "manifest.yml"
9
+
10
+ @@showed_manifest_usage = false
11
+
12
+ def manifest
13
+ return @manifest if @manifest
14
+
15
+ if manifest_file && File.exists?(manifest_file)
16
+ @manifest = load_manifest(manifest_file)
17
+ end
18
+ end
19
+
20
+ def save_manifest(save_to = manifest_file)
21
+ fail "No manifest to save!" unless @manifest
22
+
23
+ File.open(save_to, "w") do |io|
24
+ YAML.dump(@manifest, io)
25
+ end
26
+ end
27
+
28
+ # find the manifest file to work with
29
+ def manifest_file
30
+ return @manifest_file if @manifest_file
31
+
32
+ unless path = input[:manifest]
33
+ where = Dir.pwd
34
+ while true
35
+ if File.exists?(File.join(where, MANIFEST_FILE))
36
+ path = File.join(where, MANIFEST_FILE)
37
+ break
38
+ elsif File.basename(where) == "/"
39
+ path = nil
40
+ break
41
+ else
42
+ where = File.expand_path("../", where)
43
+ end
44
+ end
45
+ end
46
+
47
+ return unless path
48
+
49
+ @manifest_file = File.expand_path(path)
50
+ end
51
+
52
+ # load and resolve a given manifest file
53
+ def load_manifest(file)
54
+ Loader.new(file, self).manifest
55
+ end
56
+
57
+ # dynamic symbol resolution
58
+ def resolve_symbol(sym)
59
+ case sym
60
+ when "target-url"
61
+ client_target
62
+
63
+ when "target-base"
64
+ target_base
65
+
66
+ when "random-word"
67
+ sprintf("%04x", rand(0x0100000))
68
+
69
+ when /^ask (.+)/
70
+ ask($1)
71
+ end
72
+ end
73
+
74
+ # find apps by an identifier, which may be either a tag, a name, or a path
75
+ def find_apps(identifier)
76
+ return [] unless manifest
77
+
78
+ apps = apps_by(:name, identifier)
79
+
80
+ if apps.empty?
81
+ apps = apps_by(:path, from_manifest(identifier))
82
+ end
83
+
84
+ apps
85
+ end
86
+
87
+ # return all the apps described by the manifest
88
+ def all_apps
89
+ manifest[:applications]
90
+ end
91
+
92
+ def current_apps
93
+ manifest[:applications].select do |app|
94
+ next unless app[:path]
95
+ from_manifest(app[:path]) == Dir.pwd
96
+ end
97
+ end
98
+
99
+ # splits the user's input, resolving paths with the manifest,
100
+ # into internal/external apps
101
+ #
102
+ # internal apps are returned as their data in the manifest
103
+ #
104
+ # external apps are the strings that the user gave, to be
105
+ # passed along wholesale to the wrapped command
106
+ def apps_in_manifest(input = nil, use_name = true, &blk)
107
+ names_or_paths =
108
+ if input.has?(:apps)
109
+ # names may be given but be [], which will still cause
110
+ # interaction, so use #direct instead of #[] here
111
+ input.direct(:apps)
112
+ elsif input.has?(:app)
113
+ [input.direct(:app)]
114
+ elsif input.has?(:name)
115
+ [input.direct(:name)]
116
+ else
117
+ []
118
+ end
119
+
120
+ internal = []
121
+ external = []
122
+
123
+ names_or_paths.each do |x|
124
+ if x.is_a?(String)
125
+ if x =~ %r([/\\])
126
+ apps = find_apps(File.expand_path(x))
127
+
128
+ if apps.empty?
129
+ fail("Path #{b(x)} is not present in manifest #{b(relative_manifest_file)}.")
130
+ end
131
+ else
132
+ apps = find_apps(x)
133
+ end
134
+
135
+ if !apps.empty?
136
+ internal += apps
137
+ else
138
+ external << x
139
+ end
140
+ else
141
+ external << x
142
+ end
143
+ end
144
+
145
+ [internal, external]
146
+ end
147
+
148
+ def create_manifest_for(app, path)
149
+ meta = {
150
+ "name" => app.name,
151
+ "framework" => app.framework.name,
152
+ "runtime" => app.runtime.name,
153
+ "memory" => human_size(app.memory * 1024 * 1024, 0),
154
+ "instances" => app.total_instances,
155
+ "url" => app.url ? app.url.sub(target_base, '${target-base}') : "none",
156
+ "path" => path
157
+ }
158
+
159
+ services = app.services
160
+
161
+ unless services.empty?
162
+ meta["services"] = {}
163
+
164
+ services.each do |i|
165
+ if v2?
166
+ p = i.service_plan
167
+ s = p.service
168
+
169
+ meta["services"][i.name] = {
170
+ "label" => s.label,
171
+ "provider" => s.provider,
172
+ "version" => s.version,
173
+ "plan" => p.name
174
+ }
175
+ else
176
+ meta["services"][i.name] = {
177
+ "vendor" => i.vendor,
178
+ "version" => i.version,
179
+ "tier" => i.tier
180
+ }
181
+ end
182
+ end
183
+ end
184
+
185
+ if cmd = app.command
186
+ meta["command"] = cmd
187
+ end
188
+
189
+ if buildpack = app.buildpack
190
+ meta["buildpack"] = buildpack
191
+ end
192
+
193
+ meta
194
+ end
195
+
196
+ private
197
+
198
+ def relative_manifest_file
199
+ Pathname.new(manifest_file).relative_path_from(Pathname.pwd)
200
+ end
201
+
202
+ def show_manifest_usage
203
+ return if @@showed_manifest_usage
204
+
205
+ path = relative_manifest_file
206
+ line "Using manifest file #{c(path, :name)}"
207
+ line
208
+
209
+ @@showed_manifest_usage = true
210
+ end
211
+
212
+ def no_apps
213
+ fail "No applications or manifest to operate on."
214
+ end
215
+
216
+ def warn_reset_changes
217
+ line c("Not applying manifest changes without --reset", :warning)
218
+ line "See `cf diff` for more details."
219
+ line
220
+ end
221
+
222
+ def apps_by(attr, val)
223
+ manifest[:applications].select do |info|
224
+ info[attr] == val
225
+ end
226
+ end
227
+
228
+ # expand a path relative to the manifest file's directory
229
+ def from_manifest(path)
230
+ File.expand_path(path, File.dirname(manifest_file))
231
+ end
232
+
233
+
234
+ def ask_to_save(input, app)
235
+ return if manifest_file
236
+ return unless ask("Save configuration?", :default => false)
237
+
238
+ manifest = create_manifest_for(app, input[:path])
239
+
240
+ with_progress("Saving to #{c("manifest.yml", :name)}") do
241
+ File.open("manifest.yml", "w") do |io|
242
+ YAML.dump(
243
+ { "applications" => [manifest] },
244
+ io)
245
+ end
246
+ end
247
+ end
248
+
249
+ def env_hash(val)
250
+ if val.is_a?(Hash)
251
+ val
252
+ else
253
+ hash = {}
254
+
255
+ val.each do |pair|
256
+ name, val = pair.split("=", 2)
257
+ hash[name] = val
258
+ end
259
+
260
+ hash
261
+ end
262
+ end
263
+
264
+ def setup_env(app, info)
265
+ return unless info[:env]
266
+ app.env = env_hash(info[:env])
267
+ end
268
+
269
+ def setup_services(app, info)
270
+ return if !info[:services] || info[:services].empty?
271
+
272
+ offerings = client.services
273
+
274
+ to_bind = []
275
+
276
+ info[:services].each do |name, svc|
277
+ name = name.to_s
278
+
279
+ if instance = client.service_instance_by_name(name)
280
+ to_bind << instance
281
+ else
282
+ offering = offerings.find { |o|
283
+ o.label == (svc[:label] || svc[:type] || svc[:vendor]) &&
284
+ (!svc[:version] || o.version == svc[:version]) &&
285
+ (o.provider == (svc[:provider] || "core"))
286
+ }
287
+
288
+ fail "Unknown service offering: #{svc.inspect}." unless offering
289
+
290
+ if v2?
291
+ plan = offering.service_plans.find { |p|
292
+ p.name == (svc[:plan] || "D100")
293
+ }
294
+
295
+ fail "Unknown service plan: #{svc[:plan]}." unless plan
296
+ end
297
+
298
+ invoke :create_service,
299
+ :name => name,
300
+ :offering => offering,
301
+ :plan => plan,
302
+ :app => app
303
+ end
304
+ end
305
+
306
+ to_bind.each do |s|
307
+ next if app.binds?(s)
308
+
309
+ # TODO: splat
310
+ invoke :bind_service, :app => app, :service => s
311
+ end
312
+ end
313
+
314
+ def target_base
315
+ client_target.sub(/^[^\.]+\./, "")
316
+ end
317
+ end
@@ -0,0 +1,33 @@
1
+ module CFManifests
2
+ class InvalidManifest < RuntimeError
3
+ attr_reader :file
4
+
5
+ def initialize(file)
6
+ @file = file
7
+ end
8
+
9
+ def to_s
10
+ "Manifest file '#{@file}' is malformed."
11
+ end
12
+ end
13
+
14
+ class CircularDependency < RuntimeError
15
+ def initialize(app)
16
+ @app = app
17
+ end
18
+
19
+ def to_s
20
+ "Circular dependency in application '#@app'"
21
+ end
22
+ end
23
+
24
+ class UnknownSymbol < RuntimeError
25
+ def initialize(sym)
26
+ @sym = sym
27
+ end
28
+
29
+ def to_s
30
+ "Undefined symbol in manifest: '#@sym'"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ require "manifests-cf-plugin/loader/builder"
2
+ require "manifests-cf-plugin/loader/normalizer"
3
+ require "manifests-cf-plugin/loader/resolver"
4
+
5
+ module CFManifests
6
+ class Loader
7
+ include Builder
8
+ include Normalizer
9
+ include Resolver
10
+
11
+ def initialize(file, resolver)
12
+ @file = file
13
+ @resolver = resolver
14
+ end
15
+
16
+ def manifest
17
+ info = build(@file)
18
+ normalize! info
19
+ resolve info, @resolver
20
+ end
21
+
22
+ private
23
+
24
+ # expand a path relative to the manifest file's directory
25
+ def from_manifest(path)
26
+ return path unless @file
27
+
28
+ File.expand_path(path, File.dirname(@file))
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ module CFManifests
2
+ module Builder
3
+ # parse a manifest and merge with its inherited manifests
4
+ def build(file)
5
+ manifest = YAML.load_file file
6
+ raise InvalidManifest.new(file) unless manifest
7
+
8
+ Array(manifest["inherit"]).each do |path|
9
+ manifest = merge_parent(path, manifest)
10
+ end
11
+
12
+ manifest.delete("inherit")
13
+
14
+ manifest
15
+ end
16
+
17
+ private
18
+
19
+ # merge the manifest at `parent_path' into the `child'
20
+ def merge_parent(parent_path, child)
21
+ merge_manifest(build(from_manifest(parent_path)), child)
22
+ end
23
+
24
+ # deep hash merge
25
+ def merge_manifest(parent, child)
26
+ merge = proc do |_, old, new|
27
+ if new.is_a?(Hash) && old.is_a?(Hash)
28
+ old.merge(new, &merge)
29
+ else
30
+ new
31
+ end
32
+ end
33
+
34
+ parent.merge(child, &merge)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,149 @@
1
+ module CFManifests
2
+ module Normalizer
3
+ MANIFEST_META = ["applications", "properties"]
4
+
5
+ def normalize!(manifest)
6
+ toplevel = toplevel_attributes(manifest)
7
+
8
+ apps = manifest["applications"]
9
+ apps ||= [{}]
10
+
11
+ default_paths_to_keys!(apps)
12
+
13
+ apps = convert_to_array(apps)
14
+
15
+ merge_toplevel!(toplevel, manifest, apps)
16
+ normalize_apps!(apps)
17
+
18
+ manifest["applications"] = apps
19
+
20
+ normalize_paths!(apps)
21
+
22
+ keyval = normalize_key_val(manifest)
23
+ manifest.clear.merge!(keyval)
24
+
25
+ nil
26
+ end
27
+
28
+ private
29
+
30
+ def normalize_paths!(apps)
31
+ apps.each do |app|
32
+ app["path"] = from_manifest(app["path"])
33
+ end
34
+ end
35
+
36
+ def convert_to_array(apps)
37
+ return apps if apps.is_a?(Array)
38
+
39
+ ordered_by_deps(apps)
40
+ end
41
+
42
+ # sort applications in dependency order
43
+ # e.g. if A depends on B, B will be listed before A
44
+ def ordered_by_deps(apps, processed = Set[])
45
+ ordered = []
46
+ apps.each do |tag, info|
47
+ next if processed.include?(tag)
48
+
49
+ if deps = Array(info["depends-on"])
50
+ dep_apps = {}
51
+ deps.each do |dep|
52
+ dep_apps[dep] = apps[dep]
53
+ end
54
+
55
+ processed.add(tag)
56
+
57
+ ordered += ordered_by_deps(dep_apps, processed)
58
+ ordered << info
59
+ else
60
+ ordered << info
61
+ processed.add(tag)
62
+ end
63
+ end
64
+
65
+ ordered.each { |app| app.delete("depends-on") }
66
+
67
+ ordered
68
+ end
69
+
70
+ def default_paths_to_keys!(apps)
71
+ return if apps.is_a?(Array)
72
+
73
+ apps.each do |tag, app|
74
+ app["path"] ||= tag
75
+ end
76
+ end
77
+
78
+ def normalize_apps!(apps)
79
+ apps.each do |app|
80
+ normalize_app!(app)
81
+ end
82
+ end
83
+
84
+ def merge_toplevel!(toplevel, manifest, apps)
85
+ return if toplevel.empty?
86
+
87
+ apps.collect! do |a|
88
+ toplevel.merge(a)
89
+ end
90
+
91
+ toplevel.each do |k, _|
92
+ manifest.delete k
93
+ end
94
+ end
95
+
96
+ def normalize_app!(app)
97
+ if app["framework"].is_a?(Hash)
98
+ app["framework"] = app["framework"]["name"]
99
+ end
100
+
101
+ if app.key?("mem")
102
+ app["memory"] = app.delete("mem")
103
+ end
104
+
105
+ if app.key?("url") && app["url"].nil?
106
+ app["url"] = "none"
107
+ end
108
+
109
+ if app.key?("subdomain")
110
+ if app.key?("host")
111
+ app.delete("subdomain")
112
+ else
113
+ app["host"] = app.delete("subdomain")
114
+ end
115
+ end
116
+ end
117
+
118
+ def toplevel_attributes(manifest)
119
+ top =
120
+ manifest.reject { |k, _|
121
+ MANIFEST_META.include? k
122
+ }
123
+
124
+ # implicit toplevel path of .
125
+ top["path"] ||= "."
126
+
127
+ top
128
+ end
129
+
130
+ def normalize_key_val(val)
131
+ case val
132
+ when Hash
133
+ stringified = {}
134
+
135
+ val.each do |k, v|
136
+ stringified[k.to_sym] = normalize_key_val(v)
137
+ end
138
+
139
+ stringified
140
+ when Array
141
+ val.collect { |x| normalize_key_val(x) }
142
+ when nil
143
+ nil
144
+ else
145
+ val.to_s
146
+ end
147
+ end
148
+ end
149
+ end