static 1.0.1 → 1.0.3

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,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,31 @@
1
+ require "manifests-vmc-plugin/loader/builder"
2
+ require "manifests-vmc-plugin/loader/normalizer"
3
+ require "manifests-vmc-plugin/loader/resolver"
4
+
5
+ module VMCManifests
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,34 @@
1
+ module VMCManifests
2
+ module Builder
3
+ # parse a manifest and merge with its inherited manifests
4
+ def build(file)
5
+ manifest = YAML.load_file file
6
+
7
+ Array(manifest["inherit"]).each do |path|
8
+ manifest = merge_parent(path, manifest)
9
+ end
10
+
11
+ manifest
12
+ end
13
+
14
+ private
15
+
16
+ # merge the manifest at `parent_path' into the `child'
17
+ def merge_parent(parent_path, child)
18
+ merge_manifest(build(from_manifest(parent_path)), child)
19
+ end
20
+
21
+ # deep hash merge
22
+ def merge_manifest(parent, child)
23
+ merge = proc do |_, old, new|
24
+ if new.is_a?(Hash) && old.is_a?(Hash)
25
+ old.merge(new, &merge)
26
+ else
27
+ new
28
+ end
29
+ end
30
+
31
+ parent.merge(child, &merge)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,149 @@
1
+ module VMCManifests
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
@@ -0,0 +1,79 @@
1
+ module VMCManifests
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 "vmc/plugin"
4
+ require "manifests-vmc-plugin"
5
+
6
+
7
+ class ManifestsPlugin < VMC::App::Base
8
+ include VMCManifests
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