vmc_virgo 0.0.1.beta

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,227 @@
1
+ require 'rubygems'
2
+ require 'interact'
3
+ require 'terminal-table/import'
4
+
5
+ module VMC::Cli
6
+
7
+ module Command
8
+
9
+ class Base
10
+ include Interactive
11
+
12
+ attr_reader :no_prompt, :prompt_ok
13
+
14
+ MANIFEST = "manifest.yml"
15
+
16
+ def initialize(options={})
17
+ @options = options.dup
18
+ @no_prompt = @options[:noprompts]
19
+ @prompt_ok = !no_prompt
20
+
21
+ # Suppress colorize on Windows systems for now.
22
+ if WINDOWS
23
+ VMC::Cli::Config.colorize = false
24
+ end
25
+
26
+ @path = @options[:path] || '.'
27
+
28
+ load_manifest manifest_file if manifest_file
29
+ end
30
+
31
+ def manifest_file
32
+ return @options[:manifest] if @options[:manifest]
33
+ return @manifest_file if @manifest_file
34
+
35
+ where = File.expand_path(@path)
36
+ while true
37
+ if File.exists?(File.join(where, MANIFEST))
38
+ @manifest_file = File.join(where, MANIFEST)
39
+ break
40
+ elsif File.basename(where) == "/"
41
+ @manifest_file = nil
42
+ break
43
+ else
44
+ where = File.expand_path("../", where)
45
+ end
46
+ end
47
+
48
+ @manifest_file
49
+ end
50
+
51
+ def load_manifest_structure(file)
52
+ manifest = YAML.load_file file
53
+
54
+ Array(manifest["inherit"]).each do |p|
55
+ manifest = merge_parent(manifest, p)
56
+ end
57
+
58
+ if apps = manifest["applications"]
59
+ apps.each do |k, v|
60
+ abs = File.expand_path(k, file)
61
+ if Dir.pwd.start_with? abs
62
+ manifest = merge_manifest(manifest, v)
63
+ end
64
+ end
65
+ end
66
+
67
+ manifest
68
+ end
69
+
70
+ def resolve_manifest(manifest)
71
+ if apps = manifest["applications"]
72
+ apps.each_value do |v|
73
+ resolve_lexically(v, [manifest])
74
+ end
75
+ end
76
+
77
+ resolve_lexically(manifest, [manifest])
78
+ end
79
+
80
+ def load_manifest(file)
81
+ @manifest = load_manifest_structure(file)
82
+ resolve_manifest(@manifest)
83
+ end
84
+
85
+ def merge_parent(child, path)
86
+ file = File.expand_path("../" + path, manifest_file)
87
+ merge_manifest(child, load_manifest_structure(file))
88
+ end
89
+
90
+ def merge_manifest(child, parent)
91
+ merge = proc do |_, old, new|
92
+ if new.is_a?(Hash) and old.is_a?(Hash)
93
+ old.merge(new, &merge)
94
+ else
95
+ new
96
+ end
97
+ end
98
+
99
+ parent.merge(child, &merge)
100
+ end
101
+
102
+ def resolve_lexically(val, ctx = [@manifest])
103
+ case val
104
+ when Hash
105
+ val.each_value do |v|
106
+ resolve_lexically(v, [val] + ctx)
107
+ end
108
+ when Array
109
+ val.each do |v|
110
+ resolve_lexically(v, ctx)
111
+ end
112
+ when String
113
+ val.gsub!(/\$\{([[:alnum:]\-]+)\}/) do
114
+ resolve_symbol($1, ctx)
115
+ end
116
+ end
117
+
118
+ nil
119
+ end
120
+
121
+ def resolve_symbol(sym, ctx)
122
+ case sym
123
+ when "target-base"
124
+ target_base(ctx)
125
+
126
+ when "target-url"
127
+ target_url(ctx)
128
+
129
+ when "random-word"
130
+ "%04x" % [rand(0x0100000)]
131
+
132
+ else
133
+ found = find_symbol(sym, ctx)
134
+
135
+ if found
136
+ resolve_lexically(found, ctx)
137
+ found
138
+ else
139
+ err(sym, "Unknown symbol in manifest: ")
140
+ end
141
+ end
142
+ end
143
+
144
+ def find_symbol(sym, ctx)
145
+ ctx.each do |h|
146
+ if val = resolve_in(h, sym)
147
+ return val
148
+ end
149
+ end
150
+
151
+ nil
152
+ end
153
+
154
+ def resolve_in(hash, *where)
155
+ find_in_hash(hash, ["properties"] + where) ||
156
+ find_in_hash(hash, ["applications", @application] + where) ||
157
+ find_in_hash(hash, where)
158
+ end
159
+
160
+ def manifest(*where)
161
+ resolve_in(@manifest, *where)
162
+ end
163
+
164
+ def find_in_hash(hash, where)
165
+ what = hash
166
+ where.each do |x|
167
+ return nil unless what.is_a?(Hash)
168
+ what = what[x]
169
+ end
170
+
171
+ what
172
+ end
173
+
174
+ def target_url(ctx = [])
175
+ find_symbol("target", ctx) ||
176
+ (@client && @client.target) ||
177
+ VMC::Cli::Config.target_url
178
+ end
179
+
180
+ def target_base(ctx = [])
181
+ VMC::Cli::Config.base_of(find_symbol("target", ctx) || target_url)
182
+ end
183
+
184
+ # Inject a client to help in testing.
185
+ def client(cli=nil)
186
+ @client ||= cli
187
+ return @client if @client
188
+ @client = VMC::Client.new(target_url, auth_token)
189
+ @client.trace = VMC::Cli::Config.trace if VMC::Cli::Config.trace
190
+ @client.proxy_for @options[:proxy] if @options[:proxy]
191
+ @client
192
+ end
193
+
194
+ def client_info
195
+ @client_info ||= client.info
196
+ end
197
+
198
+ def auth_token
199
+ @auth_token = VMC::Cli::Config.auth_token(@options[:token_file])
200
+ end
201
+
202
+ def runtimes_info
203
+ return @runtimes if @runtimes
204
+ info = client_info
205
+ @runtimes = {}
206
+ if info[:frameworks]
207
+ info[:frameworks].each_value do |f|
208
+ next unless f[:runtimes]
209
+ f[:runtimes].each { |r| @runtimes[r[:name]] = r}
210
+ end
211
+ end
212
+ @runtimes
213
+ end
214
+
215
+ def frameworks_info
216
+ return @frameworks if @frameworks
217
+ info = client_info
218
+ @frameworks = []
219
+ if info[:frameworks]
220
+ info[:frameworks].each_value { |f| @frameworks << [f[:name]] }
221
+ end
222
+ @frameworks
223
+ end
224
+ end
225
+ end
226
+ end
227
+
@@ -0,0 +1,56 @@
1
+ module VMC::Cli::Command
2
+ class Manifest < Base
3
+ include VMC::Cli::ManifestHelper
4
+
5
+ def initialize(options)
6
+ super
7
+
8
+ # don't resolve any of the manifest template stuff
9
+ if manifest_file
10
+ @manifest = load_manifest_structure manifest_file
11
+ else
12
+ @manifest = {}
13
+ end
14
+ end
15
+
16
+ def edit
17
+ build_manifest
18
+ save_manifest
19
+ end
20
+
21
+ def extend(which)
22
+ parent = load_manifest_structure which
23
+ @manifest = load_manifest_structure which
24
+
25
+ build_manifest
26
+
27
+ simplify(@manifest, parent)
28
+
29
+ @manifest["inherit"] ||= []
30
+ @manifest["inherit"] << which
31
+
32
+ save_manifest(ask("Save where?"))
33
+ end
34
+
35
+ private
36
+
37
+ def simplify(child, parent)
38
+ return unless child.is_a?(Hash) and parent.is_a?(Hash)
39
+
40
+ child.reject! do |k, v|
41
+ if v == parent[k]
42
+ puts "rejecting #{k}"
43
+ true
44
+ else
45
+ simplify(v, parent[k])
46
+ false
47
+ end
48
+ end
49
+ end
50
+
51
+ def build_manifest
52
+ @application = ask("Configure for which application?", :default => ".")
53
+ interact true
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,129 @@
1
+ module VMC::Cli::Command
2
+
3
+ class Misc < Base
4
+ def version
5
+ say "vmc #{VMC::Cli::VERSION}"
6
+ end
7
+
8
+ def target
9
+ return display JSON.pretty_generate({:target => target_url}) if @options[:json]
10
+ banner "[#{target_url}]"
11
+ end
12
+
13
+ def targets
14
+ targets = VMC::Cli::Config.targets
15
+ return display JSON.pretty_generate(targets) if @options[:json]
16
+ return display 'None specified' if targets.empty?
17
+ targets_table = table do |t|
18
+ t.headings = 'Target', 'Authorization'
19
+ targets.each { |target, token| t << [target, token] }
20
+ end
21
+ display "\n"
22
+ display targets_table
23
+ end
24
+
25
+ alias :tokens :targets
26
+
27
+ def set_target(target_url)
28
+ target_url = "http://#{target_url}" unless /^https?/ =~ target_url
29
+ target_url = target_url.gsub(/\/+$/, '')
30
+ client = VMC::Client.new(target_url)
31
+ unless client.target_valid?
32
+ if prompt_ok
33
+ display "Host is not available or is not valid: '#{target_url}'".red
34
+ show_response = ask "Would you like see the response?",
35
+ :default => false
36
+ display "\n<<<\n#{client.raw_info}\n>>>\n" if show_response
37
+ end
38
+ exit(false)
39
+ else
40
+ VMC::Cli::Config.store_target(target_url)
41
+ say "Successfully targeted to [#{target_url}]".green
42
+ end
43
+ end
44
+
45
+ def info
46
+ info = client_info
47
+ return display JSON.pretty_generate(info) if @options[:json]
48
+
49
+ display "\n#{info[:description]}"
50
+ display "For support visit #{info[:support]}"
51
+ display ""
52
+ display "Target: #{target_url} (v#{info[:version]})"
53
+ display "Client: v#{VMC::Cli::VERSION}"
54
+ if info[:user]
55
+ display ''
56
+ display "User: #{info[:user]}"
57
+ end
58
+ if usage = info[:usage] and limits = info[:limits]
59
+ tmem = pretty_size(limits[:memory]*1024*1024)
60
+ mem = pretty_size(usage[:memory]*1024*1024)
61
+ tser = limits[:services]
62
+ ser = usage[:services]
63
+ tapps = limits[:apps] || 0
64
+ apps = usage[:apps] || 0
65
+ display "Usage: Memory (#{mem} of #{tmem} total)"
66
+ display " Services (#{ser} of #{tser} total)"
67
+ display " Apps (#{apps} of #{tapps} total)" if limits[:apps]
68
+ end
69
+ end
70
+
71
+ def runtimes
72
+ raise VMC::Client::AuthError unless client.logged_in?
73
+ return display JSON.pretty_generate(runtimes_info) if @options[:json]
74
+ return display "No Runtimes" if runtimes_info.empty?
75
+ rtable = table do |t|
76
+ t.headings = 'Name', 'Description', 'Version'
77
+ runtimes_info.each_value { |rt| t << [rt[:name], rt[:description], rt[:version]] }
78
+ end
79
+ display "\n"
80
+ display rtable
81
+ end
82
+
83
+ def frameworks
84
+ raise VMC::Client::AuthError unless client.logged_in?
85
+ return display JSON.pretty_generate(frameworks_info) if @options[:json]
86
+ return display "No Frameworks" if frameworks_info.empty?
87
+ rtable = table do |t|
88
+ t.headings = ['Name']
89
+ frameworks_info.each { |f| t << f }
90
+ end
91
+ display "\n"
92
+ display rtable
93
+ end
94
+
95
+ def aliases
96
+ aliases = VMC::Cli::Config.aliases
97
+ return display JSON.pretty_generate(aliases) if @options[:json]
98
+ return display "No Aliases" if aliases.empty?
99
+ atable = table do |t|
100
+ t.headings = 'Alias', 'Command'
101
+ aliases.each { |k,v| t << [k, v] }
102
+ end
103
+ display "\n"
104
+ display atable
105
+ end
106
+
107
+ def alias(k, v=nil)
108
+ k,v = k.split('=') unless v
109
+ aliases = VMC::Cli::Config.aliases
110
+ aliases[k] = v
111
+ VMC::Cli::Config.store_aliases(aliases)
112
+ display "Successfully aliased '#{k}' to '#{v}'".green
113
+ end
114
+
115
+ def unalias(key)
116
+ aliases = VMC::Cli::Config.aliases
117
+ if aliases.has_key?(key)
118
+ aliases.delete(key)
119
+ VMC::Cli::Config.store_aliases(aliases)
120
+ display "Successfully unaliased '#{key}'".green
121
+ else
122
+ display "Unknown alias '#{key}'".red
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ end
129
+
@@ -0,0 +1,180 @@
1
+ require "uuidtools"
2
+
3
+ module VMC::Cli::Command
4
+
5
+ class Services < Base
6
+ include VMC::Cli::ServicesHelper
7
+ include VMC::Cli::TunnelHelper
8
+
9
+ def services
10
+ ss = client.services_info
11
+ ps = client.services
12
+ ps.sort! {|a, b| a[:name] <=> b[:name] }
13
+
14
+ if @options[:json]
15
+ services = { :system => ss, :provisioned => ps }
16
+ return display JSON.pretty_generate(services)
17
+ end
18
+ display_system_services(ss)
19
+ display_provisioned_services(ps)
20
+ end
21
+
22
+ def create_service(service=nil, name=nil, appname=nil)
23
+ unless no_prompt || service
24
+ services = client.services_info
25
+ err 'No services available to provision' if services.empty?
26
+ service = ask(
27
+ "Which service would you like to provision?",
28
+ { :indexed => true,
29
+ :choices =>
30
+ services.values.collect { |type|
31
+ type.keys.collect(&:to_s)
32
+ }.flatten
33
+ }
34
+ )
35
+ end
36
+ name = @options[:name] unless name
37
+ unless name
38
+ name = random_service_name(service)
39
+ picked_name = true
40
+ end
41
+ create_service_banner(service, name, picked_name)
42
+ appname = @options[:bind] unless appname
43
+ bind_service_banner(name, appname) if appname
44
+ end
45
+
46
+ def delete_service(service=nil)
47
+ unless no_prompt || service
48
+ user_services = client.services
49
+ err 'No services available to delete' if user_services.empty?
50
+ service = ask(
51
+ "Which service would you like to delete?",
52
+ { :indexed => true,
53
+ :choices => user_services.collect { |s| s[:name] }
54
+ }
55
+ )
56
+ end
57
+ err "Service name required." unless service
58
+ display "Deleting service [#{service}]: ", false
59
+ client.delete_service(service)
60
+ display 'OK'.green
61
+ end
62
+
63
+ def bind_service(service, appname)
64
+ bind_service_banner(service, appname)
65
+ end
66
+
67
+ def unbind_service(service, appname)
68
+ unbind_service_banner(service, appname)
69
+ end
70
+
71
+ def clone_services(src_app, dest_app)
72
+ begin
73
+ src = client.app_info(src_app)
74
+ dest = client.app_info(dest_app)
75
+ rescue
76
+ end
77
+
78
+ err "Application '#{src_app}' does not exist" unless src
79
+ err "Application '#{dest_app}' does not exist" unless dest
80
+
81
+ services = src[:services]
82
+ err 'No services to clone' unless services && !services.empty?
83
+ services.each { |service| bind_service_banner(service, dest_app, false) }
84
+ check_app_for_restart(dest_app)
85
+ end
86
+
87
+ def tunnel(service=nil, client_name=nil)
88
+ unless defined? Caldecott
89
+ display "To use `vmc tunnel', you must first install Caldecott:"
90
+ display ""
91
+ display "\tgem install caldecott"
92
+ display ""
93
+ display "Note that you'll need a C compiler. If you're on OS X, Xcode"
94
+ display "will provide one. If you're on Windows, try DevKit."
95
+ display ""
96
+ display "This manual step will be removed in the future."
97
+ display ""
98
+ err "Caldecott is not installed."
99
+ end
100
+
101
+ ps = client.services
102
+ err "No services available to tunnel to" if ps.empty?
103
+
104
+ unless service
105
+ choices = ps.collect { |s| s[:name] }.sort
106
+ service = ask(
107
+ "Which service to tunnel to?",
108
+ :choices => choices,
109
+ :indexed => true
110
+ )
111
+ end
112
+
113
+ info = ps.select { |s| s[:name] == service }.first
114
+
115
+ err "Unknown service '#{service}'" unless info
116
+
117
+ port = pick_tunnel_port(@options[:port] || 10000)
118
+
119
+ raise VMC::Client::AuthError unless client.logged_in?
120
+
121
+ if not tunnel_pushed?
122
+ display "Deploying tunnel application '#{tunnel_appname}'."
123
+ auth = UUIDTools::UUID.random_create.to_s
124
+ push_caldecott(auth)
125
+ bind_service_banner(service, tunnel_appname, false)
126
+ start_caldecott
127
+ else
128
+ auth = tunnel_auth
129
+ end
130
+
131
+ if not tunnel_healthy?(auth)
132
+ display "Redeploying tunnel application '#{tunnel_appname}'."
133
+
134
+ # We don't expect caldecott not to be running, so take the
135
+ # most aggressive restart method.. delete/re-push
136
+ client.delete_app(tunnel_appname)
137
+ invalidate_tunnel_app_info
138
+
139
+ push_caldecott(auth)
140
+ bind_service_banner(service, tunnel_appname, false)
141
+ start_caldecott
142
+ end
143
+
144
+ if not tunnel_bound?(service)
145
+ bind_service_banner(service, tunnel_appname)
146
+ end
147
+
148
+ conn_info = tunnel_connection_info info[:vendor], service, auth
149
+ display_tunnel_connection_info(conn_info)
150
+ display "Starting tunnel to #{service.bold} on port #{port.to_s.bold}."
151
+ start_tunnel(port, conn_info, auth)
152
+
153
+ clients = get_clients_for(info[:vendor])
154
+
155
+ if clients.empty?
156
+ client_name ||= "none"
157
+ else
158
+ client_name ||= ask(
159
+ "Which client would you like to start?",
160
+ :choices => ["none"] + clients.keys,
161
+ :indexed => true
162
+ )
163
+ end
164
+
165
+ if client_name == "none"
166
+ wait_for_tunnel_end
167
+ else
168
+ wait_for_tunnel_start(port)
169
+ unless start_local_prog(clients, client_name, conn_info, port)
170
+ err "'#{client_name}' executation failed; is it in your $PATH?"
171
+ end
172
+ end
173
+ end
174
+
175
+ def get_clients_for(type)
176
+ conf = VMC::Cli::Config.clients
177
+ conf[type] || {}
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,65 @@
1
+ module VMC::Cli::Command
2
+
3
+ class User < Base
4
+
5
+ def info
6
+ info = client_info
7
+ username = info[:user] || 'N/A'
8
+ return display JSON.pretty_generate([username]) if @options[:json]
9
+ display "\n[#{username}]"
10
+ end
11
+
12
+ def login(email=nil)
13
+ email = @options[:email] unless email
14
+ password = @options[:password]
15
+ tries ||= 0
16
+
17
+ unless no_prompt
18
+ display "Attempting login to [#{target_url}]" if target_url
19
+ email ||= ask("Email")
20
+ password ||= ask("Password", :echo => "*")
21
+ end
22
+
23
+ err "Need a valid email" unless email
24
+ err "Need a password" unless password
25
+ login_and_save_token(email, password)
26
+ say "Successfully logged into [#{target_url}]".green
27
+ rescue VMC::Client::TargetError
28
+ display "Problem with login, invalid account or password when attempting to login to '#{target_url}'".red
29
+ retry if (tries += 1) < 3 && prompt_ok && !@options[:password]
30
+ exit 1
31
+ rescue => e
32
+ display "Problem with login to '#{target_url}', #{e}, try again or register for an account.".red
33
+ exit 1
34
+ end
35
+
36
+ def logout
37
+ VMC::Cli::Config.remove_token_file
38
+ say "Successfully logged out of [#{target_url}]".green
39
+ end
40
+
41
+ def change_password(password=nil)
42
+ info = client_info
43
+ email = info[:user]
44
+ err "Need to be logged in to change password." unless email
45
+ say "Changing password for '#{email}'\n"
46
+ unless no_prompt
47
+ password = ask "New Password", :echo => "*"
48
+ password2 = ask "Verify Password", :echo => "*"
49
+ err "Passwords did not match, try again" if password != password2
50
+ end
51
+ err "Password required" unless password
52
+ client.change_password(password)
53
+ say "\nSuccessfully changed password".green
54
+ end
55
+
56
+ private
57
+
58
+ def login_and_save_token(email, password)
59
+ token = client.login(email, password)
60
+ VMC::Cli::Config.store_token(token, @options[:token_file])
61
+ end
62
+
63
+ end
64
+
65
+ end