kennel 1.89.0 → 1.91.1

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: bdedd0f9bbcb9abf873cd2f9be0414700f77d2ea864e609db79773d1b3193e8e
4
- data.tar.gz: 336875475665fac019c12daced64e474fb8e85e216e158d4328b62b287785e6a
3
+ metadata.gz: 9f3417e3f9c4f8b950c89047ed25c5f44a0da8e8c7f28667f33b549b2d6fadc3
4
+ data.tar.gz: 9fec0221be92d34d7ade923a9f92bee18e9f602d719c041e21fc274fb6d10c9f
5
5
  SHA512:
6
- metadata.gz: 0041e466ce433d82f9db8252829f8c64652815d7bf932ec93596d57af76dc13aaa9dfce963bbf82888e09d424937b597364954edc1cff14344143f0661e8e274
7
- data.tar.gz: a286f227e597686085049ef0227b2e36b26d33ef41a7d96d13a535af73e1530254de6bf68ead11ebdd0c394d1562766a91a478c794ee17f35c288a7f102d46b7
6
+ metadata.gz: b31a95230656072e45d260aa549010ad5add9313af68f2490ac3d3c2220b9fe525ee6af144a3ca988103503af7f4a15cdbeb52601e1ff0427a5809714807be73
7
+ data.tar.gz: 6754253f9cc10a00b663947860527a596308856e3beca48538eef1f19f9e02aa11c98ce981b5f7f7815bb177f4b329e971decf0dd8326ccc6a767d163c2970de
data/Readme.md CHANGED
@@ -52,6 +52,7 @@ end
52
52
  ```
53
53
 
54
54
  <!-- NOT IN template/Readme.md -->
55
+
55
56
  ## Installation
56
57
 
57
58
  - create a new private `kennel` repo for your organization (do not fork this repo)
@@ -293,9 +294,12 @@ https://foo.datadog.com/monitor/123
293
294
 
294
295
  <!-- NOT IN template/Readme.md -->
295
296
 
296
-
297
297
  ## Development
298
298
 
299
+ ### Benchmarking
300
+
301
+ Setting `FORCE_GET_CACHE=true` will cache all get requests, which makes benchmarking improvements more reliable.
302
+
299
303
  ### Integration testing
300
304
 
301
305
  ```Bash
data/lib/kennel.rb CHANGED
@@ -23,6 +23,7 @@ require "kennel/models/record"
23
23
  require "kennel/models/dashboard"
24
24
  require "kennel/models/monitor"
25
25
  require "kennel/models/slo"
26
+ require "kennel/models/synthetic_test"
26
27
 
27
28
  # settings
28
29
  require "kennel/models/project"
@@ -55,7 +56,7 @@ module Kennel
55
56
 
56
57
  def store(parts)
57
58
  Progress.progress "Storing" do
58
- old = Dir["generated/**/*"]
59
+ old = Dir["generated/#{project_filter || "**"}/*"]
59
60
  used = []
60
61
 
61
62
  Utils.parallel(parts, max: 2) do |part|
@@ -83,7 +84,7 @@ module Kennel
83
84
  end
84
85
 
85
86
  def syncer
86
- @syncer ||= Syncer.new(api, generated, project: ENV["PROJECT"])
87
+ @syncer ||= Syncer.new(api, generated, project: project_filter)
87
88
  end
88
89
 
89
90
  def api
@@ -94,9 +95,21 @@ module Kennel
94
95
  @generated ||= begin
95
96
  Progress.progress "Generating" do
96
97
  load_all
98
+ known = []
97
99
  parts = Models::Project.recursive_subclasses.flat_map do |project_class|
98
- project_class.new.validated_parts
100
+ project = project_class.new
101
+ kennel_id = project.kennel_id
102
+ if project_filter
103
+ known << kennel_id
104
+ next [] if kennel_id != project_filter
105
+ end
106
+ project.validated_parts
99
107
  end
