udn 0.3.23.0.pre

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.
Files changed (43) hide show
  1. data/LICENSE +24 -0
  2. data/README.md +113 -0
  3. data/Rakefile +101 -0
  4. data/bin/udn +6 -0
  5. data/caldecott_helper/Gemfile +10 -0
  6. data/caldecott_helper/Gemfile.lock +48 -0
  7. data/caldecott_helper/server.rb +43 -0
  8. data/config/clients.yml +17 -0
  9. data/config/micro/offline.conf +2 -0
  10. data/config/micro/paths.yml +22 -0
  11. data/config/micro/refresh_ip.rb +20 -0
  12. data/lib/cli.rb +47 -0
  13. data/lib/cli/commands/admin.rb +80 -0
  14. data/lib/cli/commands/apps.rb +1129 -0
  15. data/lib/cli/commands/base.rb +228 -0
  16. data/lib/cli/commands/manifest.rb +56 -0
  17. data/lib/cli/commands/micro.rb +115 -0
  18. data/lib/cli/commands/misc.rb +129 -0
  19. data/lib/cli/commands/services.rb +259 -0
  20. data/lib/cli/commands/user.rb +65 -0
  21. data/lib/cli/config.rb +173 -0
  22. data/lib/cli/console_helper.rb +170 -0
  23. data/lib/cli/core_ext.rb +122 -0
  24. data/lib/cli/errors.rb +19 -0
  25. data/lib/cli/frameworks.rb +266 -0
  26. data/lib/cli/manifest_helper.rb +323 -0
  27. data/lib/cli/runner.rb +556 -0
  28. data/lib/cli/services_helper.rb +109 -0
  29. data/lib/cli/tunnel_helper.rb +332 -0
  30. data/lib/cli/usage.rb +124 -0
  31. data/lib/cli/version.rb +7 -0
  32. data/lib/cli/zip_util.rb +77 -0
  33. data/lib/vmc.rb +3 -0
  34. data/lib/vmc/client.rb +562 -0
  35. data/lib/vmc/const.rb +23 -0
  36. data/lib/vmc/micro.rb +56 -0
  37. data/lib/vmc/micro/switcher/base.rb +97 -0
  38. data/lib/vmc/micro/switcher/darwin.rb +19 -0
  39. data/lib/vmc/micro/switcher/dummy.rb +15 -0
  40. data/lib/vmc/micro/switcher/linux.rb +16 -0
  41. data/lib/vmc/micro/switcher/windows.rb +31 -0
  42. data/lib/vmc/micro/vmrun.rb +168 -0
  43. metadata +310 -0
