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 +4 -4
- data/Readme.md +5 -1
- data/lib/kennel.rb +20 -3
- data/lib/kennel/api.rb +85 -45
- data/lib/kennel/file_cache.rb +9 -5
- data/lib/kennel/importer.rb +6 -6
- data/lib/kennel/models/record.rb +1 -1
- data/lib/kennel/models/synthetic_test.rb +63 -0
- data/lib/kennel/progress.rb +2 -0
- data/lib/kennel/syncer.rb +32 -36
- data/lib/kennel/tasks.rb +1 -1
- data/lib/kennel/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9f3417e3f9c4f8b950c89047ed25c5f44a0da8e8c7f28667f33b549b2d6fadc3
|
|
4
|
+
data.tar.gz: 9fec0221be92d34d7ade923a9f92bee18e9f602d719c041e21fc274fb6d10c9f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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:
|
|
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
|
|
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
|
-
|
|
15
|
-
api_resource == "slo"
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
+
result = yield
|
|
143
|
+
File.write(file, Marshal.dump(result))
|
|
144
|
+
result
|
|
105
145
|
end
|
|
106
146
|
end
|
|
107
147
|
end
|
data/lib/kennel/file_cache.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# cache that reads everything from a single file
|
|
4
|
-
#
|
|
5
|
-
#
|
|
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
|
-
|
|
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,
|
|
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! { |_, (_, _,
|
|
54
|
+
@data.reject! { |_, (_, (_, cv), expires)| expires < @now || cv != @cache_version }
|
|
51
55
|
end
|
|
52
56
|
end
|
|
53
57
|
end
|
data/lib/kennel/importer.rb
CHANGED
|
@@ -15,11 +15,8 @@ module Kennel
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
model =
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
117
|
+
request.delete(:response_format)
|
|
118
118
|
request[:q] = request.delete(:queries).first.fetch(:query)
|
|
119
119
|
end
|
|
120
120
|
end
|
data/lib/kennel/models/record.rb
CHANGED
|
@@ -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
|
data/lib/kennel/progress.rb
CHANGED
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
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
130
|
-
|
|
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(:
|
|
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 { |
|
|
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[
|
|
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 ||
|
|
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
|
|
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
|
|
239
|
+
def filter_actual_by_project!(actual)
|
|
244
240
|
return unless @project_filter
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
!
|
|
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
|
data/lib/kennel/version.rb
CHANGED
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.
|
|
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-
|
|
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
|