vmc-tsuru 0.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
@@ -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