kennel 1.121.1 → 1.123.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: c3eb87db53998806797dc41a7a73bacc01e1134655c7165c0498e30d7344e8fb
4
- data.tar.gz: 5ce8b48a088fdae20fc4b49211ca229f3f8e028f654eaa489b49ebf6de980646
3
+ metadata.gz: f09678f23d63da04d4b5db81eaef471bc0aa437b62ac75082f7bd161f8cd38e4
4
+ data.tar.gz: a570cc2617295581459a5977727049c243d492312554496f0b1beb432ad8efe8
5
5
  SHA512:
6
- metadata.gz: a22b79c15a1e7c29947011de54e58fbc5a3c71d988bf2a65e046304d6c890e0472405b548c06144b6f1f9f9fe2a6a91ed1a0608e59dcd925fe73b7023f9f91fd
7
- data.tar.gz: a3afc60d010f1364a46439d4f649d770cb1a76bb720856c9beada4cd89f4ab932b9aa1dfa8470feb97de969a0230752f94ec3a92f4e5a00275fa18fecdb46651
6
+ metadata.gz: 560a1daae6cb797b0e53e06a694e5624667b5597d3d6fb5c783e0166154a8f594064d7bfc1f64fb541723e2b633130d4e8039a8caa3434145552a63114a88678
7
+ data.tar.gz: '091c93ee61b87146c69e6f8782aab7a3cf2c349a03936f686a099eaca1a96d762193ee8385220fcc9d8c694edacf89aa57c6751e6e8a17bcffb37b1f913f9591'
data/Readme.md CHANGED
@@ -406,6 +406,14 @@ https://foo.datadog.com/monitor/123
406
406
  ### Find all monitors with No-Data
407
407
  `rake kennel:nodata TAG=team:foo`
408
408
 
409
+ ### Finding the tracking id of a resource
410
+
411
+ When trying to link resources together, this avoids having to go through datadog UI.
412
+
413
+ ```Bash
414
+ rake kennel:tracking_id ID=123 RESOURCE=monitor
415
+ ```
416
+
409
417
  <!-- NOT IN template/Readme.md -->
410
418
 
411
419
  ## Development
@@ -15,11 +15,13 @@ module Kennel
15
15
  end
16
16
 
17
17
  def open
18
- load_data
19
- expire_old_data
20
- yield self
21
- ensure
22
- persist
18
+ @data = load_data || {}
19
+ begin
20
+ expire_old_data
21
+ yield self
22
+ ensure
23
+ persist
24
+ end
23
25
  end
24
26
 
25
27
  def fetch(key, key_version)
@@ -35,12 +37,9 @@ module Kennel
35
37
  private
36
38
 
37
39
  def load_data
38
- @data =
39
- begin
40
- Marshal.load(File.read(@file)) # rubocop:disable Security/MarshalLoad
41
- rescue StandardError
42
- {}
43
- end
40
+ Marshal.load(File.read(@file)) # rubocop:disable Security/MarshalLoad
41
+ rescue Errno::ENOENT, TypeError, ArgumentError
42
+ nil
44
43
  end
45
44
 
46
45
  def persist
@@ -49,6 +48,7 @@ module Kennel
49
48
 
50
49
  Tempfile.create "kennel-file-cache", dir do |tmp|
51
50
  Marshal.dump @data, tmp
51
+ tmp.flush
52
52
  File.rename tmp.path, @file
53
53
  end
54
54
  end
@@ -24,7 +24,7 @@ module Kennel
24
24
  end
25
25
 
26
26
  def report(&block)
27
- output = Utils.strip_shell_control(Utils.tee_output(&block).strip)
27
+ output = Utils.tee_output(&block).strip
28
28
  rescue StandardError
29
29
  output = "Error:\n#{$ERROR_INFO.message}"
30
30
  raise
@@ -38,6 +38,8 @@ module Kennel
38
38
 
39
39
  case resource
40
40
  when "monitor"
41
+ raise "Import the synthetic test page and not the monitor" if data[:type] == "synthetics alert"
42
+
41
43
  # flatten monitor options so they are all on the base which is how Monitor builds them
42
44
  data.merge!(data.delete(:options))
