haveapi-client 0.3.0 → 0.4.0
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.
- 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
|