108
+
109
+ if project_filter && parts.empty?
110
+ raise "#{project_filter} does not match any projects, try any of these:\n#{known.uniq.sort.join("\n")}"
111
+ end
112
+
100
113
  parts.group_by(&:tracking_id).each do |tracking_id, same|
101
114
  next if same.size == 1
102
115
  raise <<~ERROR
@@ -109,6 +122,10 @@ module Kennel
109
122
  end
110
123
  end
111
124
 
125
+ def project_filter
126
+ ENV["PROJECT"]
127
+ end
128
+
112
129
  def load_all
113
130
  ["teams", "parts", "projects"].each do |folder|
114
131
  Dir["#{folder}/**/*.rb"].sort.each { |f| require "./#{f}" }
data/lib/kennel/api.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
+ # encapsulates knowledge around how the api works
3
+ # especially 1-off weirdness that should not lak into other parts of the code
2
4
  module Kennel
3
- # encapsulates knowledge around how the api works
4
5
  class Api
5
6
  CACHE_FILE = "tmp/cache/details"
6
7
 
@@ -11,49 +12,55 @@ module Kennel
11
12
  end
12
13
 
13
14
  def show(api_resource, id, params = {})
14
- reply = request :get, "/api/v1/#{api_resource}/#{id}", params: params
15
- api_resource == "slo" ? reply[:data] : reply
15
+ response = request :get, "/api/v1/#{api_resource}/#{id}", params: params
16
+ response = response.fetch(:data) if api_resource == "slo"
17
+ response[:id] = response.delete(:public_id) if api_resource == "synthetics/tests"
18
+ response
16
19
  end
17
20
 
18
21
  def list(api_resource, params = {})
19
- if api_resource == "slo"
20
- raise ArgumentError if params[:limit] || params[:offset]
21
- limit = 1000
22
- offset = 0
23
- all = []
24
-
25
- loop do
26
- result = request :get, "/api/v1/#{api_resource}", params: params.merge(limit: limit, offset: offset)
27
- data = result.fetch(:data)
28
- all.concat data
29
- break all if data.size < limit
30
- offset += limit
22
+ with_pagination api_resource == "slo", params do |paginated_params|
23
+ response = request :get, "/api/v1/#{api_resource}", params: paginated_params
24
+ response = response.fetch(:dashboards) if api_resource == "dashboard"
25
+ response = response.fetch(:data) if api_resource == "slo"
26
+ if api_resource == "synthetics/tests"
27
+ response = response.fetch(:tests)
28
+ response.each { |r| r[:id] = r.delete(:public_id) }
31
29
  end
32
- else
33
- result = request :get, "/api/v1/#{api_resource}", params: params
34
- result = result.fetch(:dashboards) if api_resource == "dashboard"
35
- result
30
+
31
+ # ignore monitor synthetics create and that inherit the kennel_id, we do not directly manage them
32
+ response.reject! { |m| m[:type] == "synthetics alert" } if api_resource == "monitor"
33
+
34
+ response
36
35
  end
37
36
  end
38
37
 
39
38
  def create(api_resource, attributes)
40
- reply = request :post, "/api/v1/#{api_resource}", body: attributes
41
- api_resource == "slo" ? reply[:data].first : reply
39
+ response = request :post, "/api/v1/#{api_resource}", body: attributes
40
+ response = response.fetch(:data).first if api_resource == "slo"
41
+ response[:id] = response.delete(:public_id) if api_resource == "synthetics/tests"
42
+ response
42
43
  end
43
44
 
44
45
  def update(api_resource, id, attributes)
45
- request :put, "/api/v1/#{api_resource}/#{id}", body: attributes
46
+ response = request :put, "/api/v1/#{api_resource}/#{id}", body: attributes
47
+ response[:id] = response.delete(:public_id) if api_resource == "synthetics/tests"
48
+ response
46
49
  end
47
50
 
48
51
  # - force=true to not dead-lock on dependent monitors+slos
49
52
  # external dependency on kennel managed resources is their problem, we don't block on it
50
53
  # (?force=true did not work, force for dashboard is not documented but does not blow up)
51
54
  def delete(api_resource, id)
