paasio 0.3.16.beta.2

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,230 @@
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
+ disable_rewind
12
+
13
+ attr_reader :no_prompt, :prompt_ok
14
+
15
+ MANIFEST = "manifest.yml"
16
+
17
+ def initialize(options={})
18
+ @options = options.dup
19
+ @no_prompt = @options[:noprompts]
20
+ @prompt_ok = !no_prompt
21
+
22
+ # Suppress colorize on Windows systems for now.
23
+ if WINDOWS
24
+ VMC::Cli::Config.colorize = false
25
+ end
26
+
27
+ @path = @options[:path] || '.'
28
+
29
+ load_manifest manifest_file if manifest_file
30
+ end
31
+
32
+ def manifest_file
33
+ return @options[:manifest] if @options[:manifest]
34
+ return @manifest_file if @manifest_file
35
+
36
+ where = File.expand_path(@path)
37
+ while true
38
+ if File.exists?(File.join(where, MANIFEST))
39
+ @manifest_file = File.join(where, MANIFEST)
40
+ break
41
+ elsif File.basename(where) == "/"
42
+ @manifest_file = nil
43
+ break
44
+ else
45
+ where = File.expand_path("../", where)
46
+ end
47
+ end
48
+
49
+ @manifest_file
50
+ end
51
+
52
+ def load_manifest_structure(file)
53
+ manifest = YAML.load_file file
54
+
55
+ Array(manifest["inherit"]).each do |p|
56
+ manifest = merge_parent(manifest, p)
57
+ end
58
+
59
+ if apps = manifest["applications"]
60
+ apps.each do |k, v|
61
+ abs = File.expand_path(k, file)
62
+ if Dir.pwd.start_with? abs
63
+ manifest = merge_manifest(manifest, v)
64
+ end
65
+ end
66
+ end
67
+
68
+ manifest
69
+ end
70
+
71
+ def resolve_manifest(manifest)
72
+ if apps = manifest["applications"]
73
+ apps.each_value do |v|
74
+ resolve_lexically(v, [manifest])
75
+ end
76
+ end
77
+
78
+ resolve_lexically(manifest, [manifest])
79
+ end
80
+
81
+ def load_manifest(file)
82
+ @manifest = load_manifest_structure(file)
83
+ resolve_manifest(@manifest)
84
+ end
85
+
86
+ def merge_parent(child, path)
87
+ file = File.expand_path("../" + path, manifest_file)
88
+ merge_manifest(child, load_manifest_structure(file))
89
+ end
90
+
91
+ def merge_manifest(child, parent)
92
+ merge = proc do |_, old, new|
93
+ if new.is_a?(Hash) and old.is_a?(Hash)
94
+ old.merge(new, &merge)
95
+ else
96
+ new
97
+ end
98
+ end
99
+
100
+ parent.merge(child, &merge)
101
+ end
102
+
103
+ def resolve_lexically(val, ctx = [@manifest])
104
+ case val
105
+ when Hash
106
+ val.each_value do |v|
107
+ resolve_lexically(v, [val] + ctx)
108
+ end
109
+ when Array
110
+ val.each do |v|
111
+ resolve_lexically(v, ctx)
112
+ end
113
+ when String
114
+ val.gsub!(/\$\{([[:alnum:]\-]+)\}/) do
115
+ resolve_symbol($1, ctx)
116
+ end
117
+ end
118
+
119
+ nil
120
+ end
121
+
122
+ def resolve_symbol(sym, ctx)
123
+ case sym
124
+ when "target-base"
125
+ target_base(ctx)
126
+
127
+ when "target-url"
128
+ target_url(ctx)
129
+
130
+ when "random-word"
131
+ "%04x" % [rand(0x0100000)]
132
+
133
+ else
134
+ found = find_symbol(sym, ctx)
135
+
136
+ if found
137
+ resolve_lexically(found, ctx)
138
+ found
139
+ else
140
+ err(sym, "Unknown symbol in manifest: ")
141
+ end
142
+ end
143
+ end
144
+
145
+ def find_symbol(sym, ctx)
146
+ ctx.each do |h|
147
+ if val = resolve_in(h, sym)
148
+ return val
149
+ end
150
+ end
151
+
152
+ nil
153
+ end
154
+
155
+ def resolve_in(hash, *where)
156
+ find_in_hash(hash, ["properties"] + where) ||
157
+ find_in_hash(hash, ["applications", @application] + where) ||
158
+ find_in_hash(hash, where)
159
+ end
160
+
161
+ def manifest(*where)
162
+ resolve_in(@manifest, *where)
163
+ end
164
+
165
+ def find_in_hash(hash, where)
166
+ what = hash
167
+ where.each do |x|
168
+ return nil unless what.is_a?(Hash)
169
+ what = what[x]
170
+ end
171
+
172
+ what
173
+ end
174
+
175
+ def target_url(ctx = [])
176
+ find_symbol("target", ctx) || VMC::Cli::Config.target_url
177
+ end
178
+
179
+ def target_base(ctx = [])
180
+ if tgt = find_symbol("target", ctx)
181
+ VMC::Cli::Config.base_of(tgt)
182
+ else
183
+ VMC::Cli::Config.suggest_url
184
+ end
185
+ end
186
+
187
+ # Inject a client to help in testing.
188
+ def client(cli=nil)
189
+ @client ||= cli
190
+ return @client if @client
191
+ @client = VMC::Client.new(target_url, auth_token)
192
+ @client.trace = VMC::Cli::Config.trace if VMC::Cli::Config.trace
193
+ @client.proxy_for @options[:proxy] if @options[:proxy]
194
+ @client
195
+ end
196
+
197
+ def client_info
198
+ @client_info ||= client.info
199
+ end
200
+
201
+ def auth_token
202
+ @auth_token = VMC::Cli::Config.auth_token(@options[:token_file])
203
+ end
204
+
205
+ def runtimes_info
206
+ return @runtimes if @runtimes
207
+ info = client_info
208
+ @runtimes = {}
209
+ if info[:frameworks]
210
+ info[:frameworks].each_value do |f|
211
+ next unless f[:runtimes]
212
+ f[:runtimes].each { |r| @runtimes[r[:name]] = r}
213
+ end
214
+ end
215
+ @runtimes
216
+ end
217
+
218
+ def frameworks_info
219
+ return @frameworks if @frameworks
220
+ info = client_info
221
+ @frameworks = []
222
+ if info[:frameworks]
223
+ info[:frameworks].each_value { |f| @frameworks << [f[:name]] }
224
+ end
225
+ @frameworks
226
+ end
227
+ end
228
+ end
229
+ end
230
+
@@ -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 "paasio #{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,179 @@
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
+ start_tunnel(service, port, conn_info, auth)
151
+
152
+ clients = get_clients_for(info[:vendor])
153
+
154
+ if clients.empty?
155
+ client_name ||= "none"
156
+ else
157
+ client_name ||= ask(
158
+ "Which client would you like to start?",
159
+ :choices => ["none"] + clients.keys,
160
+ :indexed => true
161
+ )
162
+ end
163
+
164
+ if client_name == "none"
165
+ wait_for_tunnel_end
166
+ else
167
+ wait_for_tunnel_start(port)
168
+ unless start_local_prog(clients, client_name, conn_info, port)
169
+ err "'#{client_name}' executation failed; is it in your $PATH?"
170
+ end
171
+ end
172
+ end
173
+
174
+ def get_clients_for(type)
175
+ conf = VMC::Cli::Config.clients
176
+ conf[type] || {}
177
+ end
178
+ end
179
+ 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