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,79 @@
1
+ module CFManifests
2
+ module Resolver
3
+ def resolve(manifest, resolver)
4
+ new = {}
5
+
6
+ new[:applications] = manifest[:applications].collect do |app|
7
+ resolve_lexically(resolver, app, [manifest])
8
+ end
9
+
10
+ resolve_lexically(resolver, new, [new])
11
+ end
12
+
13
+ private
14
+
15
+ # resolve symbols, with hashes introducing new lexical symbols
16
+ def resolve_lexically(resolver, val, ctx)
17
+ case val
18
+ when Hash
19
+ new = {}
20
+
21
+ val.each do |k, v|
22
+ new[k] = resolve_lexically(resolver, v, [val] + ctx)
23
+ end
24
+
25
+ new
26
+ when Array
27
+ val.collect do |v|
28
+ resolve_lexically(resolver, v, ctx)
29
+ end
30
+ when String
31
+ val.gsub(/\$\{([^\}]+)\}/) do
32
+ resolve_symbol(resolver, $1, ctx)
33
+ end
34
+ else
35
+ val
36
+ end
37
+ end
38
+
39
+ # resolve a symbol to its value, and then resolve that value
40
+ def resolve_symbol(resolver, sym, ctx)
41
+ if found = find_symbol(sym.to_sym, ctx)
42
+ resolve_lexically(resolver, found, ctx)
43
+ found
44
+ elsif dynamic = resolver.resolve_symbol(sym)
45
+ dynamic
46
+ else
47
+ fail("Unknown symbol in manifest: #{sym}")
48
+ end
49
+ end
50
+
51
+ # search for a symbol introduced in the lexical context
52
+ def find_symbol(sym, ctx)
53
+ ctx.each do |h|
54
+ if val = resolve_in(h, sym)
55
+ return val
56
+ end
57
+ end
58
+
59
+ nil
60
+ end
61
+
62
+ # find a value, searching in explicit properties first
63
+ def resolve_in(hash, *where)
64
+ find_in_hash(hash, [:properties] + where) ||
65
+ find_in_hash(hash, where)
66
+ end
67
+
68
+ # helper for following a path of values in a hash
69
+ def find_in_hash(hash, where)
70
+ what = hash
71
+ where.each do |x|
72
+ return nil unless what.is_a?(Hash)
73
+ what = what[x]
74
+ end
75
+
76
+ what
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,145 @@
1
+ require "pathname"
2
+
3
+ require "cf/plugin"
4
+ require "manifests-cf-plugin"
5
+
6
+
7
+ class ManifestsPlugin < CF::App::Base
8
+ include CFManifests
9
+
10
+ option :manifest, :aliases => "-m", :value => :file,
11
+ :desc => "Path to manifest file to use"
12
+
13
+
14
+ [ :start, :restart, :instances, :logs, :env, :health, :stats,
15
+ :scale, :app, :stop, :delete
16
+ ].each do |wrap|
17
+ name_made_optional = change_argument(wrap, :app, :optional)
18
+
19
+ around(wrap) do |cmd, input|
20
+ wrap_with_optional_name(name_made_optional, cmd, input)
21
+ end
22
+ end
23
+
24
+
25
+ add_input :push, :reset, :desc => "Reset to values in the manifest",
26
+ :default => false
27
+
28
+ around(:push) do |push, input|
29
+ wrap_push(push, input)
30
+ end
31
+
32
+ private
33
+
34
+ def wrap_with_optional_name(name_made_optional, cmd, input)
35
+ return cmd.call if input[:all]
36
+
37
+ unless manifest
38
+ # if the command knows how to handle this
39
+ if input.has?(:app) || !name_made_optional
40
+ return cmd.call
41
+ else
42
+ return no_apps
43
+ end
44
+ end
45
+
46
+ internal, external = apps_in_manifest(input)
47
+
48
+ return cmd.call if internal.empty? && !external.empty?
49
+
50
+ show_manifest_usage
51
+
52
+ if internal.empty? && external.empty?
53
+ internal = current_apps if internal.empty?
54
+ internal = all_apps if internal.empty?
55
+ end
56
+
57
+ internal = internal.collect { |app| app[:name] }
58
+
59
+ apps = internal + external
60
+ return no_apps if apps.empty?
61
+
62
+ apps.each.with_index do |app, num|
63
+ line unless quiet? || num == 0
64
+ cmd.call(input.without(:apps).merge_given(:app => app))
65
+ end
66
+ end
67
+
68
+ def apply_changes(app, input)
69
+ app.memory = megabytes(input[:memory]) if input.has?(:memory)
70
+ app.total_instances = input[:instances] if input.has?(:instances)
71
+ app.command = input[:command] if input.has?(:command)
72
+ app.production = input[:plan].upcase.start_with?("P") if input.has?(:plan)
73
+ app.framework = input[:framework] if input.has?(:framework)
74
+ app.runtime = input[:runtime] if input.has?(:runtime)
75
+ app.buildpack = input[:buildpack] if input.has?(:buildpack)
76
+ end
77
+
78
+ def wrap_push(push, input)
79
+ unless manifest
80
+ create_and_save_manifest(push, input)
81
+ return
82
+ end
83
+
84
+ particular, external = apps_in_manifest(input)
85
+
86
+ unless external.empty?
87
+ fail "Could not find #{b(external.join(", "))}' in the manifest."
88
+ end
89
+
90
+ apps = particular.empty? ? all_apps : particular
91
+
92
+ show_manifest_usage
93
+
94
+ spaced(apps) do |app_manifest|
95
+ push_with_manifest(app_manifest, push, input)
96
+ end
97
+ end
98
+
99
+ def push_with_manifest(app_manifest, push, input)
100
+ with_filters(
101
+ :push => {
102
+ :create_app => proc { |a|
103
+ setup_env(a, app_manifest)
104
+ a
105
+ },
106
+ :push_app => proc { |a|
107
+ setup_services(a, app_manifest)
108
+ a
109
+ }
110
+ }) do
111
+ app_input = push_input_for(app_manifest, input)
112
+
113
+ push.call(app_input)
114
+ end
115
+ end
116
+
117
+ def push_input_for(app_manifest, input)
118
+ existing_app = client.app_by_name(app_manifest[:name])
119
+ rebased_input = input.rebase_given(app_manifest)
120
+
121
+ if !existing_app || input[:reset]
122
+ input = rebased_input
123
+ else
124
+ warn_reset_changes if manifest_differs?(existing_app, rebased_input)
125
+ end
126
+
127
+ input.merge(
128
+ :path => from_manifest(app_manifest[:path]),
129
+ :name => app_manifest[:name],
130
+ :bind_services => false,
131
+ :create_services => false)
132
+ end
133
+
134
+ def manifest_differs?(app, input)
135
+ apply_changes(app, input)
136
+ app.changed?
137
+ end
138
+
139
+ def create_and_save_manifest(push, input)
140
+ with_filters(
141
+ :push => { :push_app => proc { |a| ask_to_save(input, a); a } }) do
142
+ push.call
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,3 @@
1
+ module CFManifests
2
+ VERSION = "0.7.0.rc1".freeze
3
+ end
@@ -0,0 +1,29 @@
1
+ require "spec_helper"
2
+
3
+ require "manifests-cf-plugin/errors"
4
+
5
+
6
+ describe CFManifests::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-cf-plugin/loader"
4
+ require "manifests-cf-plugin/errors"
5
+
6
+
7
+ describe CFManifests::Builder do
8
+ subject { CFManifests::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(CFManifests::InvalidManifest)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,176 @@
1
+ require "spec_helper"
2
+
3
+ require "manifests-cf-plugin/loader"
4
+
5
+
6
+ describe CFManifests::Normalizer do
7
+ let(:manifest) { {} }
8
+ let(:loader) { CFManifests::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