52
- request :delete, "/api/v1/#{api_resource}/#{id}", params: { force: "true" }, ignore_404: true
55
+ if api_resource == "synthetics/tests"
56
+ # https://docs.datadoghq.com/api/latest/synthetics/#delete-tests
57
+ request :post, "/api/v1/#{api_resource}/delete", body: { public_ids: [id] }, ignore_404: true
58
+ else
59
+ request :delete, "/api/v1/#{api_resource}/#{id}", params: { force: "true" }, ignore_404: true
60
+ end
53
61
  end
54
62
 
55
63
  def fill_details!(api_resource, list)
56
- return unless api_resource == "dashboard"
57
64
  details_cache do |cache|
58
65
  Utils.parallel(list) { |a| fill_detail!(api_resource, a, cache) }
59
66
  end
@@ -61,6 +68,21 @@ module Kennel
61
68
 
62
69
  private
63
70
 
71
+ def with_pagination(enabled, params)
72
+ return yield params unless enabled
73
+ raise ArgumentError if params[:limit] || params[:offset]
74
+ limit = 1000
75
+ offset = 0
76
+ all = []
77
+
78
+ loop do
79
+ response = yield params.merge(limit: limit, offset: offset)
80
+ all.concat response
81
+ return all if response.size < limit
82
+ offset += limit
83
+ end
84
+ end
85
+
64
86
  # Make diff work even though we cannot mass-fetch definitions
65
87
  def fill_detail!(api_resource, a, cache)
66
88
  args = [api_resource, a.fetch(:id)]
@@ -74,34 +96,52 @@ module Kennel
74
96
  end
75
97
 
76
98
  def request(method, path, body: nil, params: {}, ignore_404: false)
77
- params = params.merge(application_key: @app_key, api_key: @api_key)
78
- query = Faraday::FlatParamsEncoder.encode(params)
79
- response = nil
80
- tries = 2
81
-
82
- tries.times do |i|
83
- response = Utils.retry Faraday::ConnectionFailed, Faraday::TimeoutError, times: 2 do
84
- @client.send(method, "#{path}?#{query}") do |request|
85
- request.body = JSON.generate(body) if body
86
- request.headers["Content-type"] = "application/json"
99
+ path = "#{path}?#{Faraday::FlatParamsEncoder.encode(params)}" if params.any?
100
+ with_cache ENV["FORCE_GET_CACHE"] && method == :get, path do
101
+ response = nil
102
+ tries = 2
103
+
104
+ tries.times do |i|
105
+ response = Utils.retry Faraday::ConnectionFailed, Faraday::TimeoutError, times: 2 do
106
+ @client.send(method, path) do |request|
107
+ request.body = JSON.generate(body) if body
108
+ request.headers["Content-type"] = "application/json"
109
+ request.headers["DD-API-KEY"] = @api_key
110
+ request.headers["DD-APPLICATION-KEY"] = @app_key
111
+ end
87
112
  end
113
+
114
+ break if i == tries - 1 || method != :get || response.status < 500
115
+ Kennel.err.puts "Retrying on server error #{response.status} for #{path}"
88
116
  end
89
117
 
90
- break if i == tries - 1 || method != :get || response.status < 500
91
- Kennel.err.puts "Retrying on server error #{response.status} for #{path}"
92
- end
118
+ if !response.success? && (response.status != 404 || !ignore_404)
119
+ message = +"Error #{response.status} during #{method.upcase} #{path}\n"
120
+ message << "request:\n#{JSON.pretty_generate(body)}\nresponse:\n" if body
121
+ message << response.body
122
+ raise message
123
+ end
93
124
 
94
- if !response.success? && (response.status != 404 || !ignore_404)
95
- message = +"Error #{response.status} during #{method.upcase} #{path}\n"
96
- message << "request:\n#{JSON.pretty_generate(body)}\nresponse:\n" if body
97
- message << response.body
98
- raise message
125
+ if response.body.empty?
126
+ {}
127
+ else
128
+ JSON.parse(response.body, symbolize_names: true)
129
+ end
99
130
  end
