haveapi-client 0.6.0 → 0.7.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 +13 -0
- data/README.md +2 -0
- data/haveapi-client.gemspec +2 -1
- data/lib/haveapi/cli.rb +4 -1
- data/lib/haveapi/cli/action_state.rb +147 -0
- data/lib/haveapi/cli/cli.rb +52 -5
- data/lib/haveapi/cli/commands/action_state_wait.rb +28 -0
- data/lib/haveapi/cli/example_formatter.rb +10 -3
- data/lib/haveapi/client.rb +6 -1
- data/lib/haveapi/client/action.rb +106 -3
- data/lib/haveapi/client/action_state.rb +68 -0
- data/lib/haveapi/client/authentication/token.rb +7 -1
- data/lib/haveapi/client/client.rb +33 -6
- data/lib/haveapi/client/communicator.rb +4 -1
- data/lib/haveapi/client/parameters/typed.rb +3 -1
- data/lib/haveapi/client/resource.rb +29 -3
- data/lib/haveapi/client/resource_instance.rb +7 -1
- data/lib/haveapi/client/response.rb +11 -0
- data/lib/haveapi/client/version.rb +2 -2
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbf74153c675555c4efe868808662ecba7edab78
|
4
|
+
data.tar.gz: 9841b9182c4585edbb415cc7e9a09637c1b0491b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1dfa2473733340a6650dfa0b76ae94b224f916d4fb66c8c962b5bb28f6ba3d80d3f986562ef1cdfef837001cc3805214ab0b554150b7a9ed5925ce5873a5dc3a
|
7
|
+
data.tar.gz: 1bec8283d80a29b7a70fe9fac8f7b2b51bbdcce62c143a4a325f307d278dc098e1d764d4ae991e45a91c3a9e58f8fe9447247003232413311edf5e3197c008e4
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
* Thu Nov 24 2016 - version 0.7.0
|
2
|
+
- Client.initialize takes two arguments: url and options
|
3
|
+
- Authenticate even for actions that do not require it
|
4
|
+
- New CLI options --[no-]block and --timeout
|
5
|
+
- CLI handles all interrupts
|
6
|
+
- Add shortcut method HaveAPI::Client.new
|
7
|
+
- Add method Action#meta
|
8
|
+
- Fix access to nil associations in ResourceInstance
|
9
|
+
- Handle 404 errors when fetching description
|
10
|
+
- Handle RestClient::ResourceNotFound
|
11
|
+
- Use DateTime.iso8601 to parse dates
|
12
|
+
- Do not show examples on action error, just a hint
|
13
|
+
|
1
14
|
* Tue Oct 18 2016 - version 0.6.0
|
2
15
|
- No changes
|
3
16
|
|
data/README.md
CHANGED
@@ -40,6 +40,8 @@ Or install it yourself as:
|
|
40
40
|
--utc Display Datetime parameters in UTC
|
41
41
|
--localtime Display Datetime parameters in local timezone
|
42
42
|
--date-format FORMAT Display Datetime in custom format
|
43
|
+
--[no-]block Toggle action blocking mode
|
44
|
+
--timeout SEC Fail when the action does not finish within the timeout
|
43
45
|
-v, --[no-]verbose Run verbosely
|
44
46
|
--client-version Show client version
|
45
47
|
--protocol-version Show protocol version
|
data/haveapi-client.gemspec
CHANGED
@@ -6,7 +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-
|
9
|
+
spec.date = '2016-11-24'
|
10
10
|
spec.authors = ['Jakub Skokan']
|
11
11
|
spec.email = ['jakub.skokan@vpsfree.cz']
|
12
12
|
spec.summary =
|
@@ -27,4 +27,5 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_runtime_dependency 'rest-client', '~> 1.8.0'
|
28
28
|
spec.add_runtime_dependency 'json', '~> 1.8.3'
|
29
29
|
spec.add_runtime_dependency 'highline', '~> 1.7.8'
|
30
|
+
spec.add_runtime_dependency 'ruby-progressbar', '~> 1.7.5'
|
30
31
|
end
|
data/lib/haveapi/cli.rb
CHANGED
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'ruby-progressbar'
|
2
|
+
|
3
|
+
module HaveAPI::CLI
|
4
|
+
# This class can watch action's state and show it's progress with a progress bar.
|
5
|
+
#
|
6
|
+
# When interrupted, the user is asked whether he wishes to cancel the action,
|
7
|
+
# then it shows the progress of cancellation.
|
8
|
+
#
|
9
|
+
# Methods from this class may invoke `exit()` whenever appropriate.
|
10
|
+
class ActionState
|
11
|
+
# @param opts [Hash]
|
12
|
+
# @param action [HaveAPI::Client::Action]
|
13
|
+
# @param id [Integer]
|
14
|
+
def initialize(opts, client, id)
|
15
|
+
@opts = opts
|
16
|
+
@client = client
|
17
|
+
@id = id
|
18
|
+
end
|
19
|
+
|
20
|
+
# Block until the action is finished or timeout is reached. Progress is shown with
|
21
|
+
# a progress bar. Offers cancellation on interrupt.
|
22
|
+
#
|
23
|
+
# @param timeout [Float]
|
24
|
+
# @param cancel [Boolean] determines whether we're waiting for a cancel to finish
|
25
|
+
def wait_for_completion(id: nil, timeout: nil, cancel: false)
|
26
|
+
id ||= @id
|
27
|
+
|
28
|
+
if cancel
|
29
|
+
puts "Waiting for the action to cancel (hit Ctrl+C to skip)..."
|
30
|
+
else
|
31
|
+
puts "Waiting for the action to complete (hit Ctrl+C to skip)..."
|
32
|
+
end
|
33
|
+
|
34
|
+
last_status = false
|
35
|
+
can_cancel = false
|
36
|
+
|
37
|
+
begin
|
38
|
+
ret = HaveAPI::Client::Action.wait_for_completion(
|
39
|
+
@client,
|
40
|
+
id,
|
41
|
+
timeout: timeout,
|
42
|
+
) do |state|
|
43
|
+
last_status = state.status
|
44
|
+
can_cancel = state.can_cancel?
|
45
|
+
|
46
|
+
update_progress(state, cancel)
|
47
|
+
end
|
48
|
+
|
49
|
+
rescue Interrupt
|
50
|
+
@pb && @pb.stop
|
51
|
+
puts
|
52
|
+
|
53
|
+
cancel_action(timeout: timeout) if can_cancel && !cancel && last_status
|
54
|
+
|
55
|
+
puts
|
56
|
+
print_help(id)
|
57
|
+
exit(false)
|
58
|
+
end
|
59
|
+
|
60
|
+
if ret
|
61
|
+
@pb && @pb.finish
|
62
|
+
else
|
63
|
+
@pb && @pb.stop
|
64
|
+
end
|
65
|
+
|
66
|
+
ret
|
67
|
+
end
|
68
|
+
|
69
|
+
# Ask the user if he wishes to cancel the action. If so, execute cancel
|
70
|
+
# and call self.wait_for_completion on the cancellation, if it is blocking
|
71
|
+
# operation.
|
72
|
+
def cancel_action(timeout: nil)
|
73
|
+
STDOUT.write("Do you wish to cancel the action? [y/N]: ")
|
74
|
+
STDOUT.flush
|
75
|
+
|
76
|
+
if STDIN.readline.strip.downcase == 'y'
|
77
|
+
begin
|
78
|
+
res = HaveAPI::Client::Action.cancel(@client, @id)
|
79
|
+
|
80
|
+
rescue HaveAPI::Client::ActionFailed => e
|
81
|
+
res = e.response
|
82
|
+
end
|
83
|
+
|
84
|
+
if res.is_a?(HaveAPI::Client::Response) && res.ok?
|
85
|
+
puts "Cancelled"
|
86
|
+
exit
|
87
|
+
|
88
|
+
elsif res
|
89
|
+
@pb.resume
|
90
|
+
|
91
|
+
wait_for_completion(
|
92
|
+
id: res,
|
93
|
+
timeout: timeout,
|
94
|
+
cancel: true,
|
95
|
+
)
|
96
|
+
exit
|
97
|
+
end
|
98
|
+
|
99
|
+
warn "Cancel failed: #{res.message}"
|
100
|
+
exit(false)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def print_help(id = nil)
|
105
|
+
id ||= @id
|
106
|
+
|
107
|
+
puts "Run"
|
108
|
+
puts " #{$0} action_state show #{id}"
|
109
|
+
puts "or"
|
110
|
+
puts " #{$0} action_state wait #{id}"
|
111
|
+
puts "to check the action's progress."
|
112
|
+
end
|
113
|
+
|
114
|
+
protected
|
115
|
+
def update_progress(state, cancel)
|
116
|
+
@pb ||= ProgressBar.create(
|
117
|
+
title: cancel ? 'Cancelling' : 'Executing',
|
118
|
+
total: state.progress.total,
|
119
|
+
format: if state.progress.total && state.progress.total > 0
|
120
|
+
"%t: [%B] %c/%C #{state.progress.unit}"
|
121
|
+
else
|
122
|
+
'%t: [%B]'
|
123
|
+
end,
|
124
|
+
starting_at: state.progress.current,
|
125
|
+
autofinish: false,
|
126
|
+
)
|
127
|
+
|
128
|
+
if state.status
|
129
|
+
@pb.title = cancel ? 'Cancelling' : 'Executing'
|
130
|
+
|
131
|
+
else
|
132
|
+
@pb.title = 'Failing'
|
133
|
+
end
|
134
|
+
|
135
|
+
if state.progress.total && state.progress.total > 0
|
136
|
+
@pb.progress = state.progress.current
|
137
|
+
@pb.total = state.progress.total
|
138
|
+
@pb.format("%t: [%B] %c/%C #{state.progress.unit}")
|
139
|
+
|
140
|
+
else
|
141
|
+
@pb.total = nil
|
142
|
+
@pb.format("%t: [%B] #{state.progress.unit}")
|
143
|
+
@pb.increment
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
data/lib/haveapi/cli/cli.rb
CHANGED
@@ -11,6 +11,10 @@ module HaveAPI::CLI
|
|
11
11
|
|
12
12
|
def run
|
13
13
|
c = new
|
14
|
+
|
15
|
+
rescue Interrupt
|
16
|
+
warn "Interrupted"
|
17
|
+
exit(false)
|
14
18
|
end
|
15
19
|
|
16
20
|
def register_auth_method(name, klass)
|
@@ -89,7 +93,13 @@ module HaveAPI::CLI
|
|
89
93
|
end
|
90
94
|
|
91
95
|
if authenticate(action) && !action.unresolved_args?
|
92
|
-
|
96
|
+
begin
|
97
|
+
action.update_description(@api.describe_action(action))
|
98
|
+
|
99
|
+
rescue RestClient::ResourceNotFound => e
|
100
|
+
format_errors(action, 'Object not found', {})
|
101
|
+
exit(false)
|
102
|
+
end
|
93
103
|
end
|
94
104
|
|
95
105
|
@selected_params = @opts[:output] ? @opts[:output].split(',').uniq
|
@@ -115,6 +125,32 @@ module HaveAPI::CLI
|
|
115
125
|
format_errors(action, ret[:message], ret[:errors])
|
116
126
|
exit(false)
|
117
127
|
end
|
128
|
+
|
129
|
+
if action.blocking?
|
130
|
+
res = HaveAPI::Client::Response.new(action, ret)
|
131
|
+
|
132
|
+
if res.meta[:action_state_id]
|
133
|
+
state = ActionState.new(
|
134
|
+
@opts,
|
135
|
+
HaveAPI::Client::Client.new(@api.url, communicator: @api, block: false),
|
136
|
+
res.meta[:action_state_id]
|
137
|
+
)
|
138
|
+
|
139
|
+
if @opts[:block]
|
140
|
+
puts
|
141
|
+
action_ret = state.wait_for_completion(timeout: @opts[:timeout])
|
142
|
+
|
143
|
+
if action_ret.nil?
|
144
|
+
warn "Timeout"
|
145
|
+
exit(false)
|
146
|
+
end
|
147
|
+
|
148
|
+
else
|
149
|
+
puts
|
150
|
+
state.print_help
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
118
154
|
end
|
119
155
|
|
120
156
|
def api_url
|
@@ -124,6 +160,7 @@ module HaveAPI::CLI
|
|
124
160
|
def options
|
125
161
|
options = {
|
126
162
|
client: default_url,
|
163
|
+
block: true,
|
127
164
|
verbose: false,
|
128
165
|
}
|
129
166
|
|
@@ -208,6 +245,18 @@ module HaveAPI::CLI
|
|
208
245
|
options[:date_format] = f
|
209
246
|
end
|
210
247
|
|
248
|
+
opts.on('--[no-]block', 'Toggle action blocking mode') do |v|
|
249
|
+
options[:block] = v
|
250
|
+
end
|
251
|
+
|
252
|
+
opts.on(
|
253
|
+
'--timeout SEC',
|
254
|
+
Float,
|
255
|
+
'Fail when the action does not finish within the timeout'
|
256
|
+
) do |v|
|
257
|
+
options[:timeout] = v.to_f
|
258
|
+
end
|
259
|
+
|
211
260
|
opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
|
212
261
|
options[:verbose] = v
|
213
262
|
end
|
@@ -549,8 +598,6 @@ module HaveAPI::CLI
|
|
549
598
|
end
|
550
599
|
|
551
600
|
def authenticate(action = nil)
|
552
|
-
return false if !action.nil? && !action.auth?
|
553
|
-
|
554
601
|
if @auth
|
555
602
|
@auth.communicator = @api
|
556
603
|
@auth.validate
|
@@ -606,14 +653,14 @@ module HaveAPI::CLI
|
|
606
653
|
def format_errors(action, msg, errors)
|
607
654
|
warn "Action failed: #{msg}"
|
608
655
|
|
609
|
-
if errors.any?
|
656
|
+
if errors && errors.any?
|
610
657
|
puts 'Errors:'
|
611
658
|
errors.each do |param, e|
|
612
659
|
puts "\t#{param}: #{e.join('; ')}"
|
613
660
|
end
|
614
661
|
end
|
615
662
|
|
616
|
-
|
663
|
+
puts "\nUse --help to see available parameters and example usage."
|
617
664
|
end
|
618
665
|
|
619
666
|
def find_command(resource, action)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module HaveAPI::CLI::Commands
|
2
|
+
class ActionStateWait < HaveAPI::CLI::Command
|
3
|
+
cmd :action_state, :wait
|
4
|
+
args '<STATE ID>'
|
5
|
+
desc 'Block until the action is finished'
|
6
|
+
|
7
|
+
def exec(args)
|
8
|
+
if args.size < 1
|
9
|
+
warn "Provide argument STATE ID"
|
10
|
+
exit(false)
|
11
|
+
end
|
12
|
+
|
13
|
+
@api.set_opts(block: false)
|
14
|
+
|
15
|
+
state = HaveAPI::CLI::ActionState.new(
|
16
|
+
@global_opts,
|
17
|
+
@api,
|
18
|
+
args.first.to_i
|
19
|
+
)
|
20
|
+
ret = state.wait_for_completion(timeout: @global_opts[:timeout])
|
21
|
+
|
22
|
+
if ret.nil?
|
23
|
+
warn "Timeout"
|
24
|
+
exit(false)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -7,20 +7,27 @@ module HaveAPI::CLI
|
|
7
7
|
# request
|
8
8
|
out << "$ #{$0} #{action.resource_path.join('.')} #{action.name}"
|
9
9
|
|
10
|
-
params = example[:request]
|
10
|
+
params = example[:request]
|
11
11
|
|
12
12
|
if params
|
13
13
|
out << ' --' unless params.empty?
|
14
14
|
|
15
15
|
params.each do |k, v|
|
16
|
-
|
16
|
+
desc = action.param_description(:input, k)
|
17
|
+
next unless desc
|
18
|
+
|
19
|
+
out << ' ' << example_param(k, v, desc)
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
20
23
|
out << "\n"
|
21
24
|
|
22
25
|
# response
|
23
|
-
cli.format_output(
|
26
|
+
cli.format_output(
|
27
|
+
action,
|
28
|
+
{action.namespace(:output).to_sym => example[:response]},
|
29
|
+
out
|
30
|
+
)
|
24
31
|
end
|
25
32
|
end
|
26
33
|
|
data/lib/haveapi/client.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
module HaveAPI::Client
|
2
2
|
class Action
|
3
|
+
attr_reader :client, :api, :name
|
3
4
|
attr_accessor :resource_path
|
4
5
|
|
5
|
-
def initialize(api, name, spec, args)
|
6
|
+
def initialize(client, api, name, spec, args)
|
7
|
+
@client = client
|
6
8
|
@api = api
|
7
9
|
@name = name
|
8
10
|
@spec = spec
|
@@ -18,8 +20,7 @@ module HaveAPI::Client
|
|
18
20
|
end
|
19
21
|
|
20
22
|
ret = @api.call(self, params.to_api)
|
21
|
-
|
22
|
-
@prepared_help = nil
|
23
|
+
reset
|
23
24
|
ret
|
24
25
|
end
|
25
26
|
|
@@ -31,6 +32,10 @@ module HaveAPI::Client
|
|
31
32
|
@spec[:auth]
|
32
33
|
end
|
33
34
|
|
35
|
+
def blocking?
|
36
|
+
@spec[:blocking]
|
37
|
+
end
|
38
|
+
|
34
39
|
def aliases(include_name = false)
|
35
40
|
if include_name
|
36
41
|
[@name] + @spec[:aliases]
|
@@ -83,6 +88,10 @@ module HaveAPI::Client
|
|
83
88
|
@spec[dir][:parameters][name]
|
84
89
|
end
|
85
90
|
|
91
|
+
def meta(scope)
|
92
|
+
@spec[:meta][scope]
|
93
|
+
end
|
94
|
+
|
86
95
|
def url
|
87
96
|
@spec[:url]
|
88
97
|
end
|
@@ -117,9 +126,103 @@ module HaveAPI::Client
|
|
117
126
|
@prepared_help = help
|
118
127
|
end
|
119
128
|
|
129
|
+
def reset
|
130
|
+
@prepared_url = nil
|
131
|
+
@prepared_help = nil
|
132
|
+
end
|
133
|
+
|
120
134
|
def update_description(spec)
|
121
135
|
@spec = spec
|
122
136
|
end
|
137
|
+
|
138
|
+
# Block until the action is completed or timeout occurs. If the block is given,
|
139
|
+
# it is regularly called with the action's state.
|
140
|
+
# @param interval [Float] how often should the action state be checked
|
141
|
+
# @param timeout [Integer] timeout in seconds
|
142
|
+
# @yieldparam state [ActionState]
|
143
|
+
# @return [Boolean] when the action is finished
|
144
|
+
# @return [nil] when timeout occurs
|
145
|
+
# @return [Response] if the action was cancelled and the cancel itself isn't blocking
|
146
|
+
# @return [Integer] id of cancellation if the action was cancelled, cancel is blocking
|
147
|
+
# and no cancel block is provided
|
148
|
+
def self.wait_for_completion(client, id, interval: 15, update_in: 3, timeout: nil)
|
149
|
+
res = client.action_state.show(id)
|
150
|
+
state = ActionState.new(res)
|
151
|
+
|
152
|
+
yield(state) if block_given?
|
153
|
+
return state.status if state.finished?
|
154
|
+
|
155
|
+
last = {}
|
156
|
+
t = Time.now if timeout
|
157
|
+
|
158
|
+
loop do
|
159
|
+
res = client.action_state.poll(
|
160
|
+
id,
|
161
|
+
timeout: interval,
|
162
|
+
update_in: update_in,
|
163
|
+
status: last[:status],
|
164
|
+
current: last[:current],
|
165
|
+
total: last[:total],
|
166
|
+
)
|
167
|
+
|
168
|
+
state = ActionState.new(res)
|
169
|
+
|
170
|
+
last[:status] = res.response[:status]
|
171
|
+
last[:current] = res.response[:current]
|
172
|
+
last[:total] = res.response[:total]
|
173
|
+
|
174
|
+
yield(state) if block_given?
|
175
|
+
break if state.finished?
|
176
|
+
|
177
|
+
if state.cancel?
|
178
|
+
state.stop
|
179
|
+
cancel_block = state.cancel_block
|
180
|
+
|
181
|
+
ret = cancel(client, id)
|
182
|
+
|
183
|
+
if ret.is_a?(Response)
|
184
|
+
# The cancel is not a blocking operation, return immediately
|
185
|
+
raise ActionFailed, ret unless ret.ok?
|
186
|
+
return ret
|
187
|
+
end
|
188
|
+
|
189
|
+
# Cancel is a blocking operation
|
190
|
+
if cancel_block
|
191
|
+
return wait_for_completion(
|
192
|
+
client,
|
193
|
+
ret,
|
194
|
+
interval: interval,
|
195
|
+
timeout: timeout,
|
196
|
+
update_in: update_in,
|
197
|
+
&cancel_block
|
198
|
+
)
|
199
|
+
end
|
200
|
+
|
201
|
+
return ret
|
202
|
+
end
|
203
|
+
|
204
|
+
return nil if (timeout && (Time.now - t) >= timeout) || state.stop?
|
205
|
+
end
|
206
|
+
|
207
|
+
state.status
|
208
|
+
|
209
|
+
rescue Interrupt => e
|
210
|
+
%i(show poll).each do |action|
|
211
|
+
client.action_state.actions[action].reset
|
212
|
+
end
|
213
|
+
raise e
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.cancel(client, id)
|
217
|
+
res = client.action_state.cancel(id, meta: {block: false})
|
218
|
+
|
219
|
+
if res.ok? && res.action.blocking? && res.meta[:action_state_id]
|
220
|
+
res.meta[:action_state_id]
|
221
|
+
|
222
|
+
else
|
223
|
+
res
|
224
|
+
end
|
225
|
+
end
|
123
226
|
|
124
227
|
private
|
125
228
|
def apply_args(args)
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module HaveAPI::Client
|
2
|
+
# Represents action's state as returned from API resource ActionState.Show/Poll.
|
3
|
+
class ActionState
|
4
|
+
Progress = Struct.new(:current, :total, :unit) do
|
5
|
+
def percent
|
6
|
+
100.0 / total * current
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
"#{current}/#{total} #{unit}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :progress
|
15
|
+
|
16
|
+
def initialize(response)
|
17
|
+
@data = response.response
|
18
|
+
|
19
|
+
@progress = Progress.new(@data[:current], @data[:total], @data[:unit])
|
20
|
+
end
|
21
|
+
|
22
|
+
def label
|
23
|
+
@data[:label]
|
24
|
+
end
|
25
|
+
|
26
|
+
def status
|
27
|
+
@data[:status] === true
|
28
|
+
end
|
29
|
+
|
30
|
+
def finished?
|
31
|
+
@data[:finished] === true
|
32
|
+
end
|
33
|
+
|
34
|
+
def can_cancel?
|
35
|
+
@data[:can_cancel] === true
|
36
|
+
end
|
37
|
+
|
38
|
+
# Stop monitoring the action's state and attempt to cancel it. The `block`
|
39
|
+
# is given to Action.wait_for_completion for the cancel operation. The block
|
40
|
+
# is used only if the cancel operation is blocking.
|
41
|
+
def cancel(&block)
|
42
|
+
unless can_cancel?
|
43
|
+
fail "action ##{@data[:id]} (#{label}) cannot be cancelled"
|
44
|
+
end
|
45
|
+
|
46
|
+
@cancel = true
|
47
|
+
@cancel_block = block
|
48
|
+
end
|
49
|
+
|
50
|
+
def cancel?
|
51
|
+
@cancel === true
|
52
|
+
end
|
53
|
+
|
54
|
+
def cancel_block
|
55
|
+
@cancel_block
|
56
|
+
end
|
57
|
+
|
58
|
+
# Stop monitoring the action's state, the call from Action.wait_for_completion
|
59
|
+
# will return.
|
60
|
+
def stop
|
61
|
+
@stop = true
|
62
|
+
end
|
63
|
+
|
64
|
+
def stop?
|
65
|
+
!@stop.nil? && @stop
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -36,7 +36,13 @@ module HaveAPI::Client::Authentication
|
|
36
36
|
|
37
37
|
protected
|
38
38
|
def request_token
|
39
|
-
a = HaveAPI::Client::Action.new(
|
39
|
+
a = HaveAPI::Client::Action.new(
|
40
|
+
nil,
|
41
|
+
@communicator,
|
42
|
+
:request,
|
43
|
+
@desc[:resources][:token][:actions][:request],
|
44
|
+
[]
|
45
|
+
)
|
40
46
|
ret = a.execute({
|
41
47
|
login: @opts[:user],
|
42
48
|
password: @opts[:password],
|
@@ -8,16 +8,27 @@ class HaveAPI::Client::Client
|
|
8
8
|
# The client by default uses the default version of the API.
|
9
9
|
# API is asked for description only when needed or by calling #setup.
|
10
10
|
# +identity+ is sent in each request to the API in User-Agent header.
|
11
|
-
|
11
|
+
# @param url [String] API URL
|
12
|
+
# @param opts [Hash]
|
13
|
+
# @option opts [String] version
|
14
|
+
# @option opts [String] identity
|
15
|
+
# @option opts [HaveAPI::Client::Communicator] communicator
|
16
|
+
# @option opts [Boolean] block
|
17
|
+
# @option opts [Integer] block_interval
|
18
|
+
# @option opts [Integer] block_timeout
|
19
|
+
def initialize(url, opts = {})
|
12
20
|
@setup = false
|
13
|
-
@
|
21
|
+
@opts = opts
|
22
|
+
@version = @opts[:version]
|
23
|
+
@opts[:identity] ||= 'haveapi-client'
|
24
|
+
@opts[:block] = true if @opts[:block].nil?
|
14
25
|
|
15
|
-
if communicator
|
16
|
-
@api = communicator
|
26
|
+
if @opts[:communicator]
|
27
|
+
@api = @opts[:communicator]
|
17
28
|
|
18
29
|
else
|
19
|
-
@api = HaveAPI::Client::Communicator.new(url,
|
20
|
-
@api.identity = identity
|
30
|
+
@api = HaveAPI::Client::Communicator.new(url, @version)
|
31
|
+
@api.identity = @opts[:identity]
|
21
32
|
end
|
22
33
|
end
|
23
34
|
|
@@ -54,6 +65,22 @@ class HaveAPI::Client::Client
|
|
54
65
|
@api.compatible?
|
55
66
|
end
|
56
67
|
|
68
|
+
# return [Boolean] true if global blocking mode is enabled
|
69
|
+
def blocking?
|
70
|
+
@opts[:block]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Override selected client options
|
74
|
+
# @param opts [Hash] options
|
75
|
+
def set_opts(opts)
|
76
|
+
@opts.update(opts)
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [Hash] client options
|
80
|
+
def opts(*keys)
|
81
|
+
keys.empty? ? @opts.clone : @opts.select { |k, _| keys.include?(k) }
|
82
|
+
end
|
83
|
+
|
57
84
|
# Initialize the client if it is not yet initialized and call the resource
|
58
85
|
# if it exists.
|
59
86
|
def method_missing(symbol, *args)
|
@@ -97,7 +97,7 @@ module HaveAPI::Client
|
|
97
97
|
end
|
98
98
|
|
99
99
|
if a
|
100
|
-
obj = Action.new(self, action, a, args)
|
100
|
+
obj = Action.new(nil, self, action, a, args)
|
101
101
|
obj.resource_path = resources
|
102
102
|
obj
|
103
103
|
else
|
@@ -144,6 +144,9 @@ module HaveAPI::Client
|
|
144
144
|
rescue RestClient::Forbidden
|
145
145
|
return error('Access forbidden. Bad user name or password? Not authorized?')
|
146
146
|
|
147
|
+
rescue RestClient::ResourceNotFound => e
|
148
|
+
response = parse(e.http_body)
|
149
|
+
|
147
150
|
rescue => e
|
148
151
|
return error("Fatal API error: #{e.inspect}")
|
149
152
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
1
3
|
module HaveAPI::Client
|
2
4
|
class Parameters::Typed
|
3
5
|
module Boolean
|
@@ -52,7 +54,7 @@ module HaveAPI::Client
|
|
52
54
|
|
53
55
|
elsif type == 'Datetime'
|
54
56
|
begin
|
55
|
-
|
57
|
+
DateTime.iso8601(raw).to_time
|
56
58
|
|
57
59
|
rescue ArgumentError
|
58
60
|
@errors << 'not in ISO 8601 format'
|
@@ -17,7 +17,7 @@ module HaveAPI::Client
|
|
17
17
|
@description = description
|
18
18
|
|
19
19
|
description[:actions].each do |name, desc|
|
20
|
-
action = HaveAPI::Client::Action.new(@api, name, desc, [])
|
20
|
+
action = HaveAPI::Client::Action.new(@client, @api, name, desc, [])
|
21
21
|
define_action(action)
|
22
22
|
@actions[name] = action
|
23
23
|
end
|
@@ -74,7 +74,8 @@ module HaveAPI::Client
|
|
74
74
|
action.aliases(true).each do |name|
|
75
75
|
next unless define_method?(action, name)
|
76
76
|
|
77
|
-
define_singleton_method(name) do |*args|
|
77
|
+
define_singleton_method(name) do |*args, &block|
|
78
|
+
client_opts = @client.opts(:block, :block_interval, :block_timeout)
|
78
79
|
all_args = @prepared_args + args
|
79
80
|
|
80
81
|
if action.unresolved_args?
|
@@ -96,6 +97,14 @@ module HaveAPI::Client
|
|
96
97
|
elsif all_args.last.is_a?(Hash)
|
97
98
|
last = all_args.pop
|
98
99
|
|
100
|
+
if last.has_key?(:meta)
|
101
|
+
meta = last[:meta]
|
102
|
+
|
103
|
+
%i(block block_interval block_timeout).each do |p|
|
104
|
+
client_opts[p] = meta.delete(p) if meta.has_key?(p)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
99
108
|
all_args << default_action_input_params(action).update(last)
|
100
109
|
end
|
101
110
|
|
@@ -103,7 +112,7 @@ module HaveAPI::Client
|
|
103
112
|
|
104
113
|
raise ActionFailed.new(ret) unless ret.ok?
|
105
114
|
|
106
|
-
case action.output_layout
|
115
|
+
return_value = case action.output_layout
|
107
116
|
when :object
|
108
117
|
ResourceInstance.new(@client, @api, self, action: action, response: ret)
|
109
118
|
|
@@ -116,6 +125,23 @@ module HaveAPI::Client
|
|
116
125
|
else
|
117
126
|
ret
|
118
127
|
end
|
128
|
+
|
129
|
+
if action.blocking? && client_opts[:block]
|
130
|
+
wait_opts = {}
|
131
|
+
|
132
|
+
{
|
133
|
+
block_interval: :interval,
|
134
|
+
block_timeout: :timeout
|
135
|
+
}.each do |k, v|
|
136
|
+
wait_opts[v] = client_opts[k] if client_opts.has_key?(k)
|
137
|
+
end
|
138
|
+
|
139
|
+
ret.wait_for_completion(wait_opts) do |state|
|
140
|
+
block.call(return_value, state) if block
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
return_value
|
119
145
|
end
|
120
146
|
end
|
121
147
|
end
|
@@ -22,11 +22,13 @@ module HaveAPI::Client
|
|
22
22
|
if response.is_a?(Hash)
|
23
23
|
@params = response
|
24
24
|
@prepared_args = response[:_meta][:url_params]
|
25
|
+
@meta = response[:_meta] unless @meta
|
25
26
|
|
26
27
|
else
|
27
28
|
@response = response
|
28
29
|
@params = response.response
|
29
30
|
@prepared_args = response.meta[:url_params]
|
31
|
+
@meta = response.meta unless @meta
|
30
32
|
end
|
31
33
|
|
32
34
|
setup_from_clone(resource)
|
@@ -112,10 +114,13 @@ module HaveAPI::Client
|
|
112
114
|
@resource_instances[name] = find_association(param, @params[name])
|
113
115
|
|
114
116
|
# id reader
|
115
|
-
ensure_method(:"#{name}_id")
|
117
|
+
ensure_method(:"#{name}_id") do
|
118
|
+
@params[name] && @params[name][ param[:value_id].to_sym ]
|
119
|
+
end
|
116
120
|
|
117
121
|
# id writer
|
118
122
|
ensure_method(:"#{name}_id=") do |id|
|
123
|
+
@params[name] ||= {}
|
119
124
|
@params[name][ param[:value_id].to_sym ] = id
|
120
125
|
|
121
126
|
@resource_instances[name] = find_association(
|
@@ -139,6 +144,7 @@ module HaveAPI::Client
|
|
139
144
|
|
140
145
|
# value writer
|
141
146
|
ensure_method(:"#{name}=") do |obj|
|
147
|
+
@params[name] ||= {}
|
142
148
|
@params[name][ param[:value_id].to_sym ] = obj.method(param[:value_id]).call
|
143
149
|
@params[name][ param[:value_label].to_sym ] = obj.method(param[:value_label]).call
|
144
150
|
|
@@ -56,4 +56,15 @@ class HaveAPI::Client::Response
|
|
56
56
|
|
57
57
|
@response[:response][@action.namespace(:output).to_sym].each
|
58
58
|
end
|
59
|
+
|
60
|
+
# Block until the action is completed or timeout occurs. If the block is given,
|
61
|
+
# it is regularly called with the action's state.
|
62
|
+
#
|
63
|
+
# @see HaveAPI::Client::Action#wait_for_completion
|
64
|
+
def wait_for_completion(*args, &block)
|
65
|
+
id = meta[:action_state_id]
|
66
|
+
return nil unless id
|
67
|
+
|
68
|
+
HaveAPI::Client::Action.wait_for_completion(@action.client, id, *args, &block)
|
69
|
+
end
|
59
70
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: haveapi-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jakub Skokan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-11-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - ~>
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: 1.7.8
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: ruby-progressbar
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.7.5
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.7.5
|
111
125
|
description: Ruby API and CLI for HaveAPI
|
112
126
|
email:
|
113
127
|
- jakub.skokan@vpsfree.cz
|
@@ -124,15 +138,18 @@ files:
|
|
124
138
|
- bin/haveapi-cli
|
125
139
|
- haveapi-client.gemspec
|
126
140
|
- lib/haveapi/cli.rb
|
141
|
+
- lib/haveapi/cli/action_state.rb
|
127
142
|
- lib/haveapi/cli/authentication/base.rb
|
128
143
|
- lib/haveapi/cli/authentication/basic.rb
|
129
144
|
- lib/haveapi/cli/authentication/token.rb
|
130
145
|
- lib/haveapi/cli/cli.rb
|
131
146
|
- lib/haveapi/cli/command.rb
|
147
|
+
- lib/haveapi/cli/commands/action_state_wait.rb
|
132
148
|
- lib/haveapi/cli/example_formatter.rb
|
133
149
|
- lib/haveapi/cli/output_formatter.rb
|
134
150
|
- lib/haveapi/client.rb
|
135
151
|
- lib/haveapi/client/action.rb
|
152
|
+
- lib/haveapi/client/action_state.rb
|
136
153
|
- lib/haveapi/client/authentication/base.rb
|
137
154
|
- lib/haveapi/client/authentication/basic.rb
|
138
155
|
- lib/haveapi/client/authentication/noauth.rb
|
@@ -186,4 +203,3 @@ signing_key:
|
|
186
203
|
specification_version: 4
|
187
204
|
summary: Ruby API and CLI for HaveAPI
|
188
205
|
test_files: []
|
189
|
-
has_rdoc:
|