af 0.3.12
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 +92 -0
- data/Rakefile +17 -0
- data/bin/af +6 -0
- data/lib/cli.rb +30 -0
- data/lib/cli/commands/admin.rb +77 -0
- data/lib/cli/commands/apps.rb +940 -0
- data/lib/cli/commands/base.rb +79 -0
- data/lib/cli/commands/misc.rb +128 -0
- data/lib/cli/commands/services.rb +86 -0
- data/lib/cli/commands/user.rb +60 -0
- data/lib/cli/config.rb +110 -0
- data/lib/cli/core_ext.rb +119 -0
- data/lib/cli/errors.rb +19 -0
- data/lib/cli/frameworks.rb +109 -0
- data/lib/cli/runner.rb +490 -0
- data/lib/cli/services_helper.rb +78 -0
- data/lib/cli/usage.rb +104 -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 +451 -0
- data/lib/vmc/const.rb +21 -0
- data/spec/assets/app_info.txt +9 -0
- data/spec/assets/app_listings.txt +9 -0
- data/spec/assets/bad_create_app.txt +9 -0
- data/spec/assets/delete_app.txt +9 -0
- data/spec/assets/global_service_listings.txt +9 -0
- data/spec/assets/good_create_app.txt +9 -0
- data/spec/assets/good_create_service.txt +9 -0
- data/spec/assets/info_authenticated.txt +27 -0
- data/spec/assets/info_return.txt +15 -0
- data/spec/assets/info_return_bad.txt +16 -0
- data/spec/assets/list_users.txt +13 -0
- data/spec/assets/login_fail.txt +9 -0
- data/spec/assets/login_success.txt +9 -0
- data/spec/assets/sample_token.txt +1 -0
- data/spec/assets/service_already_exists.txt +9 -0
- data/spec/assets/service_listings.txt +9 -0
- data/spec/assets/service_not_found.txt +9 -0
- data/spec/assets/user_info.txt +9 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/unit/cli_opts_spec.rb +68 -0
- data/spec/unit/client_spec.rb +332 -0
- metadata +221 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
module VMC::Cli
|
3
|
+
module ServicesHelper
|
4
|
+
def display_system_services(services=nil)
|
5
|
+
services ||= client.services_info
|
6
|
+
|
7
|
+
display "\n============== System Services ==============\n\n"
|
8
|
+
|
9
|
+
return display "No system services available" if services.empty?
|
10
|
+
|
11
|
+
displayed_services = []
|
12
|
+
services.each do |service_type, value|
|
13
|
+
value.each do |vendor, version|
|
14
|
+
version.each do |version_str, service|
|
15
|
+
displayed_services << [ vendor, version_str, service[:description] ]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
displayed_services.sort! { |a, b| a.first.to_s <=> b.first.to_s}
|
20
|
+
|
21
|
+
services_table = table do |t|
|
22
|
+
t.headings = 'Service', 'Version', 'Description'
|
23
|
+
displayed_services.each { |s| t << s }
|
24
|
+
end
|
25
|
+
display services_table
|
26
|
+
end
|
27
|
+
|
28
|
+
def display_provisioned_services(services=nil)
|
29
|
+
services ||= client.services
|
30
|
+
display "\n=========== Provisioned Services ============\n\n"
|
31
|
+
display_provisioned_services_table(services)
|
32
|
+
end
|
33
|
+
|
34
|
+
def display_provisioned_services_table(services)
|
35
|
+
return unless services && !services.empty?
|
36
|
+
services_table = table do |t|
|
37
|
+
t.headings = 'Name', 'Service'
|
38
|
+
services.each do |service|
|
39
|
+
t << [ service[:name], service[:vendor] ]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
display services_table
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_service_banner(service, name, display_name=false)
|
46
|
+
sn = " [#{name}]" if display_name
|
47
|
+
display "Creating Service#{sn}: ", false
|
48
|
+
client.create_service(service, name)
|
49
|
+
display 'OK'.green
|
50
|
+
end
|
51
|
+
|
52
|
+
def bind_service_banner(service, appname, check_restart=true)
|
53
|
+
display "Binding Service: ", false
|
54
|
+
client.bind_service(service, appname)
|
55
|
+
display 'OK'.green
|
56
|
+
check_app_for_restart(appname) if check_restart
|
57
|
+
end
|
58
|
+
|
59
|
+
def unbind_service_banner(service, appname, check_restart=true)
|
60
|
+
display "Unbinding Service: ", false
|
61
|
+
client.unbind_service(service, appname)
|
62
|
+
display 'OK'.green
|
63
|
+
check_app_for_restart(appname) if check_restart
|
64
|
+
end
|
65
|
+
|
66
|
+
def random_service_name(service)
|
67
|
+
r = "%04x" % [rand(0x0100000)]
|
68
|
+
"#{service.to_s}-#{r}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def check_app_for_restart(appname)
|
72
|
+
app = client.app_info(appname)
|
73
|
+
cmd = VMC::Cli::Command::Apps.new(@options)
|
74
|
+
cmd.restart(appname) if app[:state] == 'STARTED'
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
data/lib/cli/usage.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
class VMC::Cli::Runner
|
2
|
+
|
3
|
+
def basic_usage
|
4
|
+
"Usage: af [options] command [<args>] [command_options]\n" +
|
5
|
+
"Try 'af help [command]' or 'af help options' for more information."
|
6
|
+
end
|
7
|
+
|
8
|
+
def display_usage
|
9
|
+
if @usage
|
10
|
+
say @usage_error if @usage_error
|
11
|
+
say "Usage: #{@usage}"
|
12
|
+
return
|
13
|
+
elsif @verb_usage
|
14
|
+
say @verb_usage
|
15
|
+
return
|
16
|
+
end
|
17
|
+
say command_usage
|
18
|
+
end
|
19
|
+
|
20
|
+
def command_usage
|
21
|
+
<<-USAGE
|
22
|
+
|
23
|
+
#{basic_usage}
|
24
|
+
|
25
|
+
Currently available af commands are:
|
26
|
+
|
27
|
+
Getting Started
|
28
|
+
target [url] Reports current target or sets a new target
|
29
|
+
login [email] [--email, --passwd] Login
|
30
|
+
info System and account information
|
31
|
+
|
32
|
+
Applications
|
33
|
+
apps List deployed applications
|
34
|
+
|
35
|
+
Application Creation
|
36
|
+
push [appname] Create, push, map, and start a new application
|
37
|
+
push [appname] --path Push application from specified path
|
38
|
+
push [appname] --url Set the url for the application
|
39
|
+
push [appname] --instances <N> Set the expected number <N> of instances
|
40
|
+
push [appname] --mem M Set the memory reservation for the application
|
41
|
+
push [appname] --runtime RUNTIME Set the runtime to use for the application
|
42
|
+
push [appname] --no-start Do not auto-start the application
|
43
|
+
|
44
|
+
Application Operations
|
45
|
+
start <appname> Start the application
|
46
|
+
stop <appname> Stop the application
|
47
|
+
restart <appname> Restart the application
|
48
|
+
delete <appname> Delete the application
|
49
|
+
rename <appname> <newname> Rename the application
|
50
|
+
|
51
|
+
Application Updates
|
52
|
+
update <appname> [--path] Update the application bits
|
53
|
+
mem <appname> [memsize] Update the memory reservation for an application
|
54
|
+
map <appname> <url> Register the application to the url
|
55
|
+
unmap <appname> <url> Unregister the application from the url
|
56
|
+
instances <appname> <num|delta> Scale the application instances up or down
|
57
|
+
|
58
|
+
Application Information
|
59
|
+
crashes <appname> List recent application crashes
|
60
|
+
crashlogs <appname> Display log information for crashed applications
|
61
|
+
logs <appname> [--all] Display log information for the application
|
62
|
+
files <appname> [path] [--all] Display directory listing or file download for [path]
|
63
|
+
stats <appname> Display resource usage for the application
|
64
|
+
instances <appname> List application instances
|
65
|
+
|
66
|
+
Application Environment
|
67
|
+
env <appname> List application environment variables
|
68
|
+
env-add <appname> <variable[=]value> Add an environment variable to an application
|
69
|
+
env-del <appname> <variable> Delete an environment variable to an application
|
70
|
+
|
71
|
+
Services
|
72
|
+
services Lists of services available and provisioned
|
73
|
+
create-service <service> [--name,--bind] Create a provisioned service
|
74
|
+
create-service <service> <name> Create a provisioned service and assign it <name>
|
75
|
+
create-service <service> <name> <app> Create a provisioned service and assign it <name>, and bind to <app>
|
76
|
+
delete-service [servicename] Delete a provisioned service
|
77
|
+
bind-service <servicename> <appname> Bind a service to an application
|
78
|
+
unbind-service <servicename> <appname> Unbind service from the application
|
79
|
+
clone-services <src-app> <dest-app> Clone service bindings from <src-app> application to <dest-app>
|
80
|
+
|
81
|
+
Administration
|
82
|
+
user Display user account information
|
83
|
+
passwd Change the password for the current user
|
84
|
+
logout Logs current user out of the target system
|
85
|
+
add-user [--email, --passwd] Register a new user (requires admin privileges)
|
86
|
+
delete-user <user> Delete a user and all apps and services (requires admin privileges)
|
87
|
+
|
88
|
+
System
|
89
|
+
runtimes Display the supported runtimes of the target system
|
90
|
+
frameworks Display the recognized frameworks of the target system
|
91
|
+
|
92
|
+
Misc
|
93
|
+
aliases List aliases
|
94
|
+
alias <alias[=]command> Create an alias for a command
|
95
|
+
unalias <alias> Remove an alias
|
96
|
+
targets List known targets and associated authorization tokens
|
97
|
+
|
98
|
+
Help
|
99
|
+
help [command] Get general help or help on a specific command
|
100
|
+
help options Get help on available options
|
101
|
+
USAGE
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
data/lib/cli/version.rb
ADDED
data/lib/cli/zip_util.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
|
2
|
+
require 'zip/zipfilesystem'
|
3
|
+
|
4
|
+
module VMC::Cli
|
5
|
+
|
6
|
+
class ZipUtil
|
7
|
+
|
8
|
+
PACK_EXCLUSION_GLOBS = ['..', '.', '*~', '#*#', '*.log']
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def to_dev_null
|
13
|
+
if !!RUBY_PLATFORM['mingw'] || !!RUBY_PLATFORM['mswin32'] || !!RUBY_PLATFORM['cygwin']
|
14
|
+
'nul'
|
15
|
+
else
|
16
|
+
'/dev/null'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def entry_lines(file)
|
21
|
+
contents = nil
|
22
|
+
unless VMC::Cli::Config.nozip
|
23
|
+
contents = `unzip -l #{file} 2> #{to_dev_null}`
|
24
|
+
contents = nil if $? != 0
|
25
|
+
end
|
26
|
+
# Do Ruby version if told to or native version failed
|
27
|
+
unless contents
|
28
|
+
entries = []
|
29
|
+
Zip::ZipFile.foreach(file) { |zentry| entries << zentry }
|
30
|
+
contents = entries.join("\n")
|
31
|
+
end
|
32
|
+
contents
|
33
|
+
end
|
34
|
+
|
35
|
+
def unpack(file, dest)
|
36
|
+
unless VMC::Cli::Config.nozip
|
37
|
+
FileUtils.mkdir(dest)
|
38
|
+
`unzip -q #{file} -d #{dest} 2> #{to_dev_null}`
|
39
|
+
return unless $? != 0
|
40
|
+
end
|
41
|
+
# Do Ruby version if told to or native version failed
|
42
|
+
Zip::ZipFile.foreach(file) do |zentry|
|
43
|
+
epath = "#{dest}/#{zentry}"
|
44
|
+
dirname = File.dirname(epath)
|
45
|
+
FileUtils.mkdir_p(dirname) unless File.exists?(dirname)
|
46
|
+
zentry.extract(epath) unless File.exists?(epath)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_files_to_pack(dir)
|
51
|
+
Dir.glob("#{dir}/**/*", File::FNM_DOTMATCH).select do |f|
|
52
|
+
process = true
|
53
|
+
PACK_EXCLUSION_GLOBS.each { |e| process = false if File.fnmatch(e, File.basename(f)) }
|
54
|
+
process && File.exists?(f)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def pack(dir, zipfile)
|
59
|
+
unless VMC::Cli::Config.nozip
|
60
|
+
excludes = PACK_EXCLUSION_GLOBS.map { |e| "\\#{e}" }
|
61
|
+
excludes = excludes.join(' ')
|
62
|
+
Dir.chdir(dir) do
|
63
|
+
`zip -y -q -r #{zipfile} . -x #{excludes} 2> #{to_dev_null}`
|
64
|
+
return unless $? != 0
|
65
|
+
end
|
66
|
+
end
|
67
|
+
# Do Ruby version if told to or native version failed
|
68
|
+
Zip::ZipFile::open(zipfile, true) do |zf|
|
69
|
+
get_files_to_pack(dir).each do |f|
|
70
|
+
zf.add(f.sub("#{dir}/",''), f)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/vmc.rb
ADDED
data/lib/vmc/client.rb
ADDED
@@ -0,0 +1,451 @@
|
|
1
|
+
# VMC client
|
2
|
+
#
|
3
|
+
# Example:
|
4
|
+
#
|
5
|
+
# require 'vmc'
|
6
|
+
# client = VMC::Client.new('api.vcap.me')
|
7
|
+
# client.login(:user, :pass)
|
8
|
+
# client.create('myapplication', manifest)
|
9
|
+
# client.create_service('redis', 'my_redis_service', opts);
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'rubygems'
|
13
|
+
require 'json/pure'
|
14
|
+
require 'open-uri'
|
15
|
+
|
16
|
+
require File.expand_path('../const', __FILE__)
|
17
|
+
|
18
|
+
class VMC::Client
|
19
|
+
|
20
|
+
def self.version
|
21
|
+
VMC::VERSION
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :target, :host, :user, :proxy, :auth_token
|
25
|
+
attr_accessor :trace
|
26
|
+
|
27
|
+
# Error codes
|
28
|
+
VMC_HTTP_ERROR_CODES = [ 400, 403, 404, 500 ]
|
29
|
+
|
30
|
+
# Errors
|
31
|
+
class BadTarget < RuntimeError; end
|
32
|
+
class AuthError < RuntimeError; end
|
33
|
+
class TargetError < RuntimeError; end
|
34
|
+
class NotFound < RuntimeError; end
|
35
|
+
class BadResponse < RuntimeError; end
|
36
|
+
class HTTPException < RuntimeError; end
|
37
|
+
|
38
|
+
# Initialize new client to the target_uri with optional auth_token
|
39
|
+
def initialize(target_url=VMC::DEFAULT_TARGET, auth_token=nil)
|
40
|
+
target_url = "http://#{target_url}" unless /^https?/ =~ target_url
|
41
|
+
target_url = target_url.gsub(/\/+$/, '')
|
42
|
+
@target = target_url
|
43
|
+
@auth_token = auth_token
|
44
|
+
end
|
45
|
+
|
46
|
+
######################################################
|
47
|
+
# Target info
|
48
|
+
######################################################
|
49
|
+
|
50
|
+
# Retrieves information on the target cloud, and optionally the logged in user
|
51
|
+
def info
|
52
|
+
# TODO: Should merge for new version IMO, general, services, user_account
|
53
|
+
json_get(VMC::INFO_PATH)
|
54
|
+
end
|
55
|
+
|
56
|
+
def raw_info
|
57
|
+
http_get(VMC::INFO_PATH)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Global listing of services that are available on the target system
|
61
|
+
def services_info
|
62
|
+
check_login_status
|
63
|
+
json_get(VMC::GLOBAL_SERVICES_PATH)
|
64
|
+
end
|
65
|
+
|
66
|
+
######################################################
|
67
|
+
# Apps
|
68
|
+
######################################################
|
69
|
+
|
70
|
+
def apps
|
71
|
+
check_login_status
|
72
|
+
json_get(VMC::APPS_PATH)
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_app(name, manifest={})
|
76
|
+
check_login_status
|
77
|
+
app = manifest.dup
|
78
|
+
app[:name] = name
|
79
|
+
app[:instances] ||= 1
|
80
|
+
json_post(VMC::APPS_PATH, app)
|
81
|
+
end
|
82
|
+
|
83
|
+
def update_app(name, manifest)
|
84
|
+
check_login_status
|
85
|
+
json_put("#{VMC::APPS_PATH}/#{name}", manifest)
|
86
|
+
end
|
87
|
+
|
88
|
+
def upload_app(name, zipfile, resource_manifest=nil)
|
89
|
+
#FIXME, manifest should be allowed to be null, here for compatability with old cc's
|
90
|
+
resource_manifest ||= []
|
91
|
+
check_login_status
|
92
|
+
upload_data = {:_method => 'put'}
|
93
|
+
if zipfile
|
94
|
+
if zipfile.is_a? File
|
95
|
+
file = zipfile
|
96
|
+
else
|
97
|
+
file = File.new(zipfile, 'rb')
|
98
|
+
end
|
99
|
+
upload_data[:application] = file
|
100
|
+
end
|
101
|
+
upload_data[:resources] = resource_manifest.to_json if resource_manifest
|
102
|
+
http_post("#{VMC::APPS_PATH}/#{name}/application", upload_data)
|
103
|
+
end
|
104
|
+
|
105
|
+
def delete_app(name)
|
106
|
+
check_login_status
|
107
|
+
http_delete("#{VMC::APPS_PATH}/#{name}")
|
108
|
+
end
|
109
|
+
|
110
|
+
def app_info(name)
|
111
|
+
check_login_status
|
112
|
+
json_get("#{VMC::APPS_PATH}/#{name}")
|
113
|
+
end
|
114
|
+
|
115
|
+
def app_update_info(name)
|
116
|
+
check_login_status
|
117
|
+
json_get("#{VMC::APPS_PATH}/#{name}/update")
|
118
|
+
end
|
119
|
+
|
120
|
+
def app_stats(name)
|
121
|
+
check_login_status
|
122
|
+
stats_raw = json_get("#{VMC::APPS_PATH}/#{name}/stats")
|
123
|
+
stats = []
|
124
|
+
stats_raw.each_pair do |k, entry|
|
125
|
+
# Skip entries with no stats
|
126
|
+
next unless entry[:stats]
|
127
|
+
entry[:instance] = k.to_s.to_i
|
128
|
+
entry[:state] = entry[:state].to_sym if entry[:state]
|
129
|
+
stats << entry
|
130
|
+
end
|
131
|
+
stats.sort { |a,b| a[:instance] - b[:instance] }
|
132
|
+
end
|
133
|
+
|
134
|
+
def app_instances(name)
|
135
|
+
check_login_status
|
136
|
+
json_get("#{VMC::APPS_PATH}/#{name}/instances")
|
137
|
+
end
|
138
|
+
|
139
|
+
def app_crashes(name)
|
140
|
+
check_login_status
|
141
|
+
json_get("#{VMC::APPS_PATH}/#{name}/crashes")
|
142
|
+
end
|
143
|
+
|
144
|
+
# List the directory or download the actual file indicated by
|
145
|
+
# the path.
|
146
|
+
def app_files(name, path, instance=0)
|
147
|
+
check_login_status
|
148
|
+
url = "#{VMC::APPS_PATH}/#{name}/instances/#{instance}/files/#{path}"
|
149
|
+
url.gsub!('//', '/')
|
150
|
+
_, body, headers = http_get(url)
|
151
|
+
body
|
152
|
+
end
|
153
|
+
|
154
|
+
######################################################
|
155
|
+
# Services
|
156
|
+
######################################################
|
157
|
+
|
158
|
+
# listing of services that are available in the system
|
159
|
+
def services
|
160
|
+
check_login_status
|
161
|
+
json_get(VMC::SERVICES_PATH)
|
162
|
+
end
|
163
|
+
|
164
|
+
def create_service(service, name)
|
165
|
+
check_login_status
|
166
|
+
services = services_info
|
167
|
+
services ||= []
|
168
|
+
service_hash = nil
|
169
|
+
|
170
|
+
service = service.to_s
|
171
|
+
|
172
|
+
# FIXME!
|
173
|
+
services.each do |service_type, value|
|
174
|
+
value.each do |vendor, version|
|
175
|
+
version.each do |version_str, service_descr|
|
176
|
+
if service == service_descr[:vendor]
|
177
|
+
service_hash = {
|
178
|
+
:type => service_descr[:type], :tier => 'free',
|
179
|
+
:vendor => service, :version => version_str
|
180
|
+
}
|
181
|
+
break
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
raise TargetError, "Service [#{service}] is not a valid service choice" unless service_hash
|
188
|
+
service_hash[:name] = name
|
189
|
+
json_post(VMC::SERVICES_PATH, service_hash)
|
190
|
+
end
|
191
|
+
|
192
|
+
def delete_service(name)
|
193
|
+
check_login_status
|
194
|
+
svcs = services || []
|
195
|
+
names = svcs.collect { |s| s[:name] }
|
196
|
+
raise TargetError, "Service [#{name}] not a valid service" unless names.include? name
|
197
|
+
http_delete("#{VMC::SERVICES_PATH}/#{name}")
|
198
|
+
end
|
199
|
+
|
200
|
+
def bind_service(service, appname)
|
201
|
+
check_login_status
|
202
|
+
app = app_info(appname)
|
203
|
+
services = app[:services] || []
|
204
|
+
app[:services] = services << service
|
205
|
+
update_app(appname, app)
|
206
|
+
end
|
207
|
+
|
208
|
+
def unbind_service(service, appname)
|
209
|
+
check_login_status
|
210
|
+
app = app_info(appname)
|
211
|
+
services = app[:services] || []
|
212
|
+
services.delete(service)
|
213
|
+
app[:services] = services
|
214
|
+
update_app(appname, app)
|
215
|
+
end
|
216
|
+
|
217
|
+
######################################################
|
218
|
+
# Resources
|
219
|
+
######################################################
|
220
|
+
|
221
|
+
# Send in a resources manifest array to the system to have
|
222
|
+
# it check what is needed to actually send. Returns array
|
223
|
+
# indicating what is needed. This returned manifest should be
|
224
|
+
# sent in with the upload if resources were removed.
|
225
|
+
# E.g. [{:sha1 => xxx, :size => xxx, :fn => filename}]
|
226
|
+
def check_resources(resources)
|
227
|
+
check_login_status
|
228
|
+
status, body, headers = json_post(VMC::RESOURCES_PATH, resources)
|
229
|
+
json_parse(body)
|
230
|
+
end
|
231
|
+
|
232
|
+
######################################################
|
233
|
+
# Validation Helpers
|
234
|
+
######################################################
|
235
|
+
|
236
|
+
# Checks that the target is valid
|
237
|
+
def target_valid?
|
238
|
+
return false unless descr = info
|
239
|
+
return false unless descr[:name]
|
240
|
+
return false unless descr[:build]
|
241
|
+
return false unless descr[:version]
|
242
|
+
return false unless descr[:support]
|
243
|
+
true
|
244
|
+
rescue
|
245
|
+
false
|
246
|
+
end
|
247
|
+
|
248
|
+
# Checks that the auth_token is valid
|
249
|
+
def logged_in?
|
250
|
+
descr = info
|
251
|
+
if descr
|
252
|
+
return false unless descr[:user]
|
253
|
+
return false unless descr[:usage]
|
254
|
+
@user = descr[:user]
|
255
|
+
true
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
######################################################
|
260
|
+
# User login/password
|
261
|
+
######################################################
|
262
|
+
|
263
|
+
# login and return an auth_token
|
264
|
+
# Auth token can be retained and used in creating
|
265
|
+
# new clients, avoiding login.
|
266
|
+
def login(user, password)
|
267
|
+
status, body, headers = json_post("#{VMC::USERS_PATH}/#{user}/tokens", {:password => password})
|
268
|
+
response_info = json_parse(body)
|
269
|
+
if response_info
|
270
|
+
@user = user
|
271
|
+
@auth_token = response_info[:token]
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# sets the password for the current logged user
|
276
|
+
def change_password(new_password)
|
277
|
+
check_login_status
|
278
|
+
user_info = json_get("#{VMC::USERS_PATH}/#{@user}")
|
279
|
+
if user_info
|
280
|
+
user_info[:password] = new_password
|
281
|
+
json_put("#{VMC::USERS_PATH}/#{@user}", user_info)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
######################################################
|
286
|
+
# System administration
|
287
|
+
######################################################
|
288
|
+
|
289
|
+
def proxy=(proxy)
|
290
|
+
@proxy = proxy
|
291
|
+
end
|
292
|
+
|
293
|
+
def proxy_for(proxy)
|
294
|
+
@proxy = proxy
|
295
|
+
end
|
296
|
+
|
297
|
+
def users
|
298
|
+
check_login_status
|
299
|
+
json_get(VMC::USERS_PATH)
|
300
|
+
end
|
301
|
+
|
302
|
+
def add_user(user_email, password)
|
303
|
+
json_post(VMC::USERS_PATH, { :email => user_email, :password => password })
|
304
|
+
end
|
305
|
+
|
306
|
+
def delete_user(user_email)
|
307
|
+
check_login_status
|
308
|
+
http_delete("#{VMC::USERS_PATH}/#{user_email}")
|
309
|
+
end
|
310
|
+
|
311
|
+
######################################################
|
312
|
+
|
313
|
+
private
|
314
|
+
|
315
|
+
def json_get(url)
|
316
|
+
status, body, headers = http_get(url, 'application/json')
|
317
|
+
json_parse(body)
|
318
|
+
rescue JSON::ParserError
|
319
|
+
raise BadResponse, "Can't parse response into JSON", body
|
320
|
+
end
|
321
|
+
|
322
|
+
def json_post(url, payload)
|
323
|
+
http_post(url, payload.to_json, 'application/json')
|
324
|
+
end
|
325
|
+
|
326
|
+
def json_put(url, payload)
|
327
|
+
http_put(url, payload.to_json, 'application/json')
|
328
|
+
end
|
329
|
+
|
330
|
+
def json_parse(str)
|
331
|
+
if str
|
332
|
+
JSON.parse(str, :symbolize_names => true)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
require 'rest_client'
|
337
|
+
|
338
|
+
# HTTP helpers
|
339
|
+
|
340
|
+
def http_get(path, content_type=nil)
|
341
|
+
request(:get, path, content_type)
|
342
|
+
end
|
343
|
+
|
344
|
+
def http_post(path, body, content_type=nil)
|
345
|
+
request(:post, path, content_type, body)
|
346
|
+
end
|
347
|
+
|
348
|
+
def http_put(path, body, content_type=nil)
|
349
|
+
request(:put, path, content_type, body)
|
350
|
+
end
|
351
|
+
|
352
|
+
def http_delete(path)
|
353
|
+
request(:delete, path)
|
354
|
+
end
|
355
|
+
|
356
|
+
def request(method, path, content_type = nil, payload = nil, headers = {})
|
357
|
+
headers = headers.dup
|
358
|
+
headers['AUTHORIZATION'] = @auth_token if @auth_token
|
359
|
+
headers['PROXY-USER'] = @proxy if @proxy
|
360
|
+
|
361
|
+
if content_type
|
362
|
+
headers['Content-Type'] = content_type
|
363
|
+
headers['Accept'] = content_type
|
364
|
+
end
|
365
|
+
|
366
|
+
req = {
|
367
|
+
:method => method, :url => "#{@target}#{path}",
|
368
|
+
:payload => payload, :headers => headers
|
369
|
+
}
|
370
|
+
status, body, response_headers = perform_http_request(req)
|
371
|
+
|
372
|
+
if VMC_HTTP_ERROR_CODES.include?(status)
|
373
|
+
# FIXME, old cc returned 400 on not found for file access
|
374
|
+
err = (status == 404 || status == 400) ? NotFound : TargetError
|
375
|
+
raise err, parse_error_message(status, body)
|
376
|
+
else
|
377
|
+
return status, body, response_headers
|
378
|
+
end
|
379
|
+
rescue URI::Error, SocketError, Errno::ECONNREFUSED => e
|
380
|
+
raise BadTarget, "Cannot access target (%s)" % [ e.message ]
|
381
|
+
end
|
382
|
+
|
383
|
+
def perform_http_request(req)
|
384
|
+
proxy_uri = URI.parse(req[:url]).find_proxy()
|
385
|
+
RestClient.proxy = proxy_uri.to_s if proxy_uri
|
386
|
+
|
387
|
+
# Setup tracing if needed
|
388
|
+
unless trace.nil?
|
389
|
+
req[:headers]['X-VCAP-Trace'] = (trace == true ? '22' : trace)
|
390
|
+
end
|
391
|
+
|
392
|
+
result = nil
|
393
|
+
RestClient::Request.execute(req) do |response, request|
|
394
|
+
result = [ response.code, response.body, response.headers ]
|
395
|
+
unless trace.nil?
|
396
|
+
puts '>>>'
|
397
|
+
puts "PROXY: #{RestClient.proxy}" if RestClient.proxy
|
398
|
+
puts "REQUEST: #{req[:method]} #{req[:url]}"
|
399
|
+
puts "RESPONSE_HEADERS:"
|
400
|
+
response.headers.each do |key, value|
|
401
|
+
puts " #{key} : #{value}"
|
402
|
+
end
|
403
|
+
puts "REQUEST_BODY: #{req[:payload]}" if req[:payload]
|
404
|
+
puts "RESPONSE: [#{response.code}]"
|
405
|
+
begin
|
406
|
+
puts JSON.pretty_generate(JSON.parse(response.body))
|
407
|
+
rescue
|
408
|
+
puts "#{response.body}"
|
409
|
+
end
|
410
|
+
puts '<<<'
|
411
|
+
end
|
412
|
+
end
|
413
|
+
result
|
414
|
+
rescue Net::HTTPBadResponse => e
|
415
|
+
raise BadTarget "Received bad HTTP response from target: #{e}"
|
416
|
+
rescue SystemCallError, RestClient::Exception => e
|
417
|
+
raise HTTPException, "HTTP exception: #{e.class}:#{e}"
|
418
|
+
end
|
419
|
+
|
420
|
+
def truncate(str, limit = 30)
|
421
|
+
etc = '...'
|
422
|
+
stripped = str.strip[0..limit]
|
423
|
+
if stripped.length > limit
|
424
|
+
stripped + etc
|
425
|
+
else
|
426
|
+
stripped
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def parse_error_message(status, body)
|
431
|
+
parsed_body = json_parse(body.to_s)
|
432
|
+
if parsed_body && parsed_body[:code] && parsed_body[:description]
|
433
|
+
desc = parsed_body[:description].gsub("\"","'")
|
434
|
+
"Error #{parsed_body[:code]}: #{desc}"
|
435
|
+
else
|
436
|
+
"Error (HTTP #{status}): #{body}"
|
437
|
+
end
|
438
|
+
rescue JSON::ParserError
|
439
|
+
if body.nil? || body.empty?
|
440
|
+
"Error (#{status}): No Response Received"
|
441
|
+
else
|
442
|
+
body_out = trace ? body : truncate(body)
|
443
|
+
"Error (JSON #{status}): #{body_out}"
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
def check_login_status
|
448
|
+
raise AuthError unless @user || logged_in?
|
449
|
+
end
|
450
|
+
|
451
|
+
end
|