131
+ end
100
132
 
101
- if response.body.empty?
102
- {}
133
+ # allow caching all requests to speedup/benchmark logic that includes repeated requests
134
+ def with_cache(enabled, key)
135
+ return yield unless enabled
136
+ dir = "tmp/cache"
137
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
138
+ file = "#{dir}/#{key.delete("/?=")}" # TODO: encode nicely
139
+ if File.exist?(file)
140
+ Marshal.load(File.read(file)) # rubocop:disable Security/MarshalLoad
103
141
  else
104
- JSON.parse(response.body, symbolize_names: true)
142
+ result = yield
143
+ File.write(file, Marshal.dump(result))
144
+ result
105
145
  end
106
146
  end
107
147
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # cache that reads everything from a single file
4
- # to avoid doing multiple disk reads while iterating all definitions
5
- # it also replaces updated keys and has an overall expiry to not keep deleted things forever
4
+ # - avoids doing multiple disk reads while iterating all definitions
5
+ # - has a global expiry to not keep deleted resources forever
6
6
  module Kennel
7
7
  class FileCache
8
8
  def initialize(file, cache_version)
@@ -22,10 +22,11 @@ module Kennel
22
22
 
23
23
  def fetch(key, key_version)
24
24
  old_value, old_version = @data[key]
25
- return old_value if old_version == [key_version, @cache_version]
25
+ expected_version = [key_version, @cache_version]
26
+ return old_value if old_version == expected_version
26
27
 
27
28
  new_value = yield
28
- @data[key] = [new_value, [key_version, @cache_version], @expires]
29
+ @data[key] = [new_value, expected_version, @expires]
29
30
  new_value
30
31
  end
31
32
 
@@ -46,8 +47,11 @@ module Kennel
46
47
  File.write(@file, Marshal.dump(@data))
47
48
  end
48
49
 
50
+ # keep the cache small to make loading it fast (5MB ~= 100ms)
51
+ # - delete expired keys
52
+ # - delete what would be deleted anyway when updating
49
53
  def expire_old_data
50
- @data.reject! { |_, (_, _, ex)| ex < @now }
54
+ @data.reject! { |_, (_, (_, cv), expires)| expires < @now || cv != @cache_version }
51
55
  end
52
56
  end
53
57
  end
@@ -15,11 +15,8 @@ module Kennel
15
15
  end
16
16
 
17
17
  model =
18
- begin
19
- Kennel::Models.const_get(resource.capitalize)
20
- rescue NameError
21
- raise ArgumentError, "#{resource} is not supported"
22
- end
18
+ Kennel::Models::Record.subclasses.detect { |c| c.api_resource == resource } ||
19
+ raise(ArgumentError, "#{resource} is not supported")
23
20
 
24
21
  data = @api.show(model.api_resource, id)
25
22
  id = data.fetch(:id) # keep native value
@@ -70,6 +67,8 @@ module Kennel
70
67
  dry_up_widget_metadata!(widget)
71
68
  (widget.dig(:definition, :markers) || []).each { |m| m[:label]&.delete! " " }
72
69
  end
70
+ when "synthetics/tests"
71
+ data[:locations] = :all if data[:locations].sort == Kennel::Models::SyntheticTest::LOCATIONS.sort
73
72
  else
74
73
  # noop
75
74
  end
@@ -113,8 +112,9 @@ module Kennel
113
112
  next if request[:formulas] && request[:formulas] != [{ formula: "query1" }]
114
113
  next if request[:queries]&.size != 1
115
114
  next if request[:queries].any? { |q| q[:data_source] != "metrics" }
115
+ next if widget.dig(:definition, :type) != request[:response_format]
116
116
  request.delete(:formulas)
117
- request[:type] = request.delete(:response_format)
117
+ request.delete(:response_format)
118
118
  request[:q] = request.delete(:queries).first.fetch(:query)
119
119
  end
120
120
  end
