kennel 1.117.1 → 1.118.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/lib/kennel/compatibility.rb +27 -0
- data/lib/kennel/models/record.rb +20 -4
- data/lib/kennel/syncer.rb +24 -1
- data/lib/kennel/tasks.rb +41 -25
- data/lib/kennel/version.rb +1 -1
- data/lib/kennel.rb +27 -6
- 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: 307545b12b594343bcd2fdc3ec46736e76c2cbc44a16e13eec8d9fd1ab7071b9
|
4
|
+
data.tar.gz: dae53fe1fc68395b4945eb3c664afc7655abe5f8434385e634b39760fbc5fd5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 347f7c2611e987fb30335917cbd14281111f0083f5aef023422881c8e3d86b0ad71d5d71daa60a686b69e08454aec43fcc9499a49b02a01117f17fbd8e80aa29
|
7
|
+
data.tar.gz: e5205665b571cd51c82ad48f34e56bd1813d94632fa3e71f0318e0eb26c36b04e7d9ce057af8e20d14fd568f826d6df499805be1b77e5584c61ef36a4111ac70
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kennel
|
4
|
+
module Compatibility
|
5
|
+
def self.included(into)
|
6
|
+
class << into
|
7
|
+
%I[out out= err err= strict_imports strict_imports= generate plan update].each do |sym|
|
8
|
+
define_method(sym) { |*args| instance.public_send(sym, *args) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def build_default
|
12
|
+
Kennel::Engine.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def instance
|
16
|
+
@instance ||= build_default
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def api
|
22
|
+
instance.send(:api)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/kennel/models/record.rb
CHANGED
@@ -2,6 +2,22 @@
|
|
2
2
|
module Kennel
|
3
3
|
module Models
|
4
4
|
class Record < Base
|
5
|
+
# Apart from if you just don't like the default for some reason,
|
6
|
+
# overriding MARKER_TEXT allows for namespacing within the same
|
7
|
+
# Datadog account. If you run one Kennel setup with marker text
|
8
|
+
# A and another with marker text B (assuming that A isn't a
|
9
|
+
# substring of B and vice versa), then the two Kennel setups will
|
10
|
+
# operate independently of each other, not trampling over each
|
11
|
+
# other's objects.
|
12
|
+
#
|
13
|
+
# This could be useful for allowing multiple products / projects
|
14
|
+
# / teams to share a Datadog account but otherwise largely
|
15
|
+
# operate independently of each other. In particular, it can be
|
16
|
+
# useful for running a "dev" or "staging" instance of Kennel
|
17
|
+
# in the same account as, but mostly isolated from, a "production"
|
18
|
+
# instance.
|
19
|
+
MARKER_TEXT = ENV.fetch("KENNEL_MARKER_TEXT", "Managed by kennel")
|
20
|
+
|
5
21
|
LOCK = "\u{1F512}"
|
6
22
|
TRACKING_FIELDS = [:message, :description].freeze
|
7
23
|
READONLY_ATTRIBUTES = [
|
@@ -28,14 +44,14 @@ module Kennel
|
|
28
44
|
end
|
29
45
|
|
30
46
|
def parse_tracking_id(a)
|
31
|
-
a[self::TRACKING_FIELD].to_s[/--
|
47
|
+
a[self::TRACKING_FIELD].to_s[/-- #{Regexp.escape(MARKER_TEXT)} (#{ALLOWED_KENNEL_ID_FULL})/, 1]
|
32
48
|
end
|
33
49
|
|
34
50
|
# TODO: combine with parse into a single method or a single regex
|
35
51
|
def remove_tracking_id(a)
|
36
52
|
value = a[self::TRACKING_FIELD]
|
37
53
|
a[self::TRACKING_FIELD] =
|
38
|
-
value.dup.sub!(/\n?--
|
54
|
+
value.dup.sub!(/\n?-- #{Regexp.escape(MARKER_TEXT)} .*/, "") ||
|
39
55
|
raise("did not find tracking id in #{value}")
|
40
56
|
end
|
41
57
|
|
@@ -92,11 +108,11 @@ module Kennel
|
|
92
108
|
def add_tracking_id
|
93
109
|
json = as_json
|
94
110
|
if self.class.parse_tracking_id(json)
|
95
|
-
invalid! "remove \"--
|
111
|
+
invalid! "remove \"-- #{MARKER_TEXT}\" line it from #{self.class::TRACKING_FIELD} to copy a resource"
|
96
112
|
end
|
97
113
|
json[self.class::TRACKING_FIELD] =
|
98
114
|
"#{json[self.class::TRACKING_FIELD]}\n" \
|
99
|
-
"--
|
115
|
+
"-- #{MARKER_TEXT} #{tracking_id} in #{project.class.file_location}, do not modify manually".lstrip
|
100
116
|
end
|
101
117
|
|
102
118
|
def remove_tracking_id
|
data/lib/kennel/syncer.rb
CHANGED
@@ -7,6 +7,9 @@ module Kennel
|
|
7
7
|
DELETE_ORDER = ["dashboard", "slo", "monitor", "synthetics/tests"].freeze # dashboards references monitors + slos, slos reference monitors
|
8
8
|
LINE_UP = "\e[1A\033[K" # go up and clear
|
9
9
|
|
10
|
+
Plan = Struct.new(:noop?, :no_change, :create, :update, :delete, keyword_init: true)
|
11
|
+
Update = Struct.new(:update_log, keyword_init: true)
|
12
|
+
|
10
13
|
def initialize(api, expected, project_filter: nil, tracking_id_filter: nil)
|
11
14
|
@api = api
|
12
15
|
@project_filter = project_filter
|
@@ -27,6 +30,14 @@ module Kennel
|
|
27
30
|
print_plan "Update", @update, :yellow
|
28
31
|
print_plan "Delete", @delete, :red
|
29
32
|
end
|
33
|
+
|
34
|
+
Plan.new(
|
35
|
+
noop?: noop?,
|
36
|
+
no_change: @no_change,
|
37
|
+
create: @create,
|
38
|
+
update: @update,
|
39
|
+
delete: @delete
|
40
|
+
)
|
30
41
|
end
|
31
42
|
|
32
43
|
def confirm
|
@@ -36,12 +47,15 @@ module Kennel
|
|
36
47
|
end
|
37
48
|
|
38
49
|
def update
|
50
|
+
update_log = []
|
51
|
+
|
39
52
|
each_resolved @create do |_, e|
|
40
53
|
message = "#{e.class.api_resource} #{e.tracking_id}"
|
41
54
|
Kennel.out.puts "Creating #{message}"
|
42
55
|
reply = @api.create e.class.api_resource, e.as_json
|
43
56
|
cache_metadata reply, e.class
|
44
57
|
id = reply.fetch(:id)
|
58
|
+
update_log << [:create, e.class.api_resource, id]
|
45
59
|
populate_id_map [], [reply] # allow resolving ids we could previously no resolve
|
46
60
|
Kennel.out.puts "#{LINE_UP}Created #{message} #{e.class.url(id)}"
|
47
61
|
end
|
@@ -50,6 +64,7 @@ module Kennel
|
|
50
64
|
message = "#{e.class.api_resource} #{e.tracking_id} #{e.class.url(id)}"
|
51
65
|
Kennel.out.puts "Updating #{message}"
|
52
66
|
@api.update e.class.api_resource, id, e.as_json
|
67
|
+
update_log << [:update, e.class.api_resource, id]
|
53
68
|
Kennel.out.puts "#{LINE_UP}Updated #{message}"
|
54
69
|
end
|
55
70
|
|
@@ -58,8 +73,11 @@ module Kennel
|
|
58
73
|
message = "#{klass.api_resource} #{a.fetch(:tracking_id)} #{id}"
|
59
74
|
Kennel.out.puts "Deleting #{message}"
|
60
75
|
@api.delete klass.api_resource, id
|
76
|
+
update_log << [:delete, klass.api_resource, id]
|
61
77
|
Kennel.out.puts "#{LINE_UP}Deleted #{message}"
|
62
78
|
end
|
79
|
+
|
80
|
+
Update.new(update_log: update_log)
|
63
81
|
end
|
64
82
|
|
65
83
|
private
|
@@ -103,6 +121,7 @@ module Kennel
|
|
103
121
|
@warnings = []
|
104
122
|
@update = []
|
105
123
|
@delete = []
|
124
|
+
@no_change = []
|
106
125
|
@id_map = IdMap.new
|
107
126
|
|
108
127
|
actual = Progress.progress("Downloading definitions") { download_definitions }
|
@@ -133,7 +152,11 @@ module Kennel
|
|
133
152
|
id = a.fetch(:id)
|
134
153
|
if e
|
135
154
|
diff = e.diff(a) # slow ...
|
136
|
-
|
155
|
+
if diff.any?
|
156
|
+
@update << [id, e, a, diff]
|
157
|
+
else
|
158
|
+
@no_change << [id, e, a]
|
159
|
+
end
|
137
160
|
elsif a.fetch(:tracking_id) # was previously managed
|
138
161
|
@delete << [id, nil, a]
|
139
162
|
end
|
data/lib/kennel/tasks.rb
CHANGED
@@ -12,6 +12,44 @@ module Kennel
|
|
12
12
|
Kennel.err.puts message if message
|
13
13
|
raise SystemExit.new(1), message
|
14
14
|
end
|
15
|
+
|
16
|
+
def load_environment
|
17
|
+
@load_environment ||= begin
|
18
|
+
require "kennel"
|
19
|
+
gem "dotenv"
|
20
|
+
require "dotenv"
|
21
|
+
source = ".env"
|
22
|
+
|
23
|
+
# warn when users have things like DATADOG_TOKEN already set and it will not be loaded from .env
|
24
|
+
unless ENV["KENNEL_SILENCE_UPDATED_ENV"]
|
25
|
+
updated = Dotenv.parse(source).select { |k, v| ENV[k] && ENV[k] != v }
|
26
|
+
warn "Environment variables #{updated.keys.join(", ")} need to be unset to be sourced from #{source}" if updated.any?
|
27
|
+
end
|
28
|
+
|
29
|
+
Dotenv.load(source)
|
30
|
+
true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def ci
|
35
|
+
environment
|
36
|
+
|
37
|
+
if on_default_branch? && git_push?
|
38
|
+
Kennel.strict_imports = false
|
39
|
+
Kennel.update
|
40
|
+
else
|
41
|
+
Kennel.plan # show plan in CI logs
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def on_default_branch?
|
46
|
+
branch = (ENV["TRAVIS_BRANCH"] || ENV["GITHUB_REF"]).to_s.sub(/^refs\/heads\//, "")
|
47
|
+
(branch == (ENV["DEFAULT_BRANCH"] || "master"))
|
48
|
+
end
|
49
|
+
|
50
|
+
def git_push?
|
51
|
+
(ENV["TRAVIS_PULL_REQUEST"] == "false" || ENV["GITHUB_EVENT_NAME"] == "push")
|
52
|
+
end
|
15
53
|
end
|
16
54
|
end
|
17
55
|
end
|
@@ -71,18 +109,7 @@ namespace :kennel do
|
|
71
109
|
|
72
110
|
desc "update on push to the default branch, otherwise show plan"
|
73
111
|
task :ci do
|
74
|
-
|
75
|
-
on_default_branch = (branch == (ENV["DEFAULT_BRANCH"] || "master"))
|
76
|
-
is_push = (ENV["TRAVIS_PULL_REQUEST"] == "false" || ENV["GITHUB_EVENT_NAME"] == "push")
|
77
|
-
task_name =
|
78
|
-
if on_default_branch && is_push
|
79
|
-
Kennel.strict_imports = false
|
80
|
-
"kennel:update_datadog"
|
81
|
-
else
|
82
|
-
"kennel:plan" # show plan in CI logs
|
83
|
-
end
|
84
|
-
|
85
|
-
Rake::Task[task_name].invoke
|
112
|
+
Kennel::Tasks.ci
|
86
113
|
end
|
87
114
|
|
88
115
|
desc "show unmuted alerts filtered by TAG, for example TAG=team:foo"
|
@@ -123,7 +150,7 @@ namespace :kennel do
|
|
123
150
|
|
124
151
|
if ENV["FORMAT"] == "json"
|
125
152
|
report = monitors.map do |m|
|
126
|
-
match = m[:message].to_s.match(/--
|
153
|
+
match = m[:message].to_s.match(/-- #{Regexp.escape(Kennel::Models::Record::MARKER_TEXT)} (\S+:\S+) in (\S+), /) || []
|
127
154
|
m.slice(:url, :name, :tags, :days_in_no_data).merge(
|
128
155
|
kennel_tracking_id: match[1],
|
129
156
|
kennel_source: match[2]
|
@@ -207,17 +234,6 @@ namespace :kennel do
|
|
207
234
|
end
|
208
235
|
|
209
236
|
task :environment do
|
210
|
-
|
211
|
-
gem "dotenv"
|
212
|
-
require "dotenv"
|
213
|
-
source = ".env"
|
214
|
-
|
215
|
-
# warn when users have things like DATADOG_TOKEN already set and it will not be loaded from .env
|
216
|
-
unless ENV["KENNEL_SILENCE_UPDATED_ENV"]
|
217
|
-
updated = Dotenv.parse(source).select { |k, v| ENV[k] && ENV[k] != v }
|
218
|
-
warn "Environment variables #{updated.keys.join(", ")} need to be unset to be sourced from #{source}" if updated.any?
|
219
|
-
end
|
220
|
-
|
221
|
-
Dotenv.load(source)
|
237
|
+
Kennel::Tasks.load_environment
|
222
238
|
end
|
223
239
|
end
|
data/lib/kennel/version.rb
CHANGED
data/lib/kennel.rb
CHANGED
@@ -5,6 +5,7 @@ require "zeitwerk"
|
|
5
5
|
require "English"
|
6
6
|
|
7
7
|
require "kennel/version"
|
8
|
+
require "kennel/compatibility"
|
8
9
|
require "kennel/utils"
|
9
10
|
require "kennel/progress"
|
10
11
|
require "kennel/syncer"
|
@@ -39,11 +40,17 @@ module Kennel
|
|
39
40
|
class ValidationError < RuntimeError
|
40
41
|
end
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
include Kennel::Compatibility
|
44
|
+
|
45
|
+
UpdateResult = Struct.new(:plan, :update, keyword_init: true)
|
46
|
+
|
47
|
+
class Engine
|
48
|
+
def initialize
|
49
|
+
@out = $stdout
|
50
|
+
@err = $stderr
|
51
|
+
@strict_imports = true
|
52
|
+
end
|
45
53
|
|
46
|
-
class << self
|
47
54
|
attr_accessor :out, :err, :strict_imports
|
48
55
|
|
49
56
|
def generate
|
@@ -57,8 +64,12 @@ module Kennel
|
|
57
64
|
end
|
58
65
|
|
59
66
|
def update
|
60
|
-
syncer.plan
|
61
|
-
syncer.update if syncer.confirm
|
67
|
+
the_plan = syncer.plan
|
68
|
+
the_update = syncer.update if syncer.confirm
|
69
|
+
UpdateResult.new(
|
70
|
+
plan: the_plan,
|
71
|
+
update: the_update
|
72
|
+
)
|
62
73
|
end
|
63
74
|
|
64
75
|
private
|
@@ -164,6 +175,16 @@ module Kennel
|
|
164
175
|
end
|
165
176
|
|
166
177
|
def load_all
|
178
|
+
# load_all's purpose is to "require" all the .rb files under './projects',
|
179
|
+
# also with reference to ./teams and ./parts. What happens if you call it
|
180
|
+
# more than once?
|
181
|
+
#
|
182
|
+
# For a reason yet to be investigated, Zeitwerk rejects second and subsequent calls.
|
183
|
+
# But even if we skip over the Zeitwerk part, the nature of 'require' is
|
184
|
+
# somewhat one-way: we're not providing any mechanism to *un*load things.
|
185
|
+
# As long as the contents of `./projects`, `./teams` and `./parts` doesn't
|
186
|
+
# change between calls, then simply by no-op'ing subsequent calls to `load_all`
|
187
|
+
# we can have `load_all` appear to be idempotent.
|
167
188
|
loader = Zeitwerk::Loader.new
|
168
189
|
Dir.exist?("teams") && loader.push_dir("teams", namespace: Teams)
|
169
190
|
Dir.exist?("parts") && loader.push_dir("parts")
|
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.118.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Grosser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-09-
|
11
|
+
date: 2022-09-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: diff-lcs
|
@@ -89,6 +89,7 @@ files:
|
|
89
89
|
- Readme.md
|
90
90
|
- lib/kennel.rb
|
91
91
|
- lib/kennel/api.rb
|
92
|
+
- lib/kennel/compatibility.rb
|
92
93
|
- lib/kennel/file_cache.rb
|
93
94
|
- lib/kennel/github_reporter.rb
|
94
95
|
- lib/kennel/id_map.rb
|