43
45
  data.merge!(data.delete(:thresholds) || {})
@@ -60,6 +62,8 @@ module Kennel
60
62
  data[:critical] = data[:critical].to_i if data[:type] == "event alert"
61
63
 
62
64
  data[:type] = "query alert" if data[:type] == "metric alert"
65
+
66
+ link_composite_monitors(data)
63
67
  when "dashboard"
64
68
  widgets = data[:widgets]&.flat_map { |widget| widget.dig(:definition, :widgets) || [widget] }
65
69
  widgets&.each do |widget|
@@ -91,6 +95,18 @@ module Kennel
91
95
 
92
96
  private
93
97
 
98
+ def link_composite_monitors(data)
99
+ if data[:type] == "composite"
100
+ data[:query].gsub!(/\d+/) do |id|
101
+ object = Kennel.send(:api).show("monitor", id)
102
+ tracking_id = Kennel::Models::Monitor.parse_tracking_id(object)
103
+ tracking_id ? "%{#{tracking_id}}" : id
104
+ rescue StandardError # monitor not found
105
+ id # keep the id
106
+ end
107
+ end
108
+ end
109
+
94
110
  # reduce duplication in imports by using dry `q: :metadata` when possible
95
111
  def dry_up_widget_metadata!(widget)
96
112
  (widget.dig(:definition, :requests) || []).each do |request|
@@ -3,7 +3,6 @@ module Kennel
3
3
  module Models
4
4
  class Dashboard < Record
5
5
  include TemplateVariables
6
- include OptionalValidations
7
6
 
8
7
  READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [
9
8
  :author_handle, :author_name, :modified_at, :deleted_at, :url, :is_read_only, :notify_list, :restricted_roles
@@ -87,8 +86,7 @@ module Kennel
87
86
  tags: -> do # not inherited by default to make onboarding to using dashboard tags simple
88
87
  team = project.team
89
88
  team.tag_dashboards ? team.tags : []
90
- end,
91
- id: -> { nil }
89
+ end
92
90
  )
93
91
 
94
92
  class << self
@@ -152,29 +150,24 @@ module Kennel
152
150
  end
153
151
  end
154
152
 
155
- def as_json
156
- return @json if @json
153
+ def build_json
157
154
  all_widgets = render_definitions(definitions) + widgets
158
155
  expand_q all_widgets
159
156
  tags = tags()
160
157
  tags_as_string = (tags.empty? ? "" : " (#{tags.join(" ")})")
161
158
 
162
- @json = {
159
+ json = super.merge(
163
160
  layout_type: layout_type,
164
161
  title: "#{title}#{tags_as_string}#{LOCK}",
165
162
  description: description,
166
163
  template_variables: render_template_variables,
167
164
  template_variable_presets: template_variable_presets,
168
165
  widgets: all_widgets
169
- }
170
-
171
- @json[:reflow_type] = reflow_type if reflow_type # setting nil breaks create with "ordered"
166
+ )
172
167
 
173
- @json[:id] = id if id
168
+ json[:reflow_type] = reflow_type if reflow_type # setting nil breaks create with "ordered"
174
169
 
175
- validate_json(@json) if validate
176
-
177
- @json
170
+ json
178
171
  end
179
172
 
180
173
  def self.url(id)
@@ -210,9 +203,8 @@ module Kennel
210
203
  end
211
204
 
212
205
  def validate_update!(_actuals, diffs)
213
- if bad_diff = diffs.find { |diff| diff[1] == "layout_type" }
214
- invalid! "Datadog does not allow update of #{bad_diff[1]} (#{bad_diff[2].inspect} -> #{bad_diff[3].inspect})"
215
- end
206
+ _, path, from, to = diffs.find { |diff| diff[1] == "layout_type" }
207
+ invalid_update!(path, from, to) if path
216
208
  end
217
209
 
218
210
  private
@@ -2,8 +2,6 @@
2
2
  module Kennel
3
3
  module Models
4
4
  class Monitor < Record
5
- include OptionalValidations
6
-
7
5
  RENOTIFY_INTERVALS = [0, 10, 20, 30, 40, 50, 60, 90, 120, 180, 240, 300, 360, 720, 1440].freeze # minutes
