kennel 2.21.0 → 2.22.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f604eb100e46e40ba76333717c9ddb99e06cd85be98f7eb19217b7be1b193b2
4
- data.tar.gz: 352ead0a6c2ce295f14113d03f9ab956278851d55457340dffa9d6fdf042856e
3
+ metadata.gz: b931b9c2d75d6e0d69967e3088795e72d8a5f3f558ff1dd1b6de03efde7aabf8
4
+ data.tar.gz: '081dde24122f351f92c7dc8f16e7877ec3dcd1f7226bd1e4fc126abd3044798d'
5
5
  SHA512:
6
- metadata.gz: 5d0e3477bcef3043433fca52d3e3c3940f7cd315c7c89ecebb7a07eb7a50d56adcb46522a51492ef47c72346b10bec7446c495c8d368c5ff741a510e2c8885b9
7
- data.tar.gz: 0e71a447829ac1dcc996b08858e7dcd720b3e3f6f9e5b2e008bc0e187e952e1cafaa561b62152a37e547053d033ef0c2e37746419205eb1788d859c536f5d77a
6
+ metadata.gz: ffd01589e499c34d72dd477f5e3e00369582c42cacbf7eae19493e791b84ecbac42c48c5bf34e3d8c8a6245f555d29f62e3cb879cc2e24892b555766f9b5ffcc
7
+ data.tar.gz: 7a95114123013fd90a64993f86f217b5f4379e0d3dc6b19d876d2ac42f404b4907ef35b2396c811c4291838ca8183073523090bb2d89882dbbffa6972b40bb38
data/lib/kennel/api.rb CHANGED
@@ -22,8 +22,9 @@ module Kennel
22
22
  @client = Faraday.new(url: url)
23
23
  end
24
24
 
25
- def show(api_resource, id, params = {})
26
- response = request :get, "/api/v1/#{api_resource}/#{id}", params: params
25
+ def show(api_resource, id, params = {}, ignore_404: false)
26
+ response = request :get, "/api/v1/#{api_resource}/#{id}", params: params, ignore_404: ignore_404
27
+ return if response.nil? # 404 that was ignored
27
28
  response = response.fetch(:data) if api_resource == "slo"
28
29
  response[:id] = response.delete(:public_id) if api_resource == "synthetics/tests"
29
30
  self.class.with_tracking(api_resource, response)
@@ -54,6 +55,8 @@ module Kennel
54
55
  end
55
56
 
56
57
  def update(api_resource, id, attributes)
58
+ restore_widget_ids(id, attributes) if api_resource == "dashboard"
59
+
57
60
  response = request :put, "/api/v1/#{api_resource}/#{id}", body: attributes
58
61
  response[:id] = response.delete(:public_id) if api_resource == "synthetics/tests"
59
62
  self.class.with_tracking(api_resource, response)
@@ -74,12 +77,44 @@ module Kennel
74
77
  # fill the resource with the full response from the `show` if `list` does not return it
75
78
  def fill_details!(api_resource, list)
76
79
  details_cache do |cache|
77
- Utils.parallel(list) { |a| fill_detail!(api_resource, a, cache) }.sum
80
+ results = Utils.parallel(list) { |a| fill_detail!(api_resource, a, cache) }
81
+
82
+ # drop resources that were not found (race condition between listing and showing)
83
+ list.select!.with_index { |_, i| results[i][1] }
84
+
85
+ results.count { |uncached, _| uncached }
78
86
  end
79
87
  end
80
88
 
81
89
  private
82
90
 
91
+ # keep widget ids stable so stored dashboard urls that point at a widget do not break on every update
92
+ # widget ids are dropped when diffing since they change on every update, but that also changes them in datadog
93
+ # and breaks stored urls that point at a specific widget, so restore the previous ids by matching widget titles
94
+ def restore_widget_ids(id, attributes)
95
+ return unless (widgets = attributes[:widgets])
96
+ return unless (current = show("dashboard", id, {}, ignore_404: true))
97
+
98
+ ids = {}
99
+ each_widget_with_key(current[:widgets]) { |key, widget| ids[key] = widget[:id] }
100
+ each_widget_with_key(widgets) do |key, widget|
101
+ next unless (id = ids[key])
102
+ widget[:id] ||= id # do not set nil, that breaks updates
103
+ end
104
+ end
105
+
106
+ # yield each widget with a key built from its (optional) group title and its own title
107
+ def each_widget_with_key(widgets, prefix: nil, &block)
108
+ widgets.each do |widget|
109
+ title = widget.dig(:definition, :title)
110
+ key = [prefix, title].compact.join("-")
111
+ yield key, widget
112
+ if (nested = widget.dig(:definition, :widgets))
113
+ each_widget_with_key(nested, prefix: title, &block)
114
+ end
115
+ end
116
+ end
117
+
83
118
  def with_pagination(enabled, params)
