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.
- data/LICENSE +24 -0
- data/README.md +113 -0
- data/Rakefile +101 -0
- data/bin/udn +6 -0
- data/caldecott_helper/Gemfile +10 -0
- data/caldecott_helper/Gemfile.lock +48 -0
- data/caldecott_helper/server.rb +43 -0
- data/config/clients.yml +17 -0
- data/config/micro/offline.conf +2 -0
- data/config/micro/paths.yml +22 -0
- data/config/micro/refresh_ip.rb +20 -0
- data/lib/cli.rb +47 -0
- data/lib/cli/commands/admin.rb +80 -0
- data/lib/cli/commands/apps.rb +1129 -0
- data/lib/cli/commands/base.rb +228 -0
- data/lib/cli/commands/manifest.rb +56 -0
- data/lib/cli/commands/micro.rb +115 -0
- data/lib/cli/commands/misc.rb +129 -0
- data/lib/cli/commands/services.rb +259 -0
- data/lib/cli/commands/user.rb +65 -0
- data/lib/cli/config.rb +173 -0
- data/lib/cli/console_helper.rb +170 -0
- data/lib/cli/core_ext.rb +122 -0
- data/lib/cli/errors.rb +19 -0
- data/lib/cli/frameworks.rb +266 -0
- data/lib/cli/manifest_helper.rb +323 -0
- data/lib/cli/runner.rb +556 -0
- data/lib/cli/services_helper.rb +109 -0
- data/lib/cli/tunnel_helper.rb +332 -0
- data/lib/cli/usage.rb +124 -0
- data/lib/cli/version.rb +7 -0
- data/lib/cli/zip_util.rb +77 -0
- data/lib/vmc.rb +3 -0
- data/lib/vmc/client.rb +562 -0
- data/lib/vmc/const.rb +23 -0
- data/lib/vmc/micro.rb +56 -0
- data/lib/vmc/micro/switcher/base.rb +97 -0
- data/lib/vmc/micro/switcher/darwin.rb +19 -0
- data/lib/vmc/micro/switcher/dummy.rb +15 -0
- data/lib/vmc/micro/switcher/linux.rb +16 -0
- data/lib/vmc/micro/switcher/windows.rb +31 -0
- data/lib/vmc/micro/vmrun.rb +168 -0
- 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
|