haveapi-client 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9360cc54a28c352ca36a156d1cfdfc96eea32959
4
- data.tar.gz: c47ae6db07e500c6cf47f1836436349a9634e746
3
+ metadata.gz: fbf74153c675555c4efe868808662ecba7edab78
4
+ data.tar.gz: 9841b9182c4585edbb415cc7e9a09637c1b0491b
5
5
  SHA512:
6
- metadata.gz: 934b2293efe40af87bda6cef7a880ac8b4fdc793b8d675b8d8ee06e4dbe5d818b88b626ce7609ec5b43a228d7d7453e4cb74776f627ce84a34713b852af3c0e6
7
- data.tar.gz: e5b17a1f4d8f3367db75579008f0a4fefb6a1e5613b2f030b36951ad0076a2b1aa948a8737d70cbd6b45aae855881d2fda2fbcf91bedeacaa5428a377bc3bcf2
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
@@ -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-10-18'
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
@@ -1,7 +1,10 @@
1
1
  require_relative 'client'
2
2
 
3
3
  module HaveAPI
4
- module CLI ; end
4
+ module CLI
5
+ module Commands ; end
6
+ end
5
7
  end
6
8
 
7
9
  require_rel 'cli'
10
+ require_rel 'cli/commands'
@@ -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
@@ -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
- action.update_description(@api.describe_action(action))
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
- print_examples(action)
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][action.namespace(:input).to_sym]
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
- out << ' ' << example_param(k, v, action.param_description(:input, k))
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(action, example[:response], out)
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
 
@@ -2,7 +2,12 @@ require 'require_all'
2
2
  require 'date'
3
3
 
4
4
  module HaveAPI
5
- module Client ; end
5
+ module Client
6
+ # Shortcut to HaveAPI::Client::Client.new
7
+ def self.new(*args)
8
+ HaveAPI::Client::Client.new(*args)
9
+ end
10
+ end
6
11
  end
7
12
 
8
13
  require_rel 'client'
@@ -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
- @prepared_url = nil
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(@communicator, :request, @desc[:resources][:token][:actions][:request], [])
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
- def initialize(url, v = nil, identity: 'haveapi-client', communicator: nil)
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
- @version = v
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, v)
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
- Time.iso8601(raw)
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") { @params[name][ param[:value_id].to_sym ] }
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
@@ -1,6 +1,6 @@
1
1
  module HaveAPI
2
2
  module Client
3
- PROTOCOL_VERSION = '1.0'
4
- VERSION = '0.6.0'
3
+ PROTOCOL_VERSION = '1.1'
4
+ VERSION = '0.7.0'
5
5
  end
6
6
  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.6.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-10-18 00:00:00.000000000 Z
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: