cloulu 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,317 @@
1
+ require "yaml"
2
+ require "set"
3
+
4
+ require "manifests-vmc-plugin/loader"
5
+
6
+
7
+ module VMCManifests
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 `vmc 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,29 @@
1
+ require "spec_helper"
2
+
3
+ require "manifests-vmc-plugin/errors"
4
+
5
+
6
+ describe VMCManifests::InvalidManifest do
7
+ let(:file) { "/path/to/file" }
8
+
9
+ subject { described_class.new(file) }
10
+
11
+ describe "#initialize" do
12
+ it "is initialized with a file" do
13
+ described_class.new(file)
14
+ end
15
+ end
16
+
17
+ describe "#to_s" do
18
+ it "says the file is malformed" do
19
+ expect(subject.to_s).to eq(
20
+ "Manifest file '#{file}' is malformed.")
21
+ end
22
+ end
23
+
24
+ describe "#file" do
25
+ it "returns the file it was initialized with" do
26
+ expect(subject.file).to eq(file)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,84 @@
1
+ require "spec_helper"
2
+
3
+ require "manifests-vmc-plugin/loader"
4
+ require "manifests-vmc-plugin/errors"
5
+
6
+
7
+ describe VMCManifests::Builder do
8
+ subject { VMCManifests::Loader.new(nil, nil) }
9
+
10
+ describe "#build" do
11
+ let(:file) { "manifest.yml" }
12
+
13
+ before do
14
+ FakeFS.activate!
15
+
16
+ File.open(file, "w") do |io|
17
+ io.write manifest
18
+ end
19
+ end
20
+
21
+ after do
22
+ FakeFS.deactivate!
23
+ FakeFS::FileSystem.clear
24
+ end
25
+
26
+ context "with a simple manifest" do
27
+ let(:manifest) do
28
+ <<EOF
29
+ ---
30
+ foo: bar
31
+ EOF
32
+ end
33
+
34
+ it "loads the manifest YAML" do
35
+ expect(subject.build(file)).to eq("foo" => "bar")
36
+ end
37
+ end
38
+
39
+ context "with a manifest that inherits another" do
40
+ let(:manifest) do
41
+ <<EOF
42
+ ---
43
+ inherit: other-manifest.yml
44
+ foo:
45
+ baz: c
46
+ EOF
47
+ end
48
+
49
+ before do
50
+ FakeFS.activate!
51
+
52
+ File.open("other-manifest.yml", "w") do |io|
53
+ io.write <<OTHER
54
+ ---
55
+ foo:
56
+ bar: a
57
+ baz: b
58
+ OTHER
59
+ end
60
+ end
61
+
62
+ it "merges itself into the parent, by depth" do
63
+ manifest = subject.build(file)
64
+ expect(manifest).to include(
65
+ "foo" => { "bar" => "a", "baz" => "c" })
66
+ end
67
+
68
+ it "does not include the 'inherit' attribute" do
69
+ manifest = subject.build(file)
70
+ expect(manifest).to_not include("inherit")
71
+ end
72
+ end
73
+
74
+ context "with an invalid manifest" do
75
+ let(:manifest) { "" }
76
+
77
+ it "raises an error" do
78
+ expect {
79
+ subject.build(file)
80
+ }.to raise_error(VMCManifests::InvalidManifest)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,176 @@
1
+ require "spec_helper"
2
+
3
+ require "manifests-vmc-plugin/loader"
4
+
5
+
6
+ describe VMCManifests::Normalizer do
7
+ let(:manifest) { {} }
8
+ let(:loader) { VMCManifests::Loader.new(nil, nil) }
9
+
10
+ describe '#normalize!' do
11
+ subject do
12
+ loader.normalize!(manifest)
13
+ manifest
14
+ end
15
+
16
+ context 'with a manifest where the applications have no path set' do
17
+ let(:manifest) { { "applications" => { "." => { "name" => "foo" } } } }
18
+
19
+ it "sets the path to their tag, assuming it's a path" do
20
+ expect(subject).to eq(
21
+ :applications => [{ :name => "foo", :path => "." }])
22
+ end
23
+ end
24
+
25
+ context 'with a manifest where the url is nil' do
26
+ let(:manifest) { { "applications" => { "." => { "url" => nil } } } }
27
+
28
+ it "sets it to none" do
29
+ expect(subject).to eq(
30
+ :applications => [{ :path => ".", :url => "none" }]
31
+ )
32
+ end
33
+ end
34
+
35
+ context 'with a manifest with a subdomain attribute' do
36
+ let(:manifest) { { "applications" => { "." => { "subdomain" => "use-this-for-host" } } } }
37
+
38
+ it "sets the subdomain key to be host" do
39
+ expect(subject).to eq(
40
+ :applications => [{ :path => ".", :host => "use-this-for-host" }]
41
+ )
42
+ end
43
+
44
+ context "when the host attribute is also set" do
45
+ let(:manifest) { { "applications" => { "." => { "subdomain" => "dont-use-this-for-host", "host" => "canonical-attribute" } } } }
46
+
47
+ it 'does not overwrite an explicit host attribute' do
48
+ expect(subject).to eq(
49
+ :applications => [{ :path => ".", :host => "canonical-attribute" }]
50
+ )
51
+ end
52
+ end
53
+ end
54
+
55
+ context 'with a manifest with toplevel attributes' do
56
+ context 'and properties' do
57
+ let(:manifest) {
58
+ { "name" => "foo", "properties" => { "fizz" => "buzz" } }
59
+ }
60
+
61
+ it 'keeps the properties at the toplevel' do
62
+ expect(subject).to eq(
63
+ :applications => [{ :name => "foo", :path => "." }],
64
+ :properties => { :fizz => "buzz" })
65
+ end
66
+ end
67
+
68
+ context 'and no applications' do
69
+ context 'and no path' do
70
+ let(:manifest) { { "name" => "foo" } }
71
+
72
+ it 'adds it as an application with path .' do
73
+ expect(subject).to eq(
74
+ :applications => [{ :name => "foo", :path => "." }])
75
+ end
76
+ end
77
+
78
+ context 'and a path' do
79
+ let(:manifest) { { "name" => "foo", "path" => "./foo" } }
80
+
81
+ it 'adds it as an application with the proper tag and path' do
82
+ expect(subject).to eq(
83
+ :applications => [{ :name => "foo", :path => "./foo" }])
84
+ end
85
+ end
86
+ end
87
+
88
+ context 'and applications' do
89
+ let(:manifest) {
90
+ { "runtime" => "ruby19",
91
+ "applications" => {
92
+ "./foo" => { "name" => "foo" },
93
+ "./bar" => { "name" => "bar" },
94
+ "./baz" => { "name" => "baz", "runtime" => "ruby18" }
95
+ }
96
+ }
97
+ }
98
+
99
+ it "merges the toplevel attributes into the applications" do
100
+ expect(subject[:applications]).to match_array [
101
+ { :name => "foo", :path => "./foo", :runtime => "ruby19" },
102
+ { :name => "bar", :path => "./bar", :runtime => "ruby19" },
103
+ { :name => "baz", :path => "./baz", :runtime => "ruby18" }
104
+ ]
105
+ end
106
+ end
107
+ end
108
+
109
+ context 'with a manifest where applications is a hash' do
110
+ let(:manifest) { { "applications" => { "foo" => { "name" => "foo" } } } }
111
+
112
+ it 'converts the array to a hash, with the path as the key' do
113
+ expect(subject).to eq(
114
+ :applications => [{ :name => "foo", :path => "foo" }])
115
+ end
116
+
117
+ context "and the applications had dependencies" do
118
+ let(:manifest) do
119
+ { "applications" => {
120
+ "bar" => { "name" => "bar", "depends-on" => "foo" },
121
+ "foo" => { "name" => "foo" }
122
+ }
123
+ }
124
+ end
125
+
126
+ it "converts using dependency order" do
127
+ expect(subject).to eq(
128
+ :applications => [{ :name => "foo", :path => "foo" }, { :name => "bar", :path => "bar" }])
129
+ end
130
+
131
+ context "and there's a circular dependency" do
132
+ let(:manifest) do
133
+ { "applications" => {
134
+ "bar" => { "name" => "bar", "depends-on" => "foo" },
135
+ "foo" => { "name" => "foo", "depends-on" => "bar" }
136
+ }
137
+ }
138
+ end
139
+
140
+ it "doesn't blow up" do
141
+ expect(subject).to be_true
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ describe '#normalize_app!' do
149
+ subject do
150
+ loader.send(:normalize_app!, manifest)
151
+ manifest
152
+ end
153
+
154
+ context 'with framework as a hash' do
155
+ let(:manifest) {
156
+ { "name" => "foo",
157
+ "framework" => { "name" => "ruby19", "mem" => "64M" }
158
+ }
159
+ }
160
+
161
+ it 'sets the framework to just the name' do
162
+ expect(subject).to eq(
163
+ "name" => "foo",
164
+ "framework" => "ruby19")
165
+ end
166
+ end
167
+
168
+ context 'with mem instead of memory' do
169
+ let(:manifest) { { "name" => "foo", "mem" => "128M" } }
170
+
171
+ it 'renames mem to memory' do
172
+ expect(subject).to eq("name" => "foo", "memory" => "128M")
173
+ end
174
+ end
175
+ end
176
+ end