84
119
  return yield params unless enabled
85
120
  raise ArgumentError if params[:limit] || params[:offset]
@@ -96,15 +131,16 @@ module Kennel
96
131
  end
97
132
 
98
133
  # Make diff work even though we cannot mass-fetch definitions
134
+ # @return [uncached, found]
99
135
  def fill_detail!(api_resource, a, cache)
100
- uncached = 0
136
+ uncached = false
101
137
  args = [api_resource, a.fetch(:id)]
102
138
  full = cache.fetch(args, a.fetch(:modified_at)) do
103
- uncached += 1
104
- show(*args)
139
+ uncached = true
140
+ show(*args, ignore_404: true)
105
141
  end
106
- a.merge!(full)
107
- uncached
142
+ a.merge!(full) if full
143
+ [uncached, !!full]
108
144
  end
109
145
 
110
146
  def details_cache(&block)
@@ -117,29 +153,18 @@ module Kennel
117
153
  cached = (ENV["FORCE_GET_CACHE"] && method == :get)
118
154
 
119
155
  with_cache cached, path do
120
- response = nil
121
- tries.times do |i|
122
- response = Utils.retry Faraday::ConnectionFailed, Faraday::TimeoutError, times: 2 do
123
- @client.send(method, path) do |request|
124
- request.body = JSON.generate(body) if body
125
- request.headers["Content-type"] = "application/json"
126
- request.headers["DD-API-KEY"] = @api_key
127
- request.headers["DD-APPLICATION-KEY"] = @app_key
128
- end
156
+ response = request_with_retries(path, tries) do
157
+ @client.send(method, path) do |request|
158
+ request.body = JSON.generate(body) if body
159
+ request.headers["Content-type"] = "application/json"
160
+ request.headers["DD-API-KEY"] = @api_key
161
+ request.headers["DD-APPLICATION-KEY"] = @app_key
129
162
  end
130
-
131
- if response.status == 429
132
- sleep_until_rate_limit_resets(response)
133
- redo
134
- end
135
-
136
- last_try = (i == tries - 1)
137
- break if last_try || response.status < 500
138
- Kennel.err.puts "Retrying on server error #{response.status} for #{path}"
139
- sleep retry_backoff_time(i)
140
163
  end
141
164
 
142
- if !response.success? && (response.status != 404 || !ignore_404)
165
+ next if response.status == 404 && ignore_404
166
+
167
+ unless response.success?
143
168
  message = "Error #{response.status} during #{method.upcase} #{path}\n"
144
169
  message << "request:\n#{JSON.pretty_generate(body)}\nresponse:\n" if body
145
170
  message << response.body.encode(message.encoding, invalid: :replace, undef: :replace)
@@ -154,6 +179,28 @@ module Kennel
154
179
  end
155
180
  end
156
181
 
182
+ # retry on rate-limits and server errors, giving up after `tries` attempts
183
+ def request_with_retries(path, tries, &block)
184
+ raise ArgumentError, "tries must be > 0" if tries < 1
185
+ response = nil
186
+ tries.times do |i|
187
+ response = Utils.retry Faraday::ConnectionFailed, Faraday::TimeoutError, times: 2, &block
188
+
189
+ # we do not count 429s into the "tries", tries are only for real errors
190
+ # so this could loop forever if datadog consistently 429s
191
+ if response.status == 429
192
+ sleep_until_rate_limit_resets(response)
193
+ redo
194
+ end
195
+
196
+ last_try = (i == tries - 1)
197
+ break if last_try || response.status < 500
198
+ Kennel.err.puts "Retrying on server error #{response.status} for #{path}"
199
+ sleep retry_backoff_time(i)
200
+ end
201
+ response
202
+ end
203
+
157
204
  def sleep_until_rate_limit_resets(response)
158
205
  limit = response.headers["x-ratelimit-limit"]
159
206
  period = response.headers["x-ratelimit-period"]
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "2.21.0"
3
+ VERSION = "2.22.0"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kennel
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.21.0
4
+ version: 2.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser