manifests-cf-plugin 0.7.0.rc1

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.
@@ -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