haveapi-client 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +17 -0
- data/README.md +14 -2
- data/haveapi-client.gemspec +5 -5
- data/lib/haveapi/cli.rb +5 -0
- data/lib/haveapi/cli/cli.rb +528 -277
- data/lib/haveapi/cli/command.rb +52 -0
- data/lib/haveapi/cli/output_formatter.rb +221 -0
- data/lib/haveapi/client.rb +5 -0
- data/lib/haveapi/client/action.rb +102 -98
- data/lib/haveapi/client/authentication/token.rb +1 -0
- data/lib/haveapi/client/client.rb +21 -3
- data/lib/haveapi/client/communicator.rb +161 -129
- data/lib/haveapi/client/exceptions.rb +23 -10
- data/lib/haveapi/client/parameters/resource.rb +29 -0
- data/lib/haveapi/client/parameters/typed.rb +72 -0
- data/lib/haveapi/client/params.rb +72 -0
- data/lib/haveapi/client/resource_instance.rb +2 -0
- data/lib/haveapi/client/validator.rb +55 -0
- data/lib/haveapi/client/validators/acceptance.rb +9 -0
- data/lib/haveapi/client/validators/confirmation.rb +18 -0
- data/lib/haveapi/client/validators/custom.rb +9 -0
- data/lib/haveapi/client/validators/exclusion.rb +9 -0
- data/lib/haveapi/client/validators/format.rb +16 -0
- data/lib/haveapi/client/validators/inclusion.rb +14 -0
- data/lib/haveapi/client/validators/length.rb +14 -0
- data/lib/haveapi/client/validators/numericality.rb +24 -0
- data/lib/haveapi/client/validators/presence.rb +11 -0
- data/lib/haveapi/client/version.rb +3 -2
- metadata +28 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6beb148ebd5b4d6314016309a7fa9eb52292b78f
|
4
|
+
data.tar.gz: 54ff60ff22142d49eaa75cd27140f3349528b066
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c5f0dfbd4fce722a9ec0b02cf175d351581cfba4d2142c8304059fd8cea0d052a3d3467ceda55e5647c8d2834540c4b17fd4eb49edf9486645ebff55be4c87a
|
7
|
+
data.tar.gz: fe3dc2e629ee1505eae68a36632de3a1edd2b9b8e0daf23fc0f61540fd4165ecac69c36226c25eb6ef21f0dce5dcd4aa7c1c9bdcfd192e12388f42e75a11d552
|
data/CHANGELOG
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
* Web Jan 20 2016 - version 0.4.0
|
2
|
+
- API for accessing authentication token
|
3
|
+
- Client-side input parameter validators
|
4
|
+
- Drop table_print in favor of custom output formatter
|
5
|
+
- CLI
|
6
|
+
- switches -c, --columns, -r, --rows to select output mode
|
7
|
+
- option -o, --output to select parameters to print
|
8
|
+
- option -s, --sort to sort by specific parameter (on client-side)
|
9
|
+
- switch --timestamp, --utc and --localtime to select how to print Datetime
|
10
|
+
parameters
|
11
|
+
- switch --check-compatibility to determine whether the client is compatible
|
12
|
+
with given API server
|
13
|
+
- switch --protocol-version to print protocl version that this clients
|
14
|
+
implements
|
15
|
+
- Check protocol version
|
16
|
+
- Fixed check of invalid action
|
17
|
+
- ResourceInstance passes on prepared_args correctly
|
data/README.md
CHANGED
@@ -28,10 +28,22 @@ Or install it yourself as:
|
|
28
28
|
--list-resources [VERSION] List all resource in API version
|
29
29
|
--list-actions [VERSION] List all resources and actions in API version
|
30
30
|
--version VERSION Use specified API version
|
31
|
-
-
|
32
|
-
-
|
31
|
+
-c, --columns Print output in columns
|
32
|
+
-H, --no-header Hide header row
|
33
|
+
-L, --list-parameters List output parameters
|
34
|
+
-o, --output PARAMETERS Parameters to display, separated by a comma
|
35
|
+
-r, --rows Print output in rows
|
36
|
+
-s, --sort PARAMETER Sort output by parameter
|
37
|
+
--save Save credentials to config file for later use
|
38
|
+
--raw Print raw response as is
|
39
|
+
--timestamp Display Datetime parameters as timestamp
|
40
|
+
--utc Display Datetime parameters in UTC
|
41
|
+
--localtime Display Datetime parameters in local timezone
|
42
|
+
--date-format FORMAT Display Datetime in custom format
|
33
43
|
-v, --[no-]verbose Run verbosely
|
34
44
|
--client-version Show client version
|
45
|
+
--protocol-version Show protocol version
|
46
|
+
--check-compatibility Check compatibility with API server
|
35
47
|
-h, --help Show this message
|
36
48
|
|
37
49
|
Using the API example from
|
data/haveapi-client.gemspec
CHANGED
@@ -6,6 +6,7 @@ require 'haveapi/client/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'haveapi-client'
|
8
8
|
spec.version = HaveAPI::Client::VERSION
|
9
|
+
spec.date = '2016-01-20'
|
9
10
|
spec.authors = ['Jakub Skokan']
|
10
11
|
spec.email = ['jakub.skokan@vpsfree.cz']
|
11
12
|
spec.summary =
|
@@ -21,10 +22,9 @@ Gem::Specification.new do |spec|
|
|
21
22
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
22
23
|
spec.add_development_dependency 'rake'
|
23
24
|
|
24
|
-
spec.add_runtime_dependency 'activesupport', '~> 4.1.
|
25
|
-
spec.add_runtime_dependency 'require_all', '~> 1.3.
|
25
|
+
spec.add_runtime_dependency 'activesupport', '~> 4.1.14'
|
26
|
+
spec.add_runtime_dependency 'require_all', '~> 1.3.3'
|
26
27
|
spec.add_runtime_dependency 'rest_client', '~> 1.7.3'
|
27
|
-
spec.add_runtime_dependency 'json', '~> 1.8.
|
28
|
-
spec.add_runtime_dependency 'highline', '~> 1.
|
29
|
-
spec.add_runtime_dependency 'table_print', '~> 1.5.3'
|
28
|
+
spec.add_runtime_dependency 'json', '~> 1.8.3'
|
29
|
+
spec.add_runtime_dependency 'highline', '~> 1.7.8'
|
30
30
|
end
|
data/lib/haveapi/cli.rb
CHANGED
data/lib/haveapi/cli/cli.rb
CHANGED
@@ -1,428 +1,679 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'pp'
|
3
3
|
require 'highline/import'
|
4
|
-
require 'table_print'
|
5
4
|
require 'yaml'
|
5
|
+
require 'time'
|
6
6
|
|
7
|
-
module HaveAPI
|
8
|
-
|
9
|
-
class
|
10
|
-
|
11
|
-
attr_accessor :auth_methods
|
7
|
+
module HaveAPI::CLI
|
8
|
+
class Cli
|
9
|
+
class << self
|
10
|
+
attr_accessor :auth_methods, :commands
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
def run
|
13
|
+
c = new
|
14
|
+
end
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
16
|
+
def register_auth_method(name, klass)
|
17
|
+
@auth_methods ||= {}
|
18
|
+
@auth_methods[name] = klass
|
21
19
|
end
|
22
20
|
|
23
|
-
def
|
24
|
-
@
|
25
|
-
|
21
|
+
def register_command(cmd)
|
22
|
+
@commands ||= []
|
23
|
+
@commands << cmd
|
24
|
+
end
|
25
|
+
end
|
26
26
|
|
27
|
-
|
28
|
-
|
27
|
+
def initialize
|
28
|
+
@config = read_config || {}
|
29
|
+
args, @opts = options
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
exit
|
33
|
-
end
|
31
|
+
@api = HaveAPI::Client::Communicator.new(api_url, @opts[:version])
|
32
|
+
@api.identity = $0.split('/').last
|
34
33
|
|
35
|
-
|
36
|
-
|
34
|
+
if @action
|
35
|
+
method(@action.first).call( * @action[1..-1] )
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
|
39
|
+
if (@opts[:help] && args.empty?) || args.empty?
|
40
|
+
show_help do
|
37
41
|
puts "\nAvailable resources:"
|
38
42
|
list_resources
|
39
|
-
exit(true)
|
40
43
|
end
|
44
|
+
end
|
41
45
|
|
42
|
-
|
46
|
+
resources = args[0].split('.')
|
43
47
|
|
44
|
-
|
45
|
-
|
46
|
-
|
48
|
+
if cmd = find_command(resources, args[1])
|
49
|
+
authenticate if @auth
|
50
|
+
c = cmd.new(@opts, HaveAPI::Client::Client.new(nil, communicator: @api))
|
51
|
+
|
52
|
+
cmd_opt = OptionParser.new do |opts|
|
53
|
+
opts.banner = "\nCommand options:"
|
54
|
+
c.options(opts)
|
55
|
+
|
56
|
+
opts.on('-h', '--help', 'Show this message') do
|
57
|
+
show_help do
|
58
|
+
puts cmd_opt.help
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
if @opts[:help]
|
64
|
+
show_help do
|
65
|
+
puts cmd_opt.help
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if sep = ARGV.index('--')
|
70
|
+
cmd_opt.parse!(ARGV[sep+1..-1])
|
47
71
|
end
|
48
72
|
|
49
|
-
|
50
|
-
action.update_description(@api.describe_action(action)) if authenticate(action)
|
73
|
+
c.exec(args[2..-1] || [])
|
51
74
|
|
52
|
-
|
75
|
+
exit
|
76
|
+
end
|
53
77
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
78
|
+
if args.count == 1
|
79
|
+
describe_resource(resources)
|
80
|
+
exit(true)
|
81
|
+
end
|
58
82
|
|
59
|
-
|
83
|
+
action = @api.get_action(resources, args[1].to_sym, args[2..-1])
|
60
84
|
|
61
|
-
|
62
|
-
|
85
|
+
unless action
|
86
|
+
warn "Resource or action '#{args[0]} #{args[1]}' not found"
|
87
|
+
puts
|
88
|
+
show_help(false)
|
89
|
+
end
|
63
90
|
|
64
|
-
|
65
|
-
warn "Action failed: #{ret[:message]}"
|
91
|
+
action.update_description(@api.describe_action(action)) if authenticate(action)
|
66
92
|
|
67
|
-
|
68
|
-
|
69
|
-
ret[:errors].each do |param, e|
|
70
|
-
puts "\t#{param}: #{e.join('; ')}"
|
71
|
-
end
|
72
|
-
end
|
93
|
+
@selected_params = @opts[:output] ? @opts[:output].split(',').uniq
|
94
|
+
: nil
|
73
95
|
|
74
|
-
|
75
|
-
end
|
96
|
+
@input_params = parameters(action)
|
76
97
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
98
|
+
includes = build_includes(action) if @selected_params
|
99
|
+
@input_params[:meta] = { includes: includes } if includes
|
100
|
+
|
101
|
+
begin
|
102
|
+
ret = action.execute(@input_params, raw: @opts[:raw])
|
103
|
+
|
104
|
+
rescue HaveAPI::Client::ValidationError => e
|
105
|
+
format_errors(action, 'input parameters not valid', e.errors)
|
106
|
+
exit(false)
|
81
107
|
end
|
82
108
|
|
83
|
-
|
84
|
-
|
109
|
+
if ret[:status]
|
110
|
+
format_output(action, ret[:response])
|
111
|
+
|
112
|
+
else
|
113
|
+
format_errors(action, ret[:message], ret[:errors])
|
114
|
+
exit(false)
|
85
115
|
end
|
116
|
+
end
|
86
117
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
verbose: false,
|
91
|
-
}
|
118
|
+
def api_url
|
119
|
+
@opts[:client]
|
120
|
+
end
|
92
121
|
|
93
|
-
|
94
|
-
|
122
|
+
def options
|
123
|
+
options = {
|
124
|
+
client: default_url,
|
125
|
+
verbose: false,
|
126
|
+
}
|
95
127
|
|
96
|
-
|
97
|
-
|
98
|
-
end
|
128
|
+
@global_opt = OptionParser.new do |opts|
|
129
|
+
opts.banner = "Usage: #{$0} [options] <resource> <action> [objects ids] [-- [parameters]]"
|
99
130
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
@auth.options(opts)
|
104
|
-
end
|
131
|
+
opts.on('-u', '--api URL', 'API URL') do |url|
|
132
|
+
options[:client] = url
|
133
|
+
end
|
105
134
|
|
106
|
-
|
107
|
-
|
108
|
-
|
135
|
+
opts.on('-a', '--auth METHOD', Cli.auth_methods.keys, 'Authentication method') do |m|
|
136
|
+
options[:auth] = m
|
137
|
+
@auth = Cli.auth_methods[m].new(server_config(options[:client])[:auth][m])
|
138
|
+
@auth.options(opts)
|
139
|
+
end
|
109
140
|
|
110
|
-
|
111
|
-
|
112
|
-
|
141
|
+
opts.on('--list-versions', 'List all available API versions') do
|
142
|
+
@action = [:list_versions]
|
143
|
+
end
|
113
144
|
|
114
|
-
|
115
|
-
|
116
|
-
|
145
|
+
opts.on('--list-auth-methods [VERSION]', 'List available authentication methods') do |v|
|
146
|
+
@action = [:list_auth, v && v.sub(/^v/, '')]
|
147
|
+
end
|
117
148
|
|
118
|
-
|
119
|
-
|
120
|
-
|
149
|
+
opts.on('--list-resources [VERSION]', 'List all resource in API version') do |v|
|
150
|
+
@action = [:list_resources, v && v.sub(/^v/, '')]
|
151
|
+
end
|
121
152
|
|
122
|
-
|
123
|
-
|
124
|
-
|
153
|
+
opts.on('--list-actions [VERSION]', 'List all resources and actions in API version') do |v|
|
154
|
+
@action = [:list_actions, v && v.sub(/^v/, '')]
|
155
|
+
end
|
125
156
|
|
126
|
-
|
127
|
-
|
128
|
-
|
157
|
+
opts.on('--version VERSION', 'Use specified API version') do |v|
|
158
|
+
options[:version] = v
|
159
|
+
end
|
129
160
|
|
130
|
-
|
131
|
-
|
132
|
-
|
161
|
+
opts.on('-c', '--columns', 'Print output in columns') do
|
162
|
+
options[:layout] = :columns
|
163
|
+
end
|
133
164
|
|
134
|
-
|
135
|
-
|
136
|
-
|
165
|
+
opts.on('-H', '--no-header', 'Hide header row') do |h|
|
166
|
+
options[:header] = false
|
167
|
+
end
|
137
168
|
|
138
|
-
|
139
|
-
|
140
|
-
|
169
|
+
opts.on('-L', '--list-parameters', 'List output parameters') do |l|
|
170
|
+
options[:list_output] = true
|
171
|
+
end
|
141
172
|
|
142
|
-
|
143
|
-
|
144
|
-
end
|
173
|
+
opts.on('-o', '--output PARAMETERS', 'Parameters to display, separated by a comma') do |o|
|
174
|
+
options[:output] = o
|
145
175
|
end
|
146
176
|
|
147
|
-
|
177
|
+
opts.on('-r', '--rows', 'Print output in rows') do
|
178
|
+
options[:layout] = :rows
|
179
|
+
end
|
180
|
+
|
181
|
+
opts.on('-s', '--sort PARAMETER', 'Sort output by parameter') do |p|
|
182
|
+
options[:sort] = p
|
183
|
+
end
|
148
184
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
185
|
+
opts.on('--save', 'Save credentials to config file for later use') do
|
186
|
+
options[:save] = true
|
187
|
+
end
|
188
|
+
|
189
|
+
opts.on('--raw', 'Print raw response as is') do
|
190
|
+
options[:raw] = true
|
191
|
+
end
|
192
|
+
|
193
|
+
opts.on('--timestamp', 'Display Datetime parameters as timestamp') do
|
194
|
+
options[:datetime] = :timestamp
|
195
|
+
end
|
196
|
+
|
197
|
+
opts.on('--utc', 'Display Datetime parameters in UTC') do
|
198
|
+
options[:datetime] = :utc
|
199
|
+
end
|
200
|
+
|
201
|
+
opts.on('--localtime', 'Display Datetime parameters in local timezone') do
|
202
|
+
options[:datetime] = :local
|
203
|
+
end
|
204
|
+
|
205
|
+
opts.on('--date-format FORMAT', 'Display Datetime in custom format') do |f|
|
206
|
+
options[:date_format] = f
|
155
207
|
end
|
156
208
|
|
157
|
-
|
209
|
+
opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
|
210
|
+
options[:verbose] = v
|
211
|
+
end
|
158
212
|
|
159
|
-
|
160
|
-
|
213
|
+
opts.on('--client-version', 'Show client version') do
|
214
|
+
@action = [:show_version]
|
215
|
+
end
|
216
|
+
|
217
|
+
opts.on('--protocol-version', 'Show protocol version') do
|
218
|
+
@action = [:protocol_version]
|
219
|
+
end
|
220
|
+
|
221
|
+
opts.on('--check-compatibility', 'Check compatibility with API server') do
|
222
|
+
@action = [:check_compat]
|
223
|
+
end
|
161
224
|
|
162
|
-
|
225
|
+
opts.on('-h', '--help', 'Show this message') do
|
226
|
+
options[:help] = true
|
163
227
|
end
|
228
|
+
end
|
164
229
|
|
165
|
-
|
230
|
+
args = []
|
231
|
+
|
232
|
+
ARGV.each do |arg|
|
233
|
+
if arg == '--'
|
234
|
+
break
|
235
|
+
else
|
236
|
+
args << arg
|
237
|
+
end
|
166
238
|
end
|
167
239
|
|
168
|
-
|
169
|
-
options = {}
|
170
|
-
sep = ARGV.index('--')
|
240
|
+
@global_opt.parse!(args)
|
171
241
|
|
172
|
-
|
173
|
-
|
242
|
+
unless options[:auth]
|
243
|
+
cfg = server_config(options[:client])
|
174
244
|
|
175
|
-
|
176
|
-
|
177
|
-
options[name] = args.first
|
178
|
-
end
|
179
|
-
end
|
245
|
+
@auth = Cli.auth_methods[cfg[:last_auth]].new(cfg[:auth][cfg[:last_auth]]) if cfg[:last_auth]
|
246
|
+
end
|
180
247
|
|
181
|
-
|
182
|
-
|
248
|
+
[args, options]
|
249
|
+
end
|
250
|
+
|
251
|
+
def parameters(action)
|
252
|
+
options = {}
|
253
|
+
sep = ARGV.index('--')
|
254
|
+
|
255
|
+
@action_opt = OptionParser.new do |opts|
|
256
|
+
opts.banner = ''
|
257
|
+
|
258
|
+
action.input[:parameters].each do |name, p|
|
259
|
+
opts.on(param_option(name, p), p[:description] || p[:label] || '') do |*args|
|
260
|
+
options[name] = args.first
|
183
261
|
end
|
184
262
|
end
|
185
263
|
|
186
|
-
|
187
|
-
|
188
|
-
|
264
|
+
opts.on('-h', '--help', 'Show this message') do
|
265
|
+
@opts[:help] = true
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
if @opts[:help]
|
270
|
+
show_help do
|
189
271
|
puts 'Action description:'
|
190
272
|
puts action.description, "\n"
|
191
|
-
print '
|
273
|
+
print 'Input parameters:'
|
192
274
|
puts @action_opt.help
|
275
|
+
puts
|
276
|
+
puts 'Output parameters:'
|
277
|
+
|
278
|
+
action.params.each do |name, param|
|
279
|
+
puts sprintf(" %-32s %s", name, param[:description])
|
280
|
+
end
|
281
|
+
|
193
282
|
print_examples(action)
|
194
|
-
exit
|
195
283
|
end
|
284
|
+
end
|
196
285
|
|
197
|
-
|
286
|
+
if @opts[:list_output]
|
287
|
+
action.params.each_key { |name| puts name }
|
288
|
+
exit
|
289
|
+
end
|
198
290
|
|
199
|
-
|
291
|
+
return {} unless sep
|
200
292
|
|
201
|
-
|
202
|
-
end
|
293
|
+
@action_opt.parse!(ARGV[sep+1..-1])
|
203
294
|
|
204
|
-
|
205
|
-
|
206
|
-
name = name.to_s.dasherize
|
295
|
+
options
|
296
|
+
end
|
207
297
|
|
208
|
-
|
209
|
-
|
298
|
+
def param_option(name, p)
|
299
|
+
ret = '--'
|
300
|
+
name = name.to_s.dasherize
|
210
301
|
|
211
|
-
|
212
|
-
|
213
|
-
end
|
302
|
+
if p[:type] == 'Boolean'
|
303
|
+
ret += "[no-]#{name}"
|
214
304
|
|
215
|
-
|
305
|
+
else
|
306
|
+
ret += "#{name} #{name.underscore.upcase}"
|
216
307
|
end
|
217
308
|
|
218
|
-
|
219
|
-
|
309
|
+
ret
|
310
|
+
end
|
220
311
|
|
221
|
-
|
222
|
-
|
312
|
+
def list_versions
|
313
|
+
desc = @api.available_versions
|
223
314
|
|
224
|
-
|
315
|
+
desc[:versions].each do |v, _|
|
316
|
+
next if v == :default
|
225
317
|
|
226
|
-
|
227
|
-
|
318
|
+
v_int = v.to_s.to_i
|
319
|
+
|
320
|
+
puts "#{v_int == desc[:default] ? '*' : ' '} v#{v}"
|
228
321
|
end
|
322
|
+
end
|
229
323
|
|
230
|
-
|
231
|
-
|
324
|
+
def list_auth(v=nil)
|
325
|
+
desc = @api.describe_api(v)
|
232
326
|
|
233
|
-
|
234
|
-
|
235
|
-
end
|
327
|
+
desc[:authentication].each_key do |auth|
|
328
|
+
puts auth if Cli.auth_methods.has_key?(auth)
|
236
329
|
end
|
330
|
+
end
|
237
331
|
|
238
|
-
|
239
|
-
|
332
|
+
def list_resources(v=nil)
|
333
|
+
desc = @api.describe_api(v)
|
240
334
|
|
241
|
-
|
242
|
-
|
243
|
-
end
|
335
|
+
desc[:resources].each do |resource, children|
|
336
|
+
nested_resource(resource, children, false)
|
244
337
|
end
|
338
|
+
end
|
245
339
|
|
246
|
-
|
247
|
-
|
340
|
+
def list_actions(v=nil)
|
341
|
+
desc = @api.describe_api(v)
|
248
342
|
|
249
|
-
|
250
|
-
|
251
|
-
end
|
343
|
+
desc[:resources].each do |resource, children|
|
344
|
+
nested_resource(resource, children, true)
|
252
345
|
end
|
346
|
+
end
|
253
347
|
|
254
|
-
|
255
|
-
|
348
|
+
def show_version
|
349
|
+
puts HaveAPI::Client::VERSION
|
350
|
+
end
|
351
|
+
|
352
|
+
def protocol_version
|
353
|
+
puts HaveAPI::Client::PROTOCOL_VERSION
|
354
|
+
end
|
355
|
+
|
356
|
+
def check_compat
|
357
|
+
case @api.compatible?
|
358
|
+
when :compatible
|
359
|
+
puts 'compatible'
|
360
|
+
exit
|
361
|
+
|
362
|
+
when :imperfect
|
363
|
+
puts 'imperfect'
|
364
|
+
exit(1)
|
365
|
+
|
366
|
+
else
|
367
|
+
puts 'incompatible'
|
368
|
+
exit(2)
|
256
369
|
end
|
370
|
+
end
|
257
371
|
|
258
|
-
|
259
|
-
|
372
|
+
def describe_resource(path)
|
373
|
+
desc = @api.describe_resource(path)
|
260
374
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
375
|
+
unless desc
|
376
|
+
warn "Resource #{path.join('.')} does not exist"
|
377
|
+
exit(false)
|
378
|
+
end
|
265
379
|
|
266
|
-
|
267
|
-
|
380
|
+
unless desc[:resources].empty?
|
381
|
+
puts 'Resources:'
|
268
382
|
|
269
|
-
|
270
|
-
|
271
|
-
end
|
383
|
+
desc[:resources].each_key do |r|
|
384
|
+
puts " #{r}"
|
272
385
|
end
|
386
|
+
end
|
273
387
|
|
274
|
-
|
388
|
+
puts '' if !desc[:resources].empty? && !desc[:actions].empty?
|
275
389
|
|
276
|
-
|
277
|
-
|
390
|
+
unless desc[:actions].empty?
|
391
|
+
puts 'Actions:'
|
278
392
|
|
279
|
-
|
280
|
-
|
281
|
-
end
|
393
|
+
desc[:actions].each_key do |a|
|
394
|
+
puts " #{a}"
|
282
395
|
end
|
283
396
|
end
|
397
|
+
end
|
284
398
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
end
|
290
|
-
else
|
291
|
-
puts prefix
|
399
|
+
def nested_resource(prefix, children, actions=false)
|
400
|
+
if actions
|
401
|
+
children[:actions].each do |action, _|
|
402
|
+
puts "#{prefix}##{action}"
|
292
403
|
end
|
404
|
+
else
|
405
|
+
puts prefix
|
406
|
+
end
|
293
407
|
|
294
|
-
|
295
|
-
|
296
|
-
end
|
408
|
+
children[:resources].each do |resource, children|
|
409
|
+
nested_resource("#{prefix}.#{resource}", children, actions)
|
297
410
|
end
|
411
|
+
end
|
298
412
|
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
413
|
+
def show_help(exit_code = true)
|
414
|
+
puts @global_opt.help
|
415
|
+
|
416
|
+
if Cli.commands
|
417
|
+
puts
|
418
|
+
puts 'Commands:'
|
419
|
+
Cli.commands.each do |cmd|
|
420
|
+
puts sprintf(
|
421
|
+
'%-36s %s',
|
422
|
+
"#{cmd.resource.join('.')} #{cmd.action} #{cmd.args}",
|
423
|
+
cmd.desc
|
424
|
+
)
|
303
425
|
end
|
304
426
|
end
|
305
427
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
return
|
310
|
-
end
|
428
|
+
yield if block_given?
|
429
|
+
exit(exit_code)
|
430
|
+
end
|
311
431
|
|
312
|
-
|
432
|
+
def print_examples(action)
|
433
|
+
unless action.examples.empty?
|
434
|
+
puts "\nExamples:\n"
|
435
|
+
ExampleFormatter.format_examples(self, action)
|
436
|
+
end
|
437
|
+
end
|
313
438
|
|
314
|
-
|
439
|
+
def format_output(action, response, out = $>)
|
440
|
+
if @opts[:raw]
|
441
|
+
puts JSON.generate(response)
|
442
|
+
return
|
443
|
+
end
|
315
444
|
|
316
|
-
|
445
|
+
return if response.empty?
|
317
446
|
|
318
|
-
|
319
|
-
when :object_list, :hash_list
|
320
|
-
cols = []
|
447
|
+
namespace = action.namespace(:output).to_sym
|
321
448
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
449
|
+
if action.output_layout.to_sym == :custom
|
450
|
+
return PP.pp(response[namespace], out)
|
451
|
+
end
|
452
|
+
|
453
|
+
cols = []
|
454
|
+
|
455
|
+
(@selected_params || action.params.keys).each do |raw_name|
|
456
|
+
col = {}
|
457
|
+
name = nil
|
458
|
+
param = nil
|
459
|
+
|
460
|
+
# Fetching an associated attribute
|
461
|
+
if raw_name.to_s.index('.')
|
462
|
+
parts = raw_name.to_s.split('.').map! { |v| v.to_sym }
|
463
|
+
name = parts.first.to_sym
|
464
|
+
|
465
|
+
top = action.params
|
466
|
+
|
467
|
+
parts.each do |part|
|
468
|
+
fail "'#{part}' not found" unless top.has_key?(part)
|
469
|
+
|
470
|
+
if top[part][:type] == 'Resource'
|
471
|
+
param = top[part]
|
472
|
+
top = @api.get_action(top[part][:resource], :show, []).params
|
473
|
+
|
474
|
+
else
|
475
|
+
param = top[part]
|
476
|
+
break
|
334
477
|
end
|
478
|
+
end
|
335
479
|
|
336
|
-
|
480
|
+
col[:display] = Proc.new do |r|
|
481
|
+
next '' unless r
|
337
482
|
|
483
|
+
top = r
|
484
|
+
parts[1..-1].each do |part|
|
485
|
+
fail "'#{part}' not found" unless top.has_key?(part)
|
486
|
+
break if top[part].nil?
|
487
|
+
top = top[part]
|
488
|
+
end
|
489
|
+
|
490
|
+
case param[:type]
|
491
|
+
when 'Resource'
|
492
|
+
"#{top[ param[:value_label].to_sym ]} (##{top[ param[:value_id].to_sym ]})"
|
338
493
|
|
339
|
-
|
340
|
-
|
494
|
+
when 'Datetime'
|
495
|
+
format_date(top)
|
341
496
|
|
342
|
-
|
343
|
-
|
344
|
-
else
|
345
|
-
out << "#{k}: #{v}\n"
|
346
|
-
end
|
497
|
+
else
|
498
|
+
top
|
347
499
|
end
|
500
|
+
end
|
348
501
|
|
502
|
+
col[:label] = raw_name
|
503
|
+
|
504
|
+
else # directly accessible parameter
|
505
|
+
name = raw_name.to_sym
|
506
|
+
param = action.params[name]
|
507
|
+
fail "parameter '#{name}' does not exist" if param.nil?
|
508
|
+
|
509
|
+
if param[:type] == 'Resource'
|
510
|
+
col[:display] = Proc.new do |r|
|
511
|
+
next '' unless r
|
512
|
+
"#{r[ param[:value_label].to_sym ]} (##{r[ param[:value_id].to_sym ]})"
|
513
|
+
end
|
349
514
|
|
350
|
-
|
351
|
-
|
352
|
-
|
515
|
+
elsif param[:type] == 'Datetime'
|
516
|
+
col[:display] = ->(date) { format_date(date) }
|
517
|
+
end
|
353
518
|
end
|
519
|
+
|
520
|
+
col.update({
|
521
|
+
name: name,
|
522
|
+
align: %w(Integer Float).include?(param[:type]) ? 'right' : 'left',
|
523
|
+
})
|
524
|
+
|
525
|
+
col[:label] ||= param[:label] && !param[:label].empty? ? param[:label] : name.upcase
|
526
|
+
|
527
|
+
cols << col
|
354
528
|
end
|
355
529
|
|
356
|
-
|
357
|
-
|
530
|
+
OutputFormatter.print(
|
531
|
+
response[namespace],
|
532
|
+
cols,
|
533
|
+
header: @opts[:header].nil?,
|
534
|
+
sort: @opts[:sort] && @opts[:sort].to_sym,
|
535
|
+
layout: @opts[:layout]
|
536
|
+
)
|
537
|
+
end
|
358
538
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
539
|
+
def header_for(action, param)
|
540
|
+
params = action.params
|
541
|
+
|
542
|
+
if params.has_key?(param) && params[param][:label]
|
543
|
+
params[param][:label]
|
544
|
+
else
|
545
|
+
param.to_s.upcase
|
364
546
|
end
|
547
|
+
end
|
365
548
|
|
366
|
-
|
367
|
-
|
368
|
-
if @auth
|
369
|
-
@auth.communicator = @api
|
370
|
-
@auth.validate
|
371
|
-
@auth.authenticate
|
549
|
+
def authenticate(action = nil)
|
550
|
+
return false if !action.nil? && !action.auth?
|
372
551
|
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
write_config
|
378
|
-
end
|
379
|
-
|
380
|
-
else
|
381
|
-
# FIXME: exit as auth is needed and has not been selected
|
382
|
-
end
|
552
|
+
if @auth
|
553
|
+
@auth.communicator = @api
|
554
|
+
@auth.validate
|
555
|
+
@auth.authenticate
|
383
556
|
|
384
|
-
|
557
|
+
if @opts[:save]
|
558
|
+
cfg = server_config(api_url)
|
559
|
+
cfg[:auth][@opts[:auth]] = @auth.save
|
560
|
+
cfg[:last_auth] = @opts[:auth]
|
561
|
+
write_config
|
385
562
|
end
|
386
563
|
|
387
|
-
|
564
|
+
else
|
565
|
+
# FIXME: exit as auth is needed and has not been selected
|
388
566
|
end
|
389
567
|
|
390
|
-
|
391
|
-
|
568
|
+
return true
|
569
|
+
end
|
570
|
+
|
571
|
+
protected
|
572
|
+
def default_url
|
573
|
+
'http://localhost:4567'
|
574
|
+
end
|
575
|
+
|
576
|
+
def config_path
|
577
|
+
"#{Dir.home}/.haveapi-client.yml"
|
578
|
+
end
|
579
|
+
|
580
|
+
def write_config
|
581
|
+
File.open(config_path, 'w') do |f|
|
582
|
+
f.write(YAML.dump(@config))
|
392
583
|
end
|
584
|
+
end
|
585
|
+
|
586
|
+
def read_config
|
587
|
+
@config = YAML.load_file(config_path) if File.exists?(config_path)
|
588
|
+
end
|
393
589
|
|
394
|
-
|
395
|
-
|
396
|
-
|
590
|
+
def server_config(url)
|
591
|
+
unless @config[:servers]
|
592
|
+
@config[:servers] = [{url: url, auth: {}}]
|
593
|
+
return @config[:servers].first
|
397
594
|
end
|
398
595
|
|
399
|
-
|
400
|
-
|
596
|
+
@config[:servers].each do |s|
|
597
|
+
return s if s[:url] == url
|
401
598
|
end
|
402
599
|
|
403
|
-
|
404
|
-
|
405
|
-
|
600
|
+
@config[:servers] << {url: url, auth: {}}
|
601
|
+
@config[:servers].last
|
602
|
+
end
|
603
|
+
|
604
|
+
def format_errors(action, msg, errors)
|
605
|
+
warn "Action failed: #{msg}"
|
606
|
+
|
607
|
+
if errors.any?
|
608
|
+
puts 'Errors:'
|
609
|
+
errors.each do |param, e|
|
610
|
+
puts "\t#{param}: #{e.join('; ')}"
|
406
611
|
end
|
407
612
|
end
|
408
613
|
|
409
|
-
|
410
|
-
|
614
|
+
print_examples(action)
|
615
|
+
end
|
616
|
+
|
617
|
+
def find_command(resource, action)
|
618
|
+
return false unless Cli.commands
|
619
|
+
|
620
|
+
Cli.commands.each do |cmd|
|
621
|
+
return cmd if cmd.handle?(resource, action)
|
411
622
|
end
|
412
623
|
|
413
|
-
|
414
|
-
|
415
|
-
@config[:servers] = [{url: url, auth: {}}]
|
416
|
-
return @config[:servers].first
|
417
|
-
end
|
624
|
+
false
|
625
|
+
end
|
418
626
|
|
419
|
-
|
420
|
-
|
627
|
+
# Translate requested parameters into meta[includes] that is sent
|
628
|
+
# to the API.
|
629
|
+
#
|
630
|
+
# When using haveapi-cli vps list -o
|
631
|
+
# node.location => node__location
|
632
|
+
# node.location.domain => node_location
|
633
|
+
# node.name => node
|
634
|
+
def build_includes(action)
|
635
|
+
ret = []
|
636
|
+
|
637
|
+
@selected_params.each do |param|
|
638
|
+
next unless param.index('.')
|
639
|
+
|
640
|
+
includes = []
|
641
|
+
top = action.params
|
642
|
+
|
643
|
+
param.split('.').map! { |v| v.to_sym }.each do |part|
|
644
|
+
next unless top.has_key?(part)
|
645
|
+
next if top[part][:type] != 'Resource'
|
646
|
+
|
647
|
+
includes << part
|
648
|
+
top = @api.get_action(top[part][:resource], :show, []).params
|
421
649
|
end
|
422
650
|
|
423
|
-
|
424
|
-
|
651
|
+
ret << includes.join('__')
|
652
|
+
end
|
653
|
+
|
654
|
+
ret.uniq!
|
655
|
+
ret.empty? ? nil : ret.join(',')
|
656
|
+
end
|
657
|
+
|
658
|
+
def format_date(date)
|
659
|
+
return '' unless date
|
660
|
+
|
661
|
+
t = Time.iso8601(date)
|
662
|
+
ret = case @opts[:datetime]
|
663
|
+
when :timestamp
|
664
|
+
t.to_i
|
665
|
+
|
666
|
+
when :utc
|
667
|
+
t.utc
|
668
|
+
|
669
|
+
when :local
|
670
|
+
t.localtime
|
671
|
+
|
672
|
+
else
|
673
|
+
t.localtime
|
425
674
|
end
|
675
|
+
|
676
|
+
@opts[:date_format] ? ret.strftime(@opts[:date_format]) : ret
|
426
677
|
end
|
427
678
|
end
|
428
679
|
end
|