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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0cb7a96d8fed145f6db8ab76ee7cac6287f24af57ee8bc785707830548e07a76
4
- data.tar.gz: '076522890c0d40993b8f6a676e106da15b145df4e3361e851f7bc70d38ed096c'
3
+ metadata.gz: 307545b12b594343bcd2fdc3ec46736e76c2cbc44a16e13eec8d9fd1ab7071b9
4
+ data.tar.gz: dae53fe1fc68395b4945eb3c664afc7655abe5f8434385e634b39760fbc5fd5e
5
5
  SHA512:
6
- metadata.gz: 18c8879eaa8aff627087b164156127ac6358060985e1adb79d1a8fe9994b23647d17fe629bdd99fb8c2410f8685c17500e4a890611dd5d028f43185caef1e0e8
7
- data.tar.gz: 1ab7202e9152b7e71aee2d40b42b3fdf91ea2c2af5290152f147254895e79c83b40da015efef7e5b8855b72fe49cde96c0c4e127ba80108a388966ccae4dbe39
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
@@ -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[/-- Managed by kennel (#{ALLOWED_KENNEL_ID_FULL})/, 1]
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?-- Managed by kennel .*/, "") ||
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 \"-- Managed by kennel\" line it from #{self.class::TRACKING_FIELD} to copy a resource"
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
- "-- Managed by kennel #{tracking_id} in #{project.class.file_location}, do not modify manually".lstrip
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
- @update << [id, e, a, diff] if diff.any?
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
- branch = (ENV["TRAVIS_BRANCH"] || ENV["GITHUB_REF"]).to_s.sub(/^refs\/heads\//, "")
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(/-- Managed by kennel (\S+:\S+) in (\S+), /) || []
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
- require "kennel"
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "1.117.1"
3
+ VERSION = "1.118.0"
4
4
  end
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
- @out = $stdout
43
- @err = $stderr
44
- @strict_imports = true
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.117.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-05 00:00:00.000000000 Z
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