8
6
  OPTIONAL_SERVICE_CHECK_THRESHOLDS = [:ok, :warning].freeze
9
7
  READONLY_ATTRIBUTES = superclass::READONLY_ATTRIBUTES + [
@@ -41,7 +39,6 @@ module Kennel
41
39
  renotify_interval: -> { project.team.renotify_interval },
42
40
  warning: -> { nil },
43
41
  ok: -> { nil },
44
- id: -> { nil },
45
42
  notify_no_data: -> { true }, # datadog sets this to false by default, but true is the safer
46
43
  no_data_timeframe: -> { 60 },
47
44
  notify_audit: -> { MONITOR_OPTION_DEFAULTS.fetch(:notify_audit) },
@@ -56,9 +53,8 @@ module Kennel
56
53
  priority: -> { MONITOR_DEFAULTS.fetch(:priority) }
57
54
  )
58
55
 
59
- def as_json
60
- return @as_json if @as_json
61
- data = {
56
+ def build_json
57
+ data = super.merge(
62
58
  name: "#{name}#{LOCK}",
63
59
  type: type,
64
60
  query: query.strip,
@@ -79,9 +75,7 @@ module Kennel
79
75
  locked: false, # setting this to true prevents any edit and breaks updates when using replace workflow
80
76
  renotify_interval: renotify_interval || 0
81
77
  }
82
- }
83
-
84
- data[:id] = id if id
78
+ )
85
79
 
86
80
  options = data[:options]
87
81
  if data.fetch(:type) != "composite"
@@ -120,9 +114,7 @@ module Kennel
120
114
  options[:renotify_statuses] = statuses
121
115
  end
122
116
 
123
- validate_json(data) if validate
124
-
125
- @as_json = data
117
+ data
126
118
  end
127
119
 
128
120
  def resolve_linked_tracking_ids!(id_map, **args)
@@ -140,7 +132,7 @@ module Kennel
140
132
  # ensure type does not change, but not if it's metric->query which is supported and used by importer.rb
141
133
  _, path, from, to = diffs.detect { |_, path, _, _| path == "type" }
142
134
  if path && !(from == "metric alert" && to == "query alert")
143
- invalid! "Datadog does not allow update of #{path} (#{from.inspect} -> #{to.inspect})"
135
+ invalid_update!(path, from, to)
144
136
  end
145
137
  end
146
138
 
@@ -20,7 +20,7 @@ module Kennel
20
20
  def validated_parts
21
21
  all = parts
22
22
  unless all.is_a?(Array) && all.all? { |part| part.is_a?(Record) }
23
- invalid! "#parts must return an array of Records"
23
+ raise "Project #{kennel_id} #parts must return an array of Records"
24
24
  end
25
25
 
26
26
  validate_parts(all)
@@ -29,11 +29,6 @@ module Kennel
29
29
 
30
30
  private
31
31
 
32
- # let users know which project/resource failed when something happens during diffing where the backtrace is hidden
33
- def invalid!(message)
34
- raise ValidationError, "#{kennel_id} #{message}"
35
- end
36
-
37
32
  # hook for users to add custom validations via `prepend`
38
33
  def validate_parts(parts)
39
34
  end
@@ -2,6 +2,8 @@
2
2
  module Kennel
3
3
  module Models
4
4
  class Record < Base
5
+ include OptionalValidations
6
+
5
7
  # Apart from if you just don't like the default for some reason,
6
8
  # overriding MARKER_TEXT allows for namespacing within the same
7
9
  # Datadog account. If you run one Kennel setup with marker text
@@ -30,6 +32,8 @@ module Kennel
30
32
 
31
33
  settings :id, :kennel_id
32
34
 
35
+ defaults(id: nil)
36
+
33
37
  class << self
34
38
  def parse_any_url(url)
35
39
  subclasses.detect do |s|
@@ -96,7 +100,7 @@ module Kennel
96
100
  @tracking_id ||= begin
97
101
  id = "#{project.kennel_id}:#{kennel_id}"
98
102
  unless id.match?(ALLOWED_KENNEL_ID_REGEX) # <-> parse_tracking_id