@@ -0,0 +1,259 @@
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, plan=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
+ plans = service_plans(service, services)
36
+ if plans.size > 1
37
+ plan = ask(
38
+ "Which plan would you like to select?",
39
+ { :indexed => true,
40
+ :choices => plans
41
+ }
42
+ )
43
+ else
44
+ plan = plans[0]
45
+ end
46
+ end
47
+ name = @options[:name] unless name
48
+ unless name
49
+ name = random_service_name(service)
50
+ picked_name = true
51
+ end
52
+ plan ||= @options[:plan] || "free"
53
+ create_service_banner(service, name, picked_name, plan)
54
+ appname = @options[:bind] unless appname
55
+ bind_service_banner(name, appname) if appname
56
+ end
57
+
58
+ def delete_service(service=nil)
59
+ unless no_prompt || service
60
+ user_services = client.services
61
+ err 'No services available to delete' if user_services.empty?
62
+ service = ask(
63
+ "Which service would you like to delete?",
64
+ { :indexed => true,
65
+ :choices => user_services.collect { |s| s[:name] }
66
+ }
67
+ )
68
+ end
69
+ err "Service name required." unless service
70
+ display "Deleting service [#{service}]: ", false
71
+ client.delete_service(service)
72
+ display 'OK'.green
73
+ end
74
+
75
+ def bind_service(service, appname)
76
+ bind_service_banner(service, appname)
77
+ end
78
+
79
+ def unbind_service(service, appname)
80
+ unbind_service_banner(service, appname)
81
+ end
82
+
83
+ def clone_services(src_app, dest_app)
84
+ begin
85
+ src = client.app_info(src_app)
86
+ dest = client.app_info(dest_app)
87
+ rescue
88
+ end
89
+
90
+ err "Application '#{src_app}' does not exist" unless src
91
+ err "Application '#{dest_app}' does not exist" unless dest
92
+
93
+ services = src[:services]
94
+ err 'No services to clone' unless services && !services.empty?
95
+ services.each { |service| bind_service_banner(service, dest_app, false) }
96
+ check_app_for_restart(dest_app)
97
+ end
98
+
99
+ def tunnel(service=nil, client_name=nil)
100
+ unless defined? Caldecott
101
+ display "To use `vmc tunnel', you must first install Caldecott:"
102
+ display ""
103
+ display "\tgem install caldecott"
104
+ display ""
105
+ display "Note that you'll need a C compiler. If you're on OS X, Xcode"
106
+ display "will provide one. If you're on Windows, try DevKit."
107
+ display ""
108
+ display "This manual step will be removed in the future."
109
+ display ""
110
+ err "Caldecott is not installed."
111
+ end
112
+
113
+ ps = client.services
114
+ err "No services available to tunnel to" if ps.empty?
115
+
116
+ unless service
117
+ choices = ps.collect { |s| s[:name] }.sort
118
+ service = ask(
119
+ "Which service to tunnel to?",
120
+ :choices => choices,
121
+ :indexed => true
122
+ )
123
+ end
124
+
125
+ info = ps.select { |s| s[:name] == service }.first
126
+
127
+ err "Unknown service '#{service}'" unless info
128
+
129
+ port = pick_tunnel_port(@options[:port] || 10000)
130
+
131
+ raise VMC::Client::AuthError unless client.logged_in?
132
+
133
+ if not tunnel_pushed?
134
+ display "Deploying tunnel application '#{tunnel_appname}'."
135
+ auth = UUIDTools::UUID.random_create.to_s
136
+ push_caldecott(auth)
137
+ bind_service_banner(service, tunnel_appname, false)
138
+ start_caldecott
139
+ else
140
+ auth = tunnel_auth
141
+ end
142
+
143
+ if not tunnel_healthy?(auth)
144
+ display "Redeploying tunnel application '#{tunnel_appname}'."
145
+
146
+ # We don't expect caldecott not to be running, so take the
147
+ # most aggressive restart method.. delete/re-push
148
+ client.delete_app(tunnel_appname)
149
+ invalidate_tunnel_app_info
150
+
151
+ push_caldecott(auth)
152
+ bind_service_banner(service, tunnel_appname, false)
153
+ start_caldecott
154
+ end
155
+
156
+ if not tunnel_bound?(service)
157
+ bind_service_banner(service, tunnel_appname)
158
+ end
159
+
160
+ conn_info = tunnel_connection_info info[:vendor], service, auth
161
+ display_tunnel_connection_info(conn_info)
162
+ display "Starting tunnel to #{service.bold} on port #{port.to_s.bold}."
163
+ start_tunnel(port, conn_info, auth)
164
+
165
+ clients = get_clients_for(info[:vendor])
166
+
167
+ if clients.empty?
168
+ client_name ||= "none"
169
+ else
170
+ client_name ||= ask(
171
+ "Which client would you like to start?",
172
+ :choices => ["none"] + clients.keys,
173
+ :indexed => true
174
+ )
175
+ end
176
+
177
+ if client_name == "none"
178
+ wait_for_tunnel_end
179
+ else
180
+ wait_for_tunnel_start(port)
181
+ unless start_local_prog(clients, client_name, conn_info, port)
182
+ err "'#{client_name}' execution failed; is it in your $PATH?"
183
+ end
184
+ end
185
+ end
186
+
187
+ def register_service(service=nil, instance=nil, name=nil)
188
+ service = ask_service unless service
189
+ instance = ask_instance(service) unless instance
190
+
191
+ name = @options[:name] unless name
192
+ unless name
193
+ name = random_service_name(service)
194
+ picked_name = true
195
+ end
196
+
197
+ options = {}
198
+ if service == 'cloudnrdb'
199
+ options = ask_cloudn_rdb(instance, options)
200
+ end
201
+
202
+ register_service_banner(service, instance, name, options, picked_name)
203
+ end
204
+
205
+ def ask_service
206
+ services = client.external_services_info
207
+ err 'No external services available to register' if services.empty?
208
+ service = ask(
209
+ "Which service would you like to register?",
210
+ { :indexed => true,
211
+ :choices =>
212
+ services.values.collect { |type|
213
+ type.keys.collect(&:to_s)
214
+ }.flatten
215
+ }
216
+ )
217
+ end
218
+
219
+ def ask_instance(service)
220
+ instances = client.external_service_instances(service).collect {|item|
221
+ item[:name]
222
+ }
223
+ err "No instance in your #{service}" if instances.empty?
224
+
225
+ instance = ask(
226
+ "Which instance would you like to register?",
227
+ { :indexed => true,
228
+ :choices => instances
229
+ }
230
+ )
231
+ end
232
+
233
+ def ask_cloudn_rdb(instance, options)
234
+ options[:credentials] = {}
235
+ options[:credentials][:MasterUsername] = @options[:MasterUsername] || ask("Master Username")
236
+ options[:credentials][:MasterUserPassword] = @options[:MasterUserPassword] || ask("Master Password")
237
+
238
+ databases = client.rdb_databases("cloudnrdb", instance, options[:credentials])
239
+ err "No database in your Cloudn RDB instance" unless databases[:properties][:databases][:unregistered]
240
+ if @options[:DBName]
241
+ err "No such unregistered database in your Cloudn RDB instance" unless options[:properties][:databases][:unregistered].include?(@options[:DBName])
242
+ options[:database] = @options[:DBName]
243
+ else
244
+ options[:database] = ask(
245
+ "Which database would you like to register?",
246
+ { :indexed => true,
247
+ :choices => databases[:properties][:databases][:unregistered]
248
+ }
249
+ )
250
+ end
251
+ return options
252
+ end
253
+
254
+ def get_clients_for(type)
255
+ conf = VMC::Cli::Config.clients
256
+ conf[type] || {}
257
+ end
258
+ end
259
+ 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
data/lib/cli/config.rb ADDED
@@ -0,0 +1,173 @@
1
+ require "yaml"
2
+ require 'fileutils'
3
+
4
+ require 'rubygems'
5
+ require 'json/pure'
6
+
7
+ module VMC::Cli
8
+ class Config
9
+
10
+ DEFAULT_TARGET = 'api.vcap.me'
11
+
12
+ TARGET_FILE = '~/.udn_target'
13
+ TOKEN_FILE = '~/.udn_token'
14
+ INSTANCES_FILE = '~/.udn_instances'
15
+ ALIASES_FILE = '~/.udn_aliases'
16
+ CLIENTS_FILE = '~/.udn_clients'
17
+ MICRO_FILE = '~/.udn_micro'
18
+
19
+ STOCK_CLIENTS = File.expand_path("../../../config/clients.yml", __FILE__)
20
+
21
+ class << self
22
+ attr_accessor :colorize
23
+ attr_accessor :output
24
+ attr_accessor :trace
25
+ attr_accessor :nozip
26
+
27
+ def target_url
28
+ return @target_url if @target_url
29
+ target_file = File.expand_path(TARGET_FILE)
30
+ if File.exists? target_file
31
+ @target_url = lock_and_read(target_file).strip
32
+ else
33
+ @target_url = DEFAULT_TARGET
34
+ end
35
+ @target_url = "http://#{@target_url}" unless /^https?/ =~ @target_url
36
+ @target_url = @target_url.gsub(/\/+$/, '')
37
+ @target_url
38
+ end
39
+
40
+ def base_of(url)
41
+ url.sub(/^[^\.]+\./, "")
42
+ end
43
+
44
+ def suggest_url
45
+ @suggest_url ||= base_of(target_url)
46
+ end
47
+
48
+ def store_target(target_host)
49
+ target_file = File.expand_path(TARGET_FILE)
50
+ lock_and_write(target_file, target_host)
51
+ end
52
+
53
+ def all_tokens(token_file_path=nil)
54
+ token_file = File.expand_path(token_file_path || TOKEN_FILE)
55
+ return nil unless File.exists? token_file
56
+ contents = lock_and_read(token_file).strip
57
+ JSON.parse(contents)
58
+ end
59
+
60
+ alias :targets :all_tokens
61
+
62
+ def auth_token(token_file_path=nil)
63
+ return @token if @token
64
+ tokens = all_tokens(token_file_path)
65
+ @token = tokens[target_url] if tokens
66
+ end
67
+
68
+ def remove_token_file
69
+ FileUtils.rm_f(File.expand_path(TOKEN_FILE))
70
+ end
71
+
72
+ def store_token(token, token_file_path=nil)
73
+ tokens = all_tokens(token_file_path) || {}
74
+ tokens[target_url] = token
75
+ token_file = File.expand_path(token_file_path || TOKEN_FILE)
76
+ lock_and_write(token_file, tokens.to_json)
77
+ end
78
+
79
+ def instances
80
+ instances_file = File.expand_path(INSTANCES_FILE)
81
+ return nil unless File.exists? instances_file
82
+ contents = lock_and_read(instances_file).strip
83
+ JSON.parse(contents)
84
+ end
85
+
86
+ def store_instances(instances)
87
+ instances_file = File.expand_path(INSTANCES_FILE)
88
+ lock_and_write(instances_file, instances.to_json)
89
+ end
90
+
91
+ def aliases
92
+ aliases_file = File.expand_path(ALIASES_FILE)
93
+ # bacward compatible
94
+ unless File.exists? aliases_file
95
+ old_aliases_file = File.expand_path('~/.udn-aliases')
96
+ FileUtils.mv(old_aliases_file, aliases_file) if File.exists? old_aliases_file
97
+ end
98
+ aliases = YAML.load_file(aliases_file) rescue {}
99
+ end
100
+
101
+ def store_aliases(aliases)
102
+ aliases_file = File.expand_path(ALIASES_FILE)
103
+ File.open(aliases_file, 'wb') {|f| f.write(aliases.to_yaml)}
104
+ end
105
+
106
+ def micro
107
+ micro_file = File.expand_path(MICRO_FILE)
108
+ return {} unless File.exists? micro_file
109
+ contents = lock_and_read(micro_file).strip
110
+ JSON.parse(contents)
111
+ end
112
+
113
+ def store_micro(micro)
114
+ micro_file = File.expand_path(MICRO_FILE)
115
+ lock_and_write(micro_file, micro.to_json)
116
+ end
117
+
118
+ def deep_merge(a, b)
119
+ merge = proc do |_, old, new|
120
+ if new.is_a?(Hash) and old.is_a?(Hash)
121
+ old.merge(new, &merge)
122
+ else
123
+ new
124
+ end
125
+ end
126
+
127
+ a.merge(b, &merge)
128
+ end
129
+
130
+ def clients
131
+ return @clients if @clients
132
+
133
+ stock = YAML.load_file(STOCK_CLIENTS)
134
+ clients = File.expand_path CLIENTS_FILE
135
+ if File.exists? clients
136
+ user = YAML.load_file(clients)
137
+ @clients = deep_merge(stock, user)
138
+ else
139
+ @clients = stock
140
+ end
141
+ end
142
+
143
+ def lock_and_read(file)
144
+ File.open(file, File::RDONLY) {|f|
145
+ if defined? JRUBY_VERSION
146
+ f.flock(File::LOCK_SH)
147
+ else
148
+ f.flock(File::LOCK_EX)
149
+ end
150
+ contents = f.read
151
+ f.flock(File::LOCK_UN)
152
+ contents
153
+ }
154
+ end
155
+
156
+ def lock_and_write(file, contents)
157
+ File.open(file, File::RDWR | File::CREAT, 0600) {|f|
158
+ f.flock(File::LOCK_EX)
159
+ f.rewind
160
+ f.puts contents
161
+ f.flush
162
+ f.truncate(f.pos)
163
+ f.flock(File::LOCK_UN)
164
+ }
165
+ end
166
+ end
167
+
168
+ def initialize(work_dir = Dir.pwd)
169
+ @work_dir = work_dir
170
+ end
171
+
172
+ end
173
+ end