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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83531b8281e71f8a5f8e307320c81f2ecf4c320da617c27777b08325f1394ed1
4
- data.tar.gz: 1d3bce11c52a35f07a48546d37147213a8ca407e7e30710a8efaedc59be8dcd5
3
+ metadata.gz: 307545b12b594343bcd2fdc3ec46736e76c2cbc44a16e13eec8d9fd1ab7071b9
4
+ data.tar.gz: dae53fe1fc68395b4945eb3c664afc7655abe5f8434385e634b39760fbc5fd5e
5
5
  SHA512:
6
- metadata.gz: fddea7652c95c90fb529520ab1422f8a8dca9d27f3fd05dd6d3abb0892e831c1fb91af4455b77eba748cd635f86537d19b473eacd2004721ce0717e342efc25d
7
- data.tar.gz: 537d25042eb17f6a86a65edba83e57001f1da160eacad93478062aec665cdad2798040a946b21bab53818fec2bed6089d1f15b22ca5fddc15e85fc5f1aa0d036
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
@@ -47,7 +47,7 @@ module Kennel
47
47
  dir = File.dirname(@file)
48
48
  FileUtils.mkdir_p(dir) unless File.directory?(dir)
49
49
 
50
- Tempfile.create do |tmp|
50
+ Tempfile.create "kennel-file-cache", dir do |tmp|
51
51
  Marshal.dump @data, tmp
52
52
  File.rename tmp.path, @file
53
53
  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.0"
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.0
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