99
- raise ValidationError, "#{id} must match #{ALLOWED_KENNEL_ID_REGEX}"
103
+ raise "#{id} must match #{ALLOWED_KENNEL_ID_REGEX}"
100
104
  end
101
105
  id
102
106
  end
@@ -108,7 +112,7 @@ module Kennel
108
112
  def add_tracking_id
109
113
  json = as_json
110
114
  if self.class.parse_tracking_id(json)
111
- invalid! "remove \"-- #{MARKER_TEXT}\" line it from #{self.class::TRACKING_FIELD} to copy a resource"
115
+ raise "#{tracking_id} Remove \"-- #{MARKER_TEXT}\" line from #{self.class::TRACKING_FIELD} to copy a resource"
112
116
  end
113
117
  json[self.class::TRACKING_FIELD] =
114
118
  "#{json[self.class::TRACKING_FIELD]}\n" \
@@ -119,9 +123,29 @@ module Kennel
119
123
  self.class.remove_tracking_id(as_json)
120
124
  end
121
125
 
126
+ def build_json
127
+ {
128
+ id: id
129
+ }.compact
130
+ end
131
+
132
+ def as_json
133
+ @as_json ||= begin
134
+ json = build_json
135
+ (id = json.delete(:id)) && json[:id] = id
136
+ validate_json(json) if validate
137
+ json
138
+ end
139
+ end
140
+
141
+ # Can raise DisallowedUpdateError
122
142
  def validate_update!(*)
123
143
  end
124
144
 
145
+ def invalid_update!(field, old_value, new_value)
146
+ raise DisallowedUpdateError, "#{tracking_id} Datadog does not allow update of #{field} (#{old_value.inspect} -> #{new_value.inspect})"
147
+ end
148
+
125
149
  private
126
150
 
127
151
  def resolve(value, type, id_map, force:)
@@ -133,20 +157,24 @@ module Kennel
133
157
  id.is_a?(String) && id.include?(":")
134
158
  end
135
159
 
136
- def resolve_link(tracking_id, type, id_map, force:)
137
- if id_map.new?(type.to_s, tracking_id)
160
+ def resolve_link(sought_tracking_id, sought_type, id_map, force:)
161
+ if id_map.new?(sought_type.to_s, sought_tracking_id)
138
162
  if force
139
- invalid!(
140
- "#{type} #{tracking_id} was referenced but is also created by the current run.\n" \
141
- "It could not be created because of a circular dependency, try creating only some of the resources"
142
- )
163
+ raise UnresolvableIdError, <<~MESSAGE
164
+ #{tracking_id} #{sought_type} #{sought_tracking_id} was referenced but is also created by the current run.
165
+ It could not be created because of a circular dependency. Try creating only some of the resources.
166
+ MESSAGE
143
167
  else
144
168
  nil # will be re-resolved after the linked object was created
145
169
  end
146
- elsif id = id_map.get(type.to_s, tracking_id)
170
+ elsif id = id_map.get(sought_type.to_s, sought_tracking_id)
147
171
  id
148
172
  else
149
- invalid! "Unable to find #{type} #{tracking_id} (does not exist and is not being created by the current run)"
173
+ raise UnresolvableIdError, <<~MESSAGE
174
+ #{tracking_id} Unable to find #{sought_type} #{sought_tracking_id}
175
+ This is either because it doesn't exist, and isn't being created by the current run;
176
+ or it does exist, but is being deleted.
177
+ MESSAGE
150
178
  end
151
179
  end
152
180
 
@@ -15,7 +15,6 @@ module Kennel
15
15
  settings :type, :description, :thresholds, :query, :tags, :monitor_ids, :monitor_tags, :name, :groups
16
16
 
17
17
  defaults(
18
- id: -> { nil },
19
18
  tags: -> { @project.tags },
20
19
  query: -> { DEFAULTS.fetch(:query) },
21
20
  description: -> { DEFAULTS.fetch(:description) },
@@ -24,35 +23,25 @@ module Kennel
24
23
  groups: -> { DEFAULTS.fetch(:groups) }
25
24
  )
26
25
 
