kennel 1.117.0 → 1.118.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/kennel/compatibility.rb +27 -0
- data/lib/kennel/file_cache.rb +1 -1
- 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/file_cache.rb
CHANGED
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
|