@@ -6,7 +6,7 @@ module Kennel
6
6
  TRACKING_FIELDS = [:message, :description].freeze
7
7
  READONLY_ATTRIBUTES = [
8
8
  :deleted, :id, :created, :created_at, :creator, :org_id, :modified, :modified_at,
9
- :klass # added by syncer.rb
9
+ :klass, :tracking_id # added by syncer.rb
10
10
  ].freeze
11
11
 
12
12
  settings :id, :kennel_id
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ module Kennel
3
+ module Models
4
+ class SyntheticTest < Record
5
+ TRACKING_FIELD = :message
6
+ DEFAULTS = {
7
+ }.freeze
8
+ READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [:status, :monitor_id]
9
+ LOCATIONS = ["aws:ca-central-1", "aws:eu-north-1", "aws:eu-west-1", "aws:eu-west-3", "aws:eu-west-2", "aws:ap-south-1", "aws:us-west-2", "aws:us-west-1", "aws:sa-east-1", "aws:us-east-2", "aws:ap-northeast-1", "aws:ap-northeast-2", "aws:eu-central-1", "aws:ap-southeast-2", "aws:ap-southeast-1"].freeze
10
+
11
+ settings :tags, :config, :message, :subtype, :type, :name, :locations, :options
12
+
13
+ defaults(
14
+ id: -> { nil },
15
+ tags: -> { @project.tags },
16
+ message: -> { "\n\n#{project.mention}" }
17
+ )
18
+
19
+ def as_json
20
+ return @as_json if @as_json
21
+ locations = locations()
22
+ data = {
23
+ message: message,
24
+ tags: tags,
25
+ config: config,
26
+ type: type,
27
+ subtype: subtype,
28
+ options: options,
29
+ name: name,
30
+ locations: locations == :all ? LOCATIONS : locations
31
+ }
32
+
33
+ if v = id
34
+ data[:id] = v
35
+ end
36
+
37
+ @as_json = data
38
+ end
39
+
40
+ def self.api_resource
41
+ "synthetics/tests"
42
+ end
43
+
44
+ def self.url(id)
45
+ Utils.path_to_url "/synthetics/details/#{id}"
46
+ end
47
+
48
+ def self.parse_url(url)
49
+ url[/\/synthetics\/details\/([a-z\d-]{11,})/, 1] # id format is 1ab-2ab-3ab
50
+ end
51
+
52
+ def self.normalize(expected, actual)
53
+ super
54
+
55
+ # tags come in a semi-random order and order is never updated
56
+ expected[:tags]&.sort!
57
+ actual[:tags].sort!
58
+
59
+ ignore_default(expected, actual, DEFAULTS)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -29,6 +29,8 @@ module Kennel
29
29
  Kennel.err.print "#{time.round(2)}s\n"
30
30
 
31
31
  result
32
+ ensure
33
+ stop = true
32
34
  end
33
35
  end
34
36
  end
data/lib/kennel/syncer.rb CHANGED
@@ -1,22 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
3
  class Syncer
4
- DELETE_ORDER = ["dashboard", "slo", "monitor"].freeze # dashboards references monitors + slos, slos reference monitors
5
- LINE_UP = "\e[1A"
4
+ DELETE_ORDER = ["dashboard", "slo", "monitor", "synthetics/tests"].freeze # dashboards references monitors + slos, slos reference monitors
5
+ LINE_UP = "\e[1A\033[K" # go up and clear
6
6
 
7
7
  def initialize(api, expected, project: nil)
8
8
  @api = api
9
9
  @project_filter = project
10
10
  @expected = expected
11
- if @project_filter
12
- original = @expected
13
- @expected = @expected.select { |e| e.project.kennel_id == @project_filter }
14
- if @expected.empty?
15
- possible = original.map { |e| e.project.kennel_id }.uniq.sort
16
- raise "#{@project_filter} does not match any projects, try any of these:\n#{possible.join("\n")}"
17
- end
18
- end
19
- @expected.each(&:add_tracking_id)
20
11
  calculate_diff
21
12
  prevent_irreversible_partial_updates
22
13
  end
@@ -41,9 +32,9 @@ module Kennel
41
32
  message = "#{e.class.api_resource} #{e.tracking_id}"
42
33
  Kennel.out.puts "Creating #{message}"
43
34
  reply = @api.create e.class.api_resource, e.as_json
44
- reply[:klass] = e.class # store api resource class for later use
35
+ cache_metadata reply, e.class
45
36
  id = reply.fetch(:id)
46
- populate_id_map [reply] # allow resolving ids we could previously no resolve
37
+ populate_id_map [], [reply] # allow resolving ids we could previously no resolve
47
38
  Kennel.out.puts "#{LINE_UP}Created #{message} #{e.class.url(id)}"
48
39
  end
49
40
 
@@ -56,8 +47,7 @@ module Kennel
56
47
 
57
48
  @delete.each do |id, _, a|
58
49
  klass = a.fetch(:klass)
59
- tracking_id = klass.parse_tracking_id(a)
60
- message = "#{klass.api_resource} #{tracking_id} #{id}"
50
+ message = "#{klass.api_resource} #{a.fetch(:tracking_id)} #{id}"
61
51
  Kennel.out.puts "Deleting #{message}"
62
52
  @api.delete klass.api_resource, id
63
53
  Kennel.out.puts "#{LINE_UP}Deleted #{message}"
@@ -108,14 +98,13 @@ module Kennel
108
98
 
109
99
  actual = Progress.progress("Downloading definitions") { download_definitions }
110
100
 
111
- # resolve dependencies to avoid diff
112
- populate_id_map actual
113
- @expected.each { |e| @id_map[e.tracking_id] ||= :new }
114
- resolve_linked_tracking_ids! @expected
101
+ Progress.progress "Diffing" do
102
+ populate_id_map @expected, actual
103
+ filter_actual_by_project! actual
104
+ resolve_linked_tracking_ids! @expected # resolve dependencies to avoid diff
115
105
 
116
- filter_by_project! actual
106
+ @expected.each(&:add_tracking_id) # avoid diff with actual
117
107
 
118
- Progress.progress "Diffing" do
119
108
  items = actual.map do |a|
120
109
  e = matching_expected(a)
121
110
  if e && @expected.delete(e)
@@ -126,9 +115,8 @@ module Kennel
126
115
  end
127
116
 
128
117
  # fill details of things we need to compare
129
- detailed = Hash.new { |h, k| h[k] = [] }
130
- items.each { |e, a| detailed[a[:klass]] << a if e }
131
- detailed.each { |klass, actuals| @api.fill_details! klass.api_resource, actuals }
118
+ details = items.map { |e, a| a if e && e.class.api_resource == "dashboard" }.compact
119
+ @api.fill_details! "dashboard", details
132
120
 
133
121
  # pick out things to update or delete
134
122
  items.each do |e, a|
@@ -136,26 +124,30 @@ module Kennel
136
124
  if e
137
125
  diff = e.diff(a)
138
126
  @update << [id, e, a, diff] if diff.any?
139
- elsif a.fetch(:klass).parse_tracking_id(a) # was previously managed
127
+ elsif a.fetch(:tracking_id) # was previously managed
140
128
  @delete << [id, nil, a]
141
129
  end
142
130
  end
143
131
 
144
132
  ensure_all_ids_found
145
133
  @create = @expected.map { |e| [nil, e] }
134
+ @delete.sort_by! { |_, _, a| DELETE_ORDER.index a.fetch(:klass).api_resource }
146
135
  end
147
-
148
- @delete.sort_by! { |_, _, a| DELETE_ORDER.index a.fetch(:klass).api_resource }
149
136
  end
150
137
 
151
138
  def download_definitions
152
139
  Utils.parallel(Models::Record.subclasses) do |klass|
153
140
  results = @api.list(klass.api_resource, with_downtimes: false) # lookup monitors without adding unnecessary downtime information
154
141
  results = results[results.keys.first] if results.is_a?(Hash) # dashboards are nested in {dashboards: []}
155
- results.each { |c| c[:klass] = klass } # store api resource for later diffing
142
+ results.each { |a| cache_metadata(a, klass) }
156
143
  end.flatten(1)
157
144
  end
158
145
 
146
+ def cache_metadata(a, klass)
147
+ a[:klass] = klass
148
+ a[:tracking_id] = a.fetch(:klass).parse_tracking_id(a)
149
+ end
150
+
159
151
  def ensure_all_ids_found
160
152
  @expected.each do |e|
161
153
  next unless id = e.id
@@ -176,14 +168,14 @@ module Kennel
176
168
  end
177
169
 
178
170
  klass = a.fetch(:klass)
179
- @lookup_map["#{klass.api_resource}:#{a.fetch(:id)}"] || @lookup_map[klass.parse_tracking_id(a)]
171
+ @lookup_map["#{klass.api_resource}:#{a.fetch(:id)}"] || @lookup_map[a.fetch(:tracking_id)]
180
172
  end
181
173
 
182
174
  def print_plan(step, list, color)
183
175
  return if list.empty?
184
176
  list.each do |_, e, a, diff|
185
177
  klass = (e ? e.class : a.fetch(:klass))
186
- Kennel.out.puts Utils.color(color, "#{step} #{klass.api_resource} #{e&.tracking_id || klass.parse_tracking_id(a)}")
178
+ Kennel.out.puts Utils.color(color, "#{step} #{klass.api_resource} #{e&.tracking_id || a.fetch(:tracking_id)}")
187
179
  print_diff(diff) if diff # only for update
188
180
  end
189
181
  end
@@ -232,19 +224,23 @@ module Kennel
232
224
  end
233
225
  end
234
226
 
235
- def populate_id_map(actual)
236
- actual.each { |a| @id_map[a.fetch(:klass).parse_tracking_id(a)] = a.fetch(:id) }
227
+ def populate_id_map(expected, actual)
228
+ actual.each do |a|
229
+ next unless tracking_id = a.fetch(:tracking_id)
230
+ @id_map[tracking_id] = a.fetch(:id)
231
+ end
232
+ expected.each { |e| @id_map[e.tracking_id] ||= :new }
237
233
  end
238
234
 
239
235
  def resolve_linked_tracking_ids!(list, force: false)
240
236
  list.each { |e| e.resolve_linked_tracking_ids!(@id_map, force: force) }
241
237
  end
242
238
 
243
- def filter_by_project!(definitions)
239
+ def filter_actual_by_project!(actual)
244
240
  return unless @project_filter
245
- definitions.select! do |a|
246
- id = a.fetch(:klass).parse_tracking_id(a)
247
- !id || id.start_with?("#{@project_filter}:")
241
+ actual.select! do |a|
242
+ tracking_id = a.fetch(:tracking_id)
243
+ !tracking_id || tracking_id.start_with?("#{@project_filter}:")
248
244
  end
249
245
  end
250
246
  end
data/lib/kennel/tasks.rb CHANGED
@@ -138,7 +138,7 @@ namespace :kennel do
138
138
  resources.each do |resource|
139
139
  Kennel::Progress.progress("Downloading #{resource}") do
140
140
  list = api.list(resource)
141
- api.fill_details!(resource, list)
141
+ api.fill_details!(resource, list) if resource == "dashboard"
142
142
  end
143
143
  list.each do |r|
144
144
  r[:api_resource] = resource
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "1.89.0"
3
+ VERSION = "1.91.1"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kennel
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.89.0
4
+ version: 1.91.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-25 00:00:00.000000000 Z
11
+ date: 2021-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -70,6 +70,7 @@ files:
70
70
  - lib/kennel/models/project.rb
71
71
  - lib/kennel/models/record.rb
72
72
  - lib/kennel/models/slo.rb
73
+ - lib/kennel/models/synthetic_test.rb
73
74
  - lib/kennel/models/team.rb
74
75
  - lib/kennel/optional_validations.rb
75
76
  - lib/kennel/progress.rb