27
- def initialize(*)
28
- super
29
- if thresholds.any? { |t| t[:warning] && t[:warning].to_f <= t[:critical].to_f }
30
- raise ValidationError, "Threshold warning must be greater-than critical value"
31
- end
32
- end
33
-
34
- def as_json
35
- return @as_json if @as_json
36
- data = {
26
+ def build_json
27
+ data = super.merge(
37
28
  name: "#{name}#{LOCK}",
38
29
  description: description,
39
30
  thresholds: thresholds,
40
31
  monitor_ids: monitor_ids,
41
32
  tags: tags.uniq,
42
33
  type: type
43
- }
34
+ )
44
35
 
45
36
  if v = query
46
37
  data[:query] = v
47
38
  end
48
- if v = id
49
- data[:id] = v
50
- end
39
+
51
40
  if v = groups
52
41
  data[:groups] = v
53
42
  end
54
43
 
55
- @as_json = data
44
+ data
56
45
  end
57
46
 
58
47
  def self.api_resource
@@ -89,6 +78,16 @@ module Kennel
89
78
 
90
79
  ignore_default(expected, actual, DEFAULTS)
91
80
  end
81
+
82
+ private
83
+
84
+ def validate_json(data)
85
+ super
86
+
87
+ if data[:thresholds].any? { |t| t[:warning] && t[:warning].to_f <= t[:critical].to_f }
88
+ invalid! "Threshold warning must be greater-than critical value"
89
+ end
90
+ end
92
91
  end
93
92
  end
94
93
  end
@@ -11,15 +11,14 @@ module Kennel
11
11
  settings :tags, :config, :message, :subtype, :type, :name, :locations, :options
12
12
 
13
13
  defaults(
14
- id: -> { nil },
15
14
  tags: -> { @project.tags },
16
15
  message: -> { "\n\n#{project.mention}" }
17
16
  )
18
17
 
19
- def as_json
20
- return @as_json if @as_json
18
+ def build_json
21
19
  locations = locations()
22
- data = {
20
+
21
+ super.merge(
23
22
  message: message,
24
23
  tags: tags,
25
24
  config: config,
@@ -28,13 +27,7 @@ module Kennel
28
27
  options: options,
29
28
  name: "#{name}#{LOCK}",
30
29
  locations: locations == :all ? LOCATIONS : locations
31
- }
32
-
33
- if v = id
34
- data[:id] = v
35
- end
36
-
37
- @as_json = data
30
+ )
38
31
  end
39
32
 
40
33
  def self.api_resource
@@ -4,7 +4,9 @@ require "benchmark"
4
4
  module Kennel
5
5
  class Progress
6
6
  # print what we are doing and a spinner until it is done ... then show how long it took
7
- def self.progress(name)
7
+ def self.progress(name, interval: 0.2, &block)
8
+ return progress_no_tty(name, &block) unless Kennel.err.tty?
9
+
8
10
  Kennel.err.print "#{name} ... "
9
11
 
10
12
  stop = false
@@ -16,15 +18,20 @@ module Kennel
16
18
  loop do
17
19
  break if stop
18
20
  Kennel.err.print animation[count % animation.size]
19
- sleep 0.2
21
+ sleep interval
20
22
  Kennel.err.print "\b"
21
23
  count += 1
22
24
  end
23
25
  end
24
26
 
25
- time = Benchmark.realtime { result = yield }
27
+ time = Benchmark.realtime { result = block.call }
26
28
 
27
29
  stop = true
30
+ begin
31
+ spinner.run # wake thread, so it stops itself
32
+ rescue ThreadError
33
+ # thread was already dead, but we can't check with .alive? since it's a race condition
34
+ end
28
35
  spinner.join
29
36
  Kennel.err.print "#{time.round(2)}s\n"
30
37
 
@@ -32,5 +39,17 @@ module Kennel
32
39
  ensure
33
40
  stop = true # make thread stop without killing it
34
41
  end
42
+
43
+ class << self
44
+ private
45
+
46
+ def progress_no_tty(name)
47
+ Kennel.err.puts "#{name} ..."
48
+ result = nil
49
+ time = Benchmark.realtime { result = yield }
50
+ Kennel.err.puts "#{name} ... #{time.round(2)}s"
51
+ result
52
+ end
53
+ end
35
54
  end
36
55
  end
data/lib/kennel/syncer.rb CHANGED
@@ -42,7 +42,7 @@ module Kennel
42
42
 
43
43
  def confirm
44
44
  return false if noop?
45
- return true if ENV["CI"] || !STDIN.tty?
45
+ return true if ENV["CI"] || !STDIN.tty? || !Kennel.err.tty?
46
46
  Utils.ask("Execute Plan ?")
47
47
  end
48
48
 
@@ -104,11 +104,11 @@ module Kennel
104
104
  def resolved?(e)
105
105
  assert_resolved e
106
106
  true
107
- rescue ValidationError
107
+ rescue UnresolvableIdError
108
108
  false
109
109
  end
110
110
 
111
- # raises ValidationError when not resolved
111
+ # raises UnresolvableIdError when not resolved
112
112
  def assert_resolved(e)
113
113
  resolve_linked_tracking_ids! [e], force: true
114
114
  end
data/lib/kennel/tasks.rb CHANGED
@@ -233,6 +233,17 @@ namespace :kennel do
233
233
  end
234
234
  end
235
235
 
236
+ desc "Resolve given id to kennel tracking-id RESOURCE= ID="
237
+ task tracking_id: "kennel:environment" do
238
+ resource = ENV.fetch("RESOURCE")
239
+ id = ENV.fetch("ID")
240
+ klass =
241
+ Kennel::Models::Record.subclasses.detect { |s| s.api_resource == resource } ||
242
+ raise("resource #{resource} not know")
243
+ object = Kennel.send(:api).show(resource, id)
244
+ Kennel.out.puts klass.parse_tracking_id(object)
245
+ end
246
+
236
247
  task :environment do
237
248
  Kennel::Tasks.load_environment
238
249
  end
data/lib/kennel/utils.rb CHANGED
@@ -42,7 +42,7 @@ module Kennel
42
42
  end
43
43
 
44
44
  def ask(question)
45
- Kennel.err.printf color(:red, "#{question} - press 'y' to continue: ")
45
+ Kennel.err.printf color(:red, "#{question} - press 'y' to continue: ", force: true)
46
46
  begin
47
47
  STDIN.gets.chomp == "y"
48
48
  rescue Interrupt # do not show a backtrace if user decides to Ctrl+C here
@@ -51,12 +51,10 @@ module Kennel
51
51
  end
52
52
  end
53
53
 
54
- def color(color, text)
55
- "\e[#{COLORS.fetch(color)}m#{text}\e[0m"
56
- end
54
+ def color(color, text, force: false)
55
+ return text unless force || Kennel.out.tty?
57
56
 
58
- def strip_shell_control(text)
59
- text.gsub(/\e\[\d+m(.*?)\e\[0m/, "\\1").gsub(/.#{Regexp.escape("\b")}/, "")
57
+ "\e[#{COLORS.fetch(color)}m#{text}\e[0m"
60
58
  end
61
59
 
62
60
  def capture_stdout
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Kennel
3
- VERSION = "1.121.1"
3
+ VERSION = "1.123.0"
4
4
  end
data/lib/kennel.rb CHANGED
@@ -40,8 +40,9 @@ module Teams
40
40
  end
41
41
 
42
42
  module Kennel
43
- class ValidationError < RuntimeError
44
- end
43
+ ValidationError = Class.new(RuntimeError)
44
+ UnresolvableIdError = Class.new(RuntimeError)
45
+ DisallowedUpdateError = Class.new(RuntimeError)
45
46
 
46
47
  include Kennel::Compatibility
47
48
 
data/template/Readme.md CHANGED
@@ -388,3 +388,11 @@ https://foo.datadog.com/monitor/123
388
388
  ### Find all monitors with No-Data
389
389
  `rake kennel:nodata TAG=team:foo`
390
390
 
391
+ ### Finding the tracking id of a resource
392
+
393
+ When trying to link resources together, this avoids having to go through datadog UI.
394
+
395
+ ```Bash
396
+ rake kennel:tracking_id ID=123 RESOURCE=monitor
397
+ ```
398
+
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.121.1
4
+ version: 1.123.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-30 00:00:00.000000000 Z
11
+